/*
* This file is part of Luma3DS
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include
#include "gdb/tio.h"
#include "gdb/hio.h"
#include "gdb/net.h"
#include "gdb/mem.h"
#include "gdb/debug.h"
#include "fmt.h"
#include
#include
#include <3ds/util/utf.h>
#define GDBHIO_O_RDONLY 0x0
#define GDBHIO_O_WRONLY 0x1
#define GDBHIO_O_RDWR 0x2
#define GDBHIO_O_ACCMODE 0x3
#define GDBHIO_O_APPEND 0x8
#define GDBHIO_O_CREAT 0x200
#define GDBHIO_O_TRUNC 0x400
#define GDBHIO_O_EXCL 0x800
#define GDBHIO_O_SUPPORTED (GDBHIO_O_RDONLY | GDBHIO_O_WRONLY| \
GDBHIO_O_RDWR | GDBHIO_O_APPEND| \
GDBHIO_O_CREAT | GDBHIO_O_TRUNC| \
GDBHIO_O_EXCL)
#define GDBHIO_S_IFREG 0100000
#define GDBHIO_S_IFDIR 040000
#define GDBHIO_S_IFCHR 020000
#define GDBHIO_S_IRUSR 0400
#define GDBHIO_S_IWUSR 0200
#define GDBHIO_S_IXUSR 0100
#define GDBHIO_S_IRWXU 0700
#define GDBHIO_S_IRGRP 040
#define GDBHIO_S_IWGRP 020
#define GDBHIO_S_IXGRP 010
#define GDBHIO_S_IRWXG 070
#define GDBHIO_S_IROTH 04
#define GDBHIO_S_IWOTH 02
#define GDBHIO_S_IXOTH 01
#define GDBHIO_S_IRWXO 07
#define GDBHIO_S_SUPPORTED (GDBHIO_S_IFREG|GDBHIO_S_IFDIR| \
GDBHIO_S_IRWXU|GDBHIO_S_IRWXG| \
GDBHIO_S_IRWXO)
#define GDBHIO_SEEK_SET 0
#define GDBHIO_SEEK_CUR 1
#define GDBHIO_SEEK_END 2
#define GDBHIO_EPERM 1
#define GDBHIO_ENOENT 2
#define GDBHIO_EINTR 4
#define GDBHIO_EIO 5
#define GDBHIO_EBADF 9
#define GDBHIO_EACCES 13
#define GDBHIO_EFAULT 14
#define GDBHIO_EBUSY 16
#define GDBHIO_EEXIST 17
#define GDBHIO_ENODEV 19
#define GDBHIO_ENOTDIR 20
#define GDBHIO_EISDIR 21
#define GDBHIO_EINVAL 22
#define GDBHIO_ENFILE 23
#define GDBHIO_EMFILE 24
#define GDBHIO_EFBIG 27
#define GDBHIO_ENOSPC 28
#define GDBHIO_ESPIPE 29
#define GDBHIO_EROFS 30
#define GDBHIO_ENOSYS 88
#define GDBHIO_ENAMETOOLONG 91
#define GDBHIO_EUNKNOWN 9999
#define GDB_TIO_HANDLER(name) GDB_HANDLER(Tio##name)
#define GDB_DECLARE_TIO_HANDLER(name) GDB_DECLARE_HANDLER(Tio##name)
// https://sourceware.org/gdb/onlinedocs/gdb/struct-stat.html#struct-stat
typedef u32 gdbhio_time_t;
typedef int gdbhio_mode_t;
struct gdbhio_stat {
unsigned int st_dev; /* device */
unsigned int st_ino; /* inode */
gdbhio_mode_t st_mode; /* protection */
unsigned int st_nlink; /* number of hard links */
unsigned int st_uid; /* user ID of owner */
unsigned int st_gid; /* group ID of owner */
unsigned int st_rdev; /* device type (if inode device) */
u64 st_size; /* total size, in bytes */
u64 st_blksize; /* blocksize for filesystem I/O */
u64 st_blocks; /* number of blocks allocated */
gdbhio_time_t st_atime; /* time of last access */
gdbhio_time_t st_mtime; /* time of last modification */
gdbhio_time_t st_ctime; /* time of last change */
};
static void GDB_TioMakeStructStat(struct gdbhio_stat *out, const struct gdbhio_stat *in)
{
memset(out, 0, sizeof(struct gdbhio_stat));
out->st_dev = __builtin_bswap32(in->st_dev);
out->st_ino = __builtin_bswap32(in->st_ino);
out->st_mode = __builtin_bswap32(in->st_dev);
out->st_nlink = __builtin_bswap32(in->st_nlink);
out->st_uid = __builtin_bswap32(in->st_uid);
out->st_gid = __builtin_bswap32(in->st_gid);
out->st_rdev = __builtin_bswap32(in->st_rdev);
out->st_size = __builtin_bswap64(in->st_size);
out->st_blksize = __builtin_bswap64(in->st_blksize);
out->st_blocks = __builtin_bswap64(in->st_blocks);
out->st_atime = __builtin_bswap32(in->st_atime);
out->st_mtime = __builtin_bswap32(in->st_mtime);
out->st_ctime = __builtin_bswap32(in->st_ctime);
}
// Inspired from https://github.com/smealum/ctrulib/blob/master/libctru/source/sdmc_dev.c
static int GDB_TioConvertResult(Result res)
{
switch(res)
{
case 0: return 0;
case 0x082044BE: return GDBHIO_EEXIST;
case 0x086044D2: return GDBHIO_ENOSPC;
case 0xC8804478:
case 0xC92044FA: return GDBHIO_ENOENT;
case 0xE0E046BE: return GDBHIO_EINVAL;
case 0xE0E046BF: return GDBHIO_ENAMETOOLONG;
default: return GDBHIO_EUNKNOWN;
}
}
static GdbTioFileInfo *GDB_TioConvertFd(GDBContext *ctx, int fd)
{
if (fd < 3 || fd - 3 >= MAX_TIO_OPEN_FILE)
return NULL;
GdbTioFileInfo *slot = &ctx->openTioFileInfos[fd - 3];
return slot->f.handle == 0 ? NULL : slot;
}
static int GDB_TioRegisterFile(GDBContext *ctx, Handle h, int gdbOpenFlags)
{
int fd;
for (fd = 3; fd < 3 + MAX_TIO_OPEN_FILE && GDB_TioConvertFd(ctx, fd) != NULL; fd++);
if (fd >= 3 + MAX_TIO_OPEN_FILE)
return -1;
GdbTioFileInfo *slot = &ctx->openTioFileInfos[fd - 3];
memset(slot, 0, sizeof(GdbTioFileInfo));
slot->f.handle = h;
slot->flags = gdbOpenFlags;
ctx->numOpenTioFiles++;
return fd;
}
static int GDB_MakeUtf16Path(FS_Path *outPath, const char *path)
{
u16 *p16 = (u16 *)outPath->data;
outPath->type = PATH_UTF16;
ssize_t units = utf8_to_utf16(p16, (const u8 *) path, PATH_MAX);
if (units < 0) return GDBHIO_EINVAL;
else if (units >= PATH_MAX) return GDBHIO_ENAMETOOLONG;
p16[units] = 0;
outPath->size = 2 * (units + 1);
return 0;
}
static inline int GDB_TioReplyErrno(GDBContext *ctx, int err)
{
return GDB_SendFormattedPacket(ctx, "F-1,%lx", (u32)err);
}
GDB_DECLARE_TIO_HANDLER(Open)
{
s64 out = 1;
svcGetSystemInfo(&out, 0x10000, 0x203);
bool isSdMode = (bool)out;
FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
u16 fsNameBuf[PATH_MAX + 1];
FS_Path fsPath;
fsPath.data = fsNameBuf;
char *comma = strchr(ctx->commandData, ',');
if (comma == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
*comma = 0;
char *fileName = ctx->commandData;
u32 args[2] = {0};
if (GDB_ParseHexIntegerList(args, comma + 1, 2, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
int flags = (int)args[1];
IFile f = {0};
u32 fsFlags = 0;
if (ctx->numOpenTioFiles >= MAX_TIO_OPEN_FILE)
return GDB_TioReplyErrno(ctx, GDBHIO_EMFILE);
int err = GDB_MakeUtf16Path(&fsPath, fileName);
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
switch (flags & GDBHIO_O_ACCMODE)
{
case GDBHIO_O_RDONLY:
fsFlags |= FS_OPEN_READ;
if (flags & GDBHIO_O_APPEND) err = GDBHIO_EINVAL;
break;
case GDBHIO_O_WRONLY:
fsFlags |= FS_OPEN_WRITE;
break;
case GDBHIO_O_RDWR:
fsFlags |= FS_OPEN_READ | FS_OPEN_WRITE;
break;
default:
err = GDBHIO_EINVAL;
break;
}
if (flags & GDBHIO_O_CREAT)
fsFlags |= FS_OPEN_CREATE;
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
if ((flags & O_CREAT) && (flags & O_EXCL))
{
err = GDB_TioConvertResult(FSUSER_CreateFile(archiveId, fsPath, 0, 0));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
}
err = GDB_TioConvertResult(IFile_Open(&f, archiveId, fsPath, fsMakePath(PATH_EMPTY, ""), fsFlags));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
if((flags & GDBHIO_O_ACCMODE) != GDBHIO_O_RDONLY && (flags & GDBHIO_O_TRUNC))
{
f.size = 0;
err = GDB_TioConvertResult(FSFILE_SetSize(f.handle, 0));
if (err != 0)
{
IFile_Close(&f);
return GDB_TioReplyErrno(ctx, err);
}
}
int fd = GDB_TioRegisterFile(ctx, f.handle, flags & (GDBHIO_O_ACCMODE | GDBHIO_O_APPEND));
if (fd < 0)
return GDB_TioReplyErrno(ctx, GDBHIO_EMFILE);
return GDB_SendFormattedPacket(ctx, "F%lx", (u32)fd);
}
GDB_DECLARE_TIO_HANDLER(Close)
{
int fd;
if (GDB_ParseHexIntegerList((u32 *)&fd, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
GdbTioFileInfo *fi = GDB_TioConvertFd(ctx, fd);
if (fi == NULL)
return GDB_TioReplyErrno(ctx, GDBHIO_EBADF);
int err = GDB_TioConvertResult(IFile_Close(&fi->f));
memset(fi, 0, sizeof(GdbTioFileInfo));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
else
return GDB_SendPacket(ctx, "F0", 2);
}
GDB_DECLARE_TIO_HANDLER(Read)
{
// "$F;#XX"
char buf2[GDB_BUF_LEN - 4];
u8 buf[sizeof(buf2) - 2 - 8];
u32 args[3];
if (GDB_ParseHexIntegerList(args, ctx->commandData, 3, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
u64 numRead = 0;
int fd = (int)args[0];
u32 count = args[1] > sizeof(buf) ? sizeof(buf) : args[1];
u32 offset = args[2];
GdbTioFileInfo *fi = GDB_TioConvertFd(ctx, fd);
if (fi == NULL)
return GDB_TioReplyErrno(ctx, GDBHIO_EBADF);
fi->f.pos = offset;
int err = GDB_TioConvertResult(IFile_Read(&fi->f, &numRead, buf, count));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
int pos = sprintf(buf2, "F%lx;", (u32)numRead);
u32 actualCount = GDB_EscapeBinaryData(buf2 + pos, buf, (u32)numRead, sizeof(buf));
return GDB_SendPacket(ctx, buf2, pos + actualCount);
}
GDB_DECLARE_TIO_HANDLER(Write)
{
u8 buf[GDB_BUF_LEN];
u32 args[2];
const char *comma = GDB_ParseHexIntegerList(args, ctx->commandData, 2, ',');
if (comma == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
int fd = (int)args[0];
u32 offset = args[1];
const char *escData = comma + 1;
u32 count = GDB_UnescapeBinaryData(buf, escData, ctx->commandEnd - escData);
GdbTioFileInfo *fi = GDB_TioConvertFd(ctx, fd);
if (fi == NULL)
return GDB_TioReplyErrno(ctx, GDBHIO_EBADF);
fi->f.pos = offset;
u64 written;
int err = GDB_TioConvertResult(IFile_Write(&fi->f, &written, buf, count, 0));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
else
return GDB_SendFormattedPacket(ctx, "F%lx", (u32)written);
}
GDB_DECLARE_TIO_HANDLER(Stat)
{
char *fileName = ctx->commandData;
if (*fileName == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
s64 out = 1;
svcGetSystemInfo(&out, 0x10000, 0x203);
bool isSdMode = (bool)out;
FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
u16 fsNameBuf[PATH_MAX + 1];
FS_Path fsPath;
fsPath.data = fsNameBuf;
int err = GDB_MakeUtf16Path(&fsPath, fileName);
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
Handle fileHandle;
Handle dirHandle;
FS_Archive ar;
err = GDB_TioConvertResult(FSUSER_OpenArchive(&ar, archiveId, fsMakePath(PATH_EMPTY, "")));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
struct gdbhio_stat gdbSt = {0};
err = GDB_TioConvertResult(FSUSER_OpenFile(&fileHandle, ar, fsPath, FS_OPEN_READ, 0));
if (err == 0)
{
u64 size = 0;
err = GDB_TioConvertResult(FSFILE_GetSize(fileHandle, &size));
FSFILE_Close(fileHandle);
if (err == 0)
{
gdbSt.st_nlink = 1;
gdbSt.st_mode = GDBHIO_S_IFREG | GDBHIO_S_IRUSR | GDBHIO_S_IWUSR |
GDBHIO_S_IRGRP | GDBHIO_S_IWGRP | GDBHIO_S_IROTH | GDBHIO_S_IWOTH;
}
}
else
{
err = GDB_TioConvertResult(FSUSER_OpenDirectory(&dirHandle, ar, fsPath));
if (err == 0)
{
FSDIR_Close(dirHandle);
gdbSt.st_nlink = 1;
gdbSt.st_mode = GDBHIO_S_IFDIR | GDBHIO_S_IRWXU | GDBHIO_S_IRWXG | GDBHIO_S_IRWXO;
}
}
FSUSER_CloseArchive(ar);
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
struct gdbhio_stat gdbStFinal;
GDB_TioMakeStructStat(&gdbStFinal, &gdbSt);
char buf[3 + 2 * sizeof(struct gdbhio_stat)] = "F0;";
u32 actualCount = GDB_EscapeBinaryData(buf + 3, buf, sizeof(struct gdbhio_stat), 2 * sizeof(struct gdbhio_stat));
return GDB_SendPacket(ctx, buf, 3 + actualCount);
}
GDB_DECLARE_TIO_HANDLER(Unlink)
{
char *fileName = ctx->commandData;
if (*fileName == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
s64 out = 1;
svcGetSystemInfo(&out, 0x10000, 0x203);
bool isSdMode = (bool)out;
FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
u16 fsNameBuf[PATH_MAX + 1];
FS_Path fsPath;
fsPath.data = fsNameBuf;
int err = GDB_MakeUtf16Path(&fsPath, fileName);
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
err = GDB_TioConvertResult(FSUSER_DeleteFile(archiveId, fsPath));
if (err != 0)
return GDB_TioReplyErrno(ctx, err);
return GDB_SendPacket(ctx, "F0", 2);
}
GDB_DECLARE_TIO_HANDLER(Readlink)
{
// Not really supported
char *fileName = ctx->commandData;
if (*fileName == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
return GDB_SendPacket(ctx, "F0;", 3);
}
GDB_DECLARE_TIO_HANDLER(Setfs)
{
// Largely ignored
u32 pid;
if (GDB_ParseHexIntegerList(&pid, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
return GDB_SendPacket(ctx, "F0", 2);
}
static const struct
{
const char *name;
GDBCommandHandler handler;
} gdbTioCommandHandlers[] =
{
{ "open", GDB_TIO_HANDLER(Open) },
{ "close", GDB_TIO_HANDLER(Close) },
{ "pread", GDB_TIO_HANDLER(Read) },
{ "pwrite", GDB_TIO_HANDLER(Write) },
{ "fstat", GDB_TIO_HANDLER(Stat) },
{ "unlink", GDB_TIO_HANDLER(Unlink) },
{ "readlink", GDB_TIO_HANDLER(Readlink) },
{ "setfs", GDB_TIO_HANDLER(Setfs) },
};
GDB_DECLARE_VERBOSE_HANDLER(File)
{
if (*ctx->commandData == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
char *colon = strchr(ctx->commandData, ':');
if (colon == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
*colon = 0;
for(u32 i = 0; i < sizeof(gdbTioCommandHandlers) / sizeof(gdbTioCommandHandlers[0]); i++)
{
if(strcmp(gdbTioCommandHandlers[i].name, ctx->commandData) == 0)
{
ctx->commandData = colon + 1;
return gdbTioCommandHandlers[i].handler(ctx);
}
}
return GDB_HandleUnsupported(ctx); // No handler found!
}