/* * Copyright (c) 2018 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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 . */ #include #include #include #include #include #include #include #include #include "raw_dev.h" static int rawdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode); static int rawdev_close(struct _reent *r, void *fd); static ssize_t rawdev_write(struct _reent *r, void *fd, const char *ptr, size_t len); static ssize_t rawdev_read(struct _reent *r, void *fd, char *ptr, size_t len); static off_t rawdev_seek(struct _reent *r, void *fd, off_t pos, int whence); static int rawdev_fstat(struct _reent *r, void *fd, struct stat *st); static int rawdev_stat(struct _reent *r, const char *file, struct stat *st); static int rawdev_fsync(struct _reent *r, void *fd); typedef struct rawdev_device_t { devoptab_t devoptab; uint8_t *tmp_sector; device_partition_t devpart; char name[32+1]; char root_path[34+1]; bool setup, registered; } rawdev_device_t; typedef struct rawdev_file_t { rawdev_device_t *device; int open_flags; uint64_t offset; } rawdev_file_t; static rawdev_device_t g_rawdev_devices[RAWDEV_MAX_DEVICES] = {0}; static devoptab_t g_rawdev_devoptab = { .structSize = sizeof(rawdev_file_t), .open_r = rawdev_open, .close_r = rawdev_close, .write_r = rawdev_write, .read_r = rawdev_read, .seek_r = rawdev_seek, .fstat_r = rawdev_fstat, .stat_r = rawdev_stat, .fsync_r = rawdev_fsync, .deviceData = NULL, }; static rawdev_device_t *rawdev_find_device(const char *name) { for (size_t i = 0; i < RAWDEV_MAX_DEVICES; i++) { if (g_rawdev_devices[i].setup && strcmp(g_rawdev_devices[i].name, name) == 0) { return &g_rawdev_devices[i]; } } return NULL; } int rawdev_mount_device(const char *name, const device_partition_t *devpart, bool initialize_immediately) { rawdev_device_t *device = NULL; char drname[40]; strcpy(drname, name); strcat(drname, ":"); if (name[0] == '\0' || devpart == NULL) { errno = EINVAL; return -1; } if (strlen(name) > 32) { errno = ENAMETOOLONG; return -1; } if (rawdev_find_device(name) != NULL) { errno = EEXIST; /* Device already exists */ return -1; } /* Find an unused slot. */ for(size_t i = 0; i < RAWDEV_MAX_DEVICES; i++) { if (!g_rawdev_devices[i].setup) { device = &g_rawdev_devices[i]; break; } } if (device == NULL) { errno = ENOMEM; return -1; } memset(device, 0, sizeof(rawdev_device_t)); device->devoptab = g_rawdev_devoptab; device->devpart = *devpart; strcpy(device->name, name); strcpy(device->root_path, name); strcat(device->root_path, ":/"); device->devoptab.name = device->name; device->devoptab.deviceData = device; if (initialize_immediately) { int rc = device->devpart.initializer(&device->devpart); if (rc != 0) { errno = rc; return -1; } } device->tmp_sector = (uint8_t *)malloc(devpart->sector_size); if (device->tmp_sector == NULL) { errno = ENOMEM; return -1; } device->setup = true; device->registered = false; return 0; } int rawdev_register_device(const char *name) { rawdev_device_t *device = rawdev_find_device(name); if (device == NULL) { errno = ENOENT; return -1; } if (device->registered) { /* Do nothing if the device is already registered. */ return 0; } if (AddDevice(&device->devoptab) == -1) { errno = ENOMEM; return -1; } else { device->registered = true; return 0; } } int rawdev_unregister_device(const char *name) { rawdev_device_t *device = rawdev_find_device(name); char drname[40]; if (device == NULL) { errno = ENOENT; return -1; } if (!device->registered) { /* Do nothing if the device is not registered. */ return 0; } strcpy(drname, name); strcat(drname, ":"); if (RemoveDevice(drname) == -1) { errno = ENOENT; return -1; } else { device->registered = false; return 0; } } int rawdev_unmount_device(const char *name) { int rc; rawdev_device_t *device = rawdev_find_device(name); if (device == NULL) { errno = ENOENT; return -1; } rc = rawdev_unregister_device(name); if (rc == -1) { return -1; } free(device->tmp_sector); device->devpart.finalizer(&device->devpart); memset(device, 0, sizeof(rawdev_device_t)); return 0; } int rawdev_unmount_all(void) { for (size_t i = 0; i < RAWDEV_MAX_DEVICES; i++) { int rc = rawdev_unmount_device(g_rawdev_devices[i].name); if (rc != 0) { return rc; } } return 0; } static int rawdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) { (void)mode; rawdev_file_t *f = (rawdev_file_t *)fileStruct; rawdev_device_t *device = (rawdev_device_t *)(r->deviceData); /* Only allow "device:/". */ if (strcmp(path, device->root_path) != 0) { r->_errno = ENOENT; return -1; } /* Forbid some flags that we explicitely don't support.*/ if (flags & (O_APPEND | O_TRUNC | O_EXCL)) { r->_errno = EINVAL; return -1; } memset(f, 0, sizeof(rawdev_file_t)); f->device = device; f->open_flags = flags; return 0; } static int rawdev_close(struct _reent *r, void *fd) { (void)r; rawdev_file_t *f = (rawdev_file_t *)fd; memset(f, 0, sizeof(rawdev_file_t)); return 0; } static ssize_t rawdev_write(struct _reent *r, void *fd, const char *ptr, size_t len) { rawdev_file_t *f = (rawdev_file_t *)fd; rawdev_device_t *device = f->device; size_t sector_size = device->devpart.sector_size; uint64_t sector_begin = f->offset / sector_size; uint64_t sector_end = (f->offset + len + sector_size - 1) / sector_size; uint64_t sector_end_aligned; uint64_t current_sector = sector_begin; const uint8_t *data = (const uint8_t *)ptr; int no = 0; if (sector_end >= device->devpart.num_sectors) { len = (size_t)(sector_size * device->devpart.num_sectors - f->offset); sector_end = device->devpart.num_sectors; } sector_end_aligned = sector_end - ((f->offset + len) % sector_size != 0 ? 1 : 0); if (len == 0) { return 0; } /* Unaligned at the start, we need to read the sector and incorporate the data. */ if (f->offset % sector_size != 0) { size_t nb = (size_t)(len <= (sector_size - (f->offset % sector_size)) ? len : sector_size - (f->offset % sector_size)); no = device_partition_read_data(&device->devpart, device->tmp_sector, sector_begin, 1); if (no != 0) { r->_errno = no; return -1; } memcpy(device->tmp_sector + (f->offset % sector_size), data, nb); no = device_partition_write_data(&device->devpart, device->tmp_sector, sector_begin, 1); if (no != 0) { r->_errno = no; return -1; } /* Advance */ data += sector_size - (f->offset % sector_size); current_sector++; } /* Check if we're already done (otherwise this causes a bug in handling the last sector of the range). */ if (current_sector == sector_end) { f->offset += len; return len; } /* Write all of the sector-aligned data. */ if (current_sector != sector_end_aligned) { no = device_partition_write_data(&device->devpart, data, current_sector, sector_end_aligned - current_sector); if (no != 0) { r->_errno = no; return -1; } } data += sector_size * (sector_end_aligned - current_sector); current_sector = sector_end_aligned; /* Unaligned at the end, we need to read the sector and incorporate the data. */ if (sector_end != sector_end_aligned) { no = device_partition_read_data(&device->devpart, device->tmp_sector, sector_end_aligned, 1); if (no != 0) { r->_errno = no; return -1; } memcpy(device->tmp_sector, data, (size_t)((f->offset + len) % sector_size)); no = device_partition_write_data(&device->devpart, device->tmp_sector, sector_end_aligned, 1); if (no != 0) { r->_errno = no; return -1; } /* Advance */ data += sector_size - ((f->offset + len) % sector_size); current_sector++; } f->offset += len; return len; } static ssize_t rawdev_read(struct _reent *r, void *fd, char *ptr, size_t len) { rawdev_file_t *f = (rawdev_file_t *)fd; rawdev_device_t *device = f->device; size_t sector_size = device->devpart.sector_size; uint64_t sector_begin = f->offset / sector_size; uint64_t sector_end = (f->offset + len + sector_size - 1) / sector_size; uint64_t sector_end_aligned; uint64_t current_sector = sector_begin; uint8_t *data = (uint8_t *)ptr; int no = 0; if (sector_end >= device->devpart.num_sectors) { len = (size_t)(sector_size * device->devpart.num_sectors - f->offset); sector_end = device->devpart.num_sectors; } sector_end_aligned = sector_end - ((f->offset + len) % sector_size != 0 ? 1 : 0); if (len == 0) { return 0; } /* Unaligned at the start, we need to read the sector and incorporate the data. */ if (f->offset % sector_size != 0) { size_t nb = (size_t)(len <= (sector_size - (f->offset % sector_size)) ? len : sector_size - (f->offset % sector_size)); no = device_partition_read_data(&device->devpart, device->tmp_sector, sector_begin, 1); if (no != 0) { r->_errno = no; return -1; } memcpy(data, device->tmp_sector + (f->offset % sector_size), nb); /* Advance */ data += sector_size - (f->offset % sector_size); current_sector++; } /* Check if we're already done (otherwise this causes a bug in handling the last sector of the range). */ if (current_sector == sector_end) { f->offset += len; return len; } /* Read all of the sector-aligned data. */ if (current_sector != sector_end_aligned) { no = device_partition_read_data(&device->devpart, data, current_sector, sector_end_aligned - current_sector); if (no != 0) { r->_errno = no; return -1; } } data += sector_size * (sector_end_aligned - current_sector); current_sector = sector_end_aligned; /* Unaligned at the end, we need to read the sector and incorporate the data. */ if (sector_end != sector_end_aligned) { no = device_partition_read_data(&device->devpart, device->tmp_sector, sector_end_aligned, 1); if (no != 0) { r->_errno = no; return -1; } memcpy(data, device->tmp_sector, (size_t)((f->offset + len) % sector_size)); /* Advance */ data += sector_size - ((f->offset + len) % sector_size); current_sector++; } f->offset += len; return len; } static off_t rawdev_seek(struct _reent *r, void *fd, off_t pos, int whence) { rawdev_file_t *f = (rawdev_file_t *)fd; rawdev_device_t *device = f->device; uint64_t off; switch (whence) { case SEEK_SET: off = 0; break; case SEEK_CUR: off = f->offset; break; case SEEK_END: off = device->devpart.num_sectors * device->devpart.sector_size; break; default: r->_errno = EINVAL; return -1; } if (pos < 0 && pos + off < 0) { /* don't allow seek to before the beginning of the file */ r->_errno = EINVAL; return -1; } f->offset = (uint64_t)(pos + off); return (off_t)(pos + off); } static void rawdev_stat_impl(rawdev_device_t *device, struct stat *st) { memset(st, 0, sizeof(struct stat)); st->st_size = (off_t)(device->devpart.num_sectors * device->devpart.sector_size); st->st_nlink = 1; st->st_blksize = device->devpart.sector_size; st->st_blocks = st->st_size / st->st_blksize; st->st_mode = S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; } static int rawdev_fstat(struct _reent *r, void *fd, struct stat *st) { (void)r; rawdev_file_t *f = (rawdev_file_t *)fd; rawdev_device_t *device = f->device; rawdev_stat_impl(device, st); return 0; } static int rawdev_stat(struct _reent *r, const char *file, struct stat *st) { rawdev_device_t *device = (rawdev_device_t *)(r->deviceData); if (strcmp(file, device->root_path) != 0) { r->_errno = ENOENT; return -1; } rawdev_stat_impl(device, st); return 0; } static int rawdev_fsync(struct _reent *r, void *fd) { /* Nothing to do. */ (void)r; (void)fd; return 0; }