diff --git a/hexagonrpcd/apps_std.c b/hexagonrpcd/apps_std.c index 7172bf0..f5aeaba 100644 --- a/hexagonrpcd/apps_std.c +++ b/hexagonrpcd/apps_std.c @@ -20,6 +20,8 @@ */ #include +#include +#include #include #include #include @@ -150,13 +152,219 @@ static uint32_t apps_std_fseek(void *data, return 0; } +/* + * Open NAME below DIRFD as fopen(3) would for MODE. The Snapdragon Sensor + * Core writes into its registry while it initialises and takes the whole DSP + * down when that is refused. + */ +static int apps_std_open_mode(struct apps_std_ctx *ctx, int dirfd, + const char *name, const char *mode) +{ + const char *m; + bool writing = false; + int flags = 0; + int ret; + + for (m = mode; *m != '\0'; m++) { + switch (*m) { + case 'w': + writing = true; + flags |= O_CREAT | O_TRUNC; + break; + case 'a': + writing = true; + flags |= O_CREAT | O_APPEND; + break; + case '+': + writing = true; + break; + default: + break; + } + } + + if (!writing) + return hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, name); + + ret = hexagonfs_create(ctx->fds, ctx->rootfd, dirfd, name, flags); + + /* + * "r+" must not create the file, and on a read-only tree the DSP is + * normally only going to read it. + */ + if (ret < 0 && !(flags & O_CREAT) + && (ret == -EACCES || ret == -EROFS || ret == -ENOSYS)) + ret = hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, name); + + return ret; +} + +static uint32_t apps_std_fopen(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + uint32_t *out = outbufs[0].p; + const char *name = inbufs[1].p; + const char *mode = inbufs[2].p; + int dirfd, fd; + + // The name and mode must be NULL-terminated + if (name[inbufs[1].s - 1] != 0 || mode[inbufs[2].s - 1] != 0) + return AEE_EBADPARM; + + dirfd = name[0] == '/' ? ctx->rootfd : ctx->adsp_library_dirfd; + if (dirfd < 0) { + fprintf(stderr, "Could not open %s: no search directory\n", name); + return AEE_EFAILED; + } + + fd = apps_std_open_mode(ctx, dirfd, name, mode); + if (fd < 0) { + fprintf(stderr, "Could not open %s (%s): %s\n", + name, mode, strerror(-fd)); + return AEE_EFAILED; + } + +#ifdef HEXAGONRPC_VERBOSE + printf("open(%s, %s) -> %d\n", name, mode, fd); +#endif + + *out = fd; + + return 0; +} + +static uint32_t apps_std_fwrite(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const struct { + uint32_t fd; + uint32_t buf_size; + } *first_in = inbufs[0].p; + struct { + uint32_t written; + uint32_t is_eof; + } *first_out = outbufs[0].p; + ssize_t ret; + + ret = hexagonfs_write(ctx->fds, first_in->fd, inbufs[1].s, inbufs[1].p); + if (ret < 0) { + fprintf(stderr, "Could not write file: %s\n", strerror(-ret)); + return AEE_EFAILED; + } + +#ifdef HEXAGONRPC_VERBOSE + printf("write(%u, %u) -> %ld\n", first_in->fd, first_in->buf_size, ret); +#endif + + first_out->written = ret; + first_out->is_eof = 0; + + return 0; +} + +/* + * Writes are synchronous here and land in the page cache; nothing to wait + * for from the DSP's point of view. + */ +static uint32_t apps_std_fsync(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ +#ifdef HEXAGONRPC_VERBOSE + const uint32_t *fd = inbufs[0].p; + + printf("ignore fsync(%u)\n", *fd); +#endif + + return 0; +} + +static uint32_t apps_std_fremove(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const char *name = inbufs[1].p; + int ret; + + // The name must be NULL-terminated + if (name[inbufs[1].s - 1] != 0) + return AEE_EBADPARM; + + ret = hexagonfs_unlink(ctx->fds, ctx->rootfd, ctx->rootfd, name); + +#ifdef HEXAGONRPC_VERBOSE + printf("remove(%s) -> %d\n", name, ret); +#endif + + if (ret) { + fprintf(stderr, "Could not remove %s: %s\n", name, strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + +/* Extended method (id > 30): the first word of the primary buffer is the method id. */ +static uint32_t apps_std_ftrunc(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const uint32_t *words = inbufs[0].p; + int ret; + + ret = hexagonfs_ftruncate(ctx->fds, words[1], words[2]); + +#ifdef HEXAGONRPC_VERBOSE + printf("ftruncate(%u, %u) -> %d\n", words[1], words[2], ret); +#endif + + if (ret) { + fprintf(stderr, "Could not truncate: %s\n", strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + +static uint32_t apps_std_frename(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const char *oldname = inbufs[1].p; + const char *newname = inbufs[2].p; + int ret; + + // Both names must be NULL-terminated + if (oldname[inbufs[1].s - 1] != 0 || newname[inbufs[2].s - 1] != 0) + return AEE_EBADPARM; + + ret = hexagonfs_rename(ctx->fds, ctx->rootfd, ctx->rootfd, oldname, newname); + +#ifdef HEXAGONRPC_VERBOSE + printf("rename(%s, %s) -> %d\n", oldname, newname, ret); +#endif + + if (ret) { + fprintf(stderr, "Could not rename %s: %s\n", oldname, strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + static uint32_t apps_std_fopen_with_env(void *data, const struct fastrpc_io_buffer *inbufs, struct fastrpc_io_buffer *outbufs) { struct apps_std_ctx *ctx = data; uint32_t *out = outbufs[0].p; - char rw_mode; int dirfd, fd; // The name and environment variable must also be NULL-terminated @@ -165,17 +373,14 @@ static uint32_t apps_std_fopen_with_env(void *data, || ((const char *) inbufs[4].p)[inbufs[4].s - 1] != 0) return AEE_EBADPARM; - rw_mode = ((const char *) inbufs[4].p)[0]; - if (rw_mode == 'w' || rw_mode == 'a') { - fprintf(stderr, "Tried to open %s for writing\n", - (const char *) inbufs[3].p); - return AEE_EUNSUPPORTED; - } - if (!strcmp(inbufs[1].p, "ADSP_LIBRARY_PATH")) { dirfd = ctx->adsp_library_dirfd; } else if (!strcmp(inbufs[1].p, "ADSP_AVS_CFG_PATH")) { dirfd = ctx->adsp_avs_cfg_dirfd; + } else if (((const char *) inbufs[3].p)[0] == '/') { + /* An absolute name needs no search directory (the sensor + * framework passes its own variable names with them). */ + dirfd = ctx->rootfd; } else { fprintf(stderr, "Unknown search directory %s\n", (const char *) inbufs[1].p); @@ -188,18 +393,19 @@ static uint32_t apps_std_fopen_with_env(void *data, return AEE_EFAILED; } - fd = hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, inbufs[3].p); + fd = apps_std_open_mode(ctx, dirfd, inbufs[3].p, inbufs[4].p); if (fd < 0) { - fprintf(stderr, "Could not open %s: %s\n", + fprintf(stderr, "Could not open %s (%s): %s\n", (const char *) inbufs[3].p, - strerror(errno)); + (const char *) inbufs[4].p, + strerror(-fd)); return AEE_EFAILED; } #ifdef HEXAGONRPC_VERBOSE - printf("openat($%s, %s, %c) -> %d\n", (const char *) inbufs[1].p, + printf("openat($%s, %s, %s) -> %d\n", (const char *) inbufs[1].p, (const char *) inbufs[3].p, - rw_mode, + (const char *) inbufs[4].p, fd); #endif @@ -396,79 +602,43 @@ void fastrpc_apps_std_deinit(struct fastrpc_interface *iface) struct apps_std_ctx *ctx = iface->data; int i; - for (i = 0; i < HEXAGONFS_MAX_FD; i++) { - if (ctx->fds[i] != NULL) + /* + * Destroying a descriptor walks its ->up chain, so close descendants + * before the root rather than ascending from it. + */ + for (i = HEXAGONFS_MAX_FD - 1; i >= 0; i--) { + if (i != ctx->rootfd && ctx->fds[i] != NULL) hexagonfs_close(ctx->fds, i); } + if (ctx->rootfd >= 0 && ctx->rootfd < HEXAGONFS_MAX_FD + && ctx->fds[ctx->rootfd] != NULL) + hexagonfs_close(ctx->fds, ctx->rootfd); + free(iface->data); free(iface); } static const struct fastrpc_function_impl apps_std_procs[] = { - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fflush_def, - .impl = apps_std_fflush, - }, - { - .def = &apps_std_fclose_def, - .impl = apps_std_fclose, - }, - { - .def = &apps_std_fread_def, - .impl = apps_std_fread, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fseek_def, - .impl = apps_std_fseek, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fopen_with_env_def, - .impl = apps_std_fopen_with_env, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_opendir_def, - .impl = apps_std_opendir, - }, - { - .def = &apps_std_closedir_def, - .impl = apps_std_closedir, - }, - { - .def = &apps_std_readdir_def, - .impl = apps_std_readdir, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_stat_def, - .impl = apps_std_stat, - }, + [0] = { .def = &apps_std_fopen_def, .impl = apps_std_fopen, }, + [2] = { .def = &apps_std_fflush_def, .impl = apps_std_fflush, }, + [3] = { .def = &apps_std_fclose_def, .impl = apps_std_fclose, }, + [4] = { .def = &apps_std_fread_def, .impl = apps_std_fread, }, + [5] = { .def = &apps_std_fwrite_def, .impl = apps_std_fwrite, }, + [9] = { .def = &apps_std_fseek_def, .impl = apps_std_fseek, }, + [19] = { .def = &apps_std_fopen_with_env_def, .impl = apps_std_fopen_with_env, }, + [23] = { .def = &apps_std_fsync_def, .impl = apps_std_fsync, }, + [24] = { .def = &apps_std_fremove_def, .impl = apps_std_fremove, }, + [26] = { .def = &apps_std_opendir_def, .impl = apps_std_opendir, }, + [27] = { .def = &apps_std_closedir_def, .impl = apps_std_closedir, }, + [28] = { .def = &apps_std_readdir_def, .impl = apps_std_readdir, }, + [31] = { .def = &apps_std_stat_def, .impl = apps_std_stat, }, + [32] = { .def = &apps_std_ftrunc_def, .impl = apps_std_ftrunc, }, + [33] = { .def = &apps_std_frename_def, .impl = apps_std_frename, }, }; const struct fastrpc_interface apps_std_interface = { .name = "apps_std", - .n_procs = 32, + .n_procs = 34, .procs = apps_std_procs, }; diff --git a/hexagonrpcd/hexagonfs.c b/hexagonrpcd/hexagonfs.c index bd9f353..603a40a 100644 --- a/hexagonrpcd/hexagonfs.c +++ b/hexagonrpcd/hexagonfs.c @@ -194,6 +194,7 @@ int hexagonfs_openat(struct hexagonfs_fd **fds, int rootfd, int dirfd, const cha int hexagonfs_close(struct hexagonfs_fd **fds, int fileno) { struct hexagonfs_fd *fd; + size_t i; if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) return -EBADF; @@ -202,11 +203,20 @@ int hexagonfs_close(struct hexagonfs_fd **fds, int fileno) if (fd == NULL || fd->ops == NULL) return -EBADF; + fds[fileno] = NULL; + + /* + * ".", ".." and the search directory itself can be handed out under a + * second file number: only the last reference may destroy it. + */ + for (i = 0; i < HEXAGONFS_MAX_FD; i++) { + if (fds[i] == fd) + return 0; + } + fd->is_assigned = false; destroy_file_descriptor(fd); - fds[fileno] = NULL; - return 0; } @@ -277,3 +287,185 @@ int hexagonfs_fstat(struct hexagonfs_fd **fds, int fileno, struct stat *stats) return fd->ops->stat(fd, stats); } + +/* + * Resolve the directory part of NAME, setting *BASE to the final component. + * *OWNED is true when the directory was opened here and the caller closes it. + */ +static int resolve_parent(struct hexagonfs_fd **fds, int rootfd, int dirfd, + const char *name, const char **base, bool *owned) +{ + const char *slash = strrchr(name, '/'); + char *dir; + int fileno; + + *owned = false; + + if (slash == NULL) { + *base = name; + return dirfd; + } + + *base = slash + 1; + + if (slash == name) + return rootfd; + + dir = strndup(name, slash - name); + if (dir == NULL) + return -ENOMEM; + + fileno = hexagonfs_openat(fds, rootfd, dirfd, dir); + free(dir); + if (fileno < 0) + return fileno; + + *owned = true; + + return fileno; +} + +int hexagonfs_create(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, int flags) +{ + struct hexagonfs_fd *dir, *fd; + const char *base; + bool owned; + int parent, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + dir = fds[parent]; + if (dir == NULL) { + ret = -EBADF; + goto out; + } + + if (*base == '\0') { + ret = -EISDIR; + goto out; + } + + if (dir->ops->create == NULL) { + ret = -ENOSYS; + goto out; + } + + ret = dir->ops->create(dir, base, flags, &fd); + if (ret) + goto out; + + /* The file stands on its own: its directory may be closed before it. */ + fd->up = NULL; + + ret = allocate_file_number(fds, fd); + if (ret < 0) + destroy_file_descriptor(fd); + +out: + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} + +ssize_t hexagonfs_write(struct hexagonfs_fd **fds, int fileno, size_t size, const void *ptr) +{ + struct hexagonfs_fd *fd; + + if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) + return -EBADF; + + fd = fds[fileno]; + if (fd == NULL) + return -EBADF; + + if (fd->ops->write == NULL) + return -ENOSYS; + + return fd->ops->write(fd, size, ptr); +} + +int hexagonfs_ftruncate(struct hexagonfs_fd **fds, int fileno, off_t len) +{ + struct hexagonfs_fd *fd; + + if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) + return -EBADF; + + fd = fds[fileno]; + if (fd == NULL) + return -EBADF; + + if (fd->ops->truncate == NULL) + return -ENOSYS; + + return fd->ops->truncate(fd, len); +} + +int hexagonfs_unlink(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name) +{ + struct hexagonfs_fd *dir; + const char *base; + bool owned; + int parent, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + dir = fds[parent]; + if (dir == NULL) { + ret = -EBADF; + } else if (*base == '\0') { + ret = -EISDIR; + } else if (dir->ops->unlink == NULL) { + ret = -ENOSYS; + } else { + ret = dir->ops->unlink(dir, base); + } + + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} + +int hexagonfs_rename(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, const char *newname) +{ + struct hexagonfs_fd *dir, *newdir; + const char *base, *newbase; + bool owned, newowned = false; + int parent, newparent = -1, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + newparent = resolve_parent(fds, rootfd, dirfd, newname, &newbase, &newowned); + if (newparent < 0) { + ret = newparent; + goto out; + } + + dir = fds[parent]; + newdir = fds[newparent]; + if (dir == NULL || newdir == NULL) { + ret = -EBADF; + } else if (*base == '\0' || *newbase == '\0') { + ret = -EISDIR; + } else if (dir->ops->rename == NULL) { + ret = -ENOSYS; + } else { + ret = dir->ops->rename(dir, base, newdir, newbase); + } + +out: + if (newowned) + hexagonfs_close(fds, newparent); + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} diff --git a/hexagonrpcd/hexagonfs.h b/hexagonrpcd/hexagonfs.h index 7682604..e24b4b5 100644 --- a/hexagonrpcd/hexagonfs.h +++ b/hexagonrpcd/hexagonfs.h @@ -42,6 +42,16 @@ struct hexagonfs_file_ops { ssize_t (*read)(struct hexagonfs_fd *fd, size_t size, void *ptr); int (*stat)(struct hexagonfs_fd *fd, struct stat *stats); int (*seek)(struct hexagonfs_fd *fd, off_t off, int whence); + /* Writable backends only; NULL here is reported as ENOSYS. */ + int (*create)(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out); + ssize_t (*write)(struct hexagonfs_fd *fd, size_t size, const void *ptr); + int (*truncate)(struct hexagonfs_fd *fd, off_t len); + int (*unlink)(struct hexagonfs_fd *dir, const char *name); + int (*rename)(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname); }; struct hexagonfs_dirent { @@ -77,5 +87,10 @@ int hexagonfs_fstat(struct hexagonfs_fd **fds, int fileno, struct stat *stats); int hexagonfs_lseek(struct hexagonfs_fd **fds, int fileno, off_t pos, int whence); int hexagonfs_readdir(struct hexagonfs_fd **fds, int fileno, size_t size, char *name); ssize_t hexagonfs_read(struct hexagonfs_fd **fds, int fileno, size_t size, void *ptr); +int hexagonfs_create(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, int flags); +ssize_t hexagonfs_write(struct hexagonfs_fd **fds, int fileno, size_t size, const void *ptr); +int hexagonfs_ftruncate(struct hexagonfs_fd **fds, int fileno, off_t len); +int hexagonfs_unlink(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name); +int hexagonfs_rename(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, const char *newname); #endif diff --git a/hexagonrpcd/hexagonfs_mapped.c b/hexagonrpcd/hexagonfs_mapped.c index fd28c8c..400800f 100644 --- a/hexagonrpcd/hexagonfs_mapped.c +++ b/hexagonrpcd/hexagonfs_mapped.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,134 @@ static int mapped_openat(struct hexagonfs_fd *dir, return ret; } +/* The physical directory behind a mapped file descriptor, or NULL for any other backend. */ +static struct mapped_ctx *mapped_ctx_of(struct hexagonfs_fd *fd) +{ + if (fd->ops != &hexagonfs_mapped_ops + && fd->ops != &hexagonfs_mapped_or_empty_ops + && fd->ops != &hexagonfs_mapped_sysfs_ops) + return NULL; + + return fd->data; +} + +static int mapped_create(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + struct hexagonfs_fd *fd; + struct mapped_ctx *ctx; + int ret; + + if (dir_ctx == NULL) + return -ENOSYS; + + ctx = malloc(sizeof(struct mapped_ctx)); + if (ctx == NULL) + return -ENOMEM; + + fd = malloc(sizeof(struct hexagonfs_fd)); + if (fd == NULL) { + ret = -ENOMEM; + goto err; + } + + ctx->fd = openat(dir_ctx->fd, name, + O_RDWR | O_CLOEXEC + | (flags & (O_CREAT | O_TRUNC | O_APPEND)), + 0644); + if (ctx->fd == -1) { + ret = -errno; + goto err_free_fd; + } + + ctx->dir = NULL; + + fd->is_assigned = false; + fd->up = dir; + fd->ops = &hexagonfs_mapped_ops; + fd->data = ctx; + + *out = fd; + + return 0; + +err_free_fd: + free(fd); +err: + free(ctx); + return ret; +} + +static ssize_t mapped_write(struct hexagonfs_fd *fd, size_t size, const void *in) +{ + struct mapped_ctx *ctx = fd->data; + const char *buf = in; + size_t done = 0; + ssize_t ret; + + /* + * A short write reported as success lets the DSP rename a truncated + * file into place. + */ + while (done < size) { + ret = write(ctx->fd, &buf[done], size - done); + if (ret < 0) { + if (errno == EINTR) + continue; + + return done ? (ssize_t) done : -errno; + } + + if (ret == 0) + break; + + done += ret; + } + + return done; +} + +static int mapped_truncate(struct hexagonfs_fd *fd, off_t len) +{ + struct mapped_ctx *ctx = fd->data; + + if (ftruncate(ctx->fd, len)) + return -errno; + + return 0; +} + +static int mapped_unlink(struct hexagonfs_fd *dir, const char *name) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + + if (dir_ctx == NULL) + return -ENOSYS; + + if (unlinkat(dir_ctx->fd, name, 0)) + return -errno; + + return 0; +} + +static int mapped_rename(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + struct mapped_ctx *newdir_ctx = mapped_ctx_of(newdir); + + if (dir_ctx == NULL || newdir_ctx == NULL) + return -ENOSYS; + + if (renameat(dir_ctx->fd, name, newdir_ctx->fd, newname)) + return -errno; + + return 0; +} + static ssize_t mapped_read(struct hexagonfs_fd *fd, size_t size, void *out) { struct mapped_ctx *ctx = fd->data; @@ -206,6 +335,13 @@ static int mapped_stat(struct hexagonfs_fd *fd, struct stat *stats) stats->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; } + /* + * Claiming a write bit the backing file lacks makes the DSP take a + * write path that can only fail. + */ + if (phys.st_mode & S_IWUSR) + stats->st_mode |= S_IWUSR; + stats->st_atim.tv_sec = phys.st_atim.tv_sec; stats->st_atim.tv_nsec = phys.st_atim.tv_nsec; stats->st_ctim.tv_sec = phys.st_ctim.tv_sec; @@ -264,6 +400,51 @@ static int mapped_or_empty_readdir(struct hexagonfs_fd *fd, } } +static int mapped_or_empty_create(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out) +{ + if (dir->data) + return mapped_create(dir, name, flags, out); + else + return -ENOENT; +} + +static int mapped_or_empty_unlink(struct hexagonfs_fd *dir, const char *name) +{ + if (dir->data) + return mapped_unlink(dir, name); + else + return -ENOENT; +} + +static int mapped_or_empty_rename(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname) +{ + if (dir->data) + return mapped_rename(dir, name, newdir, newname); + else + return -ENOENT; +} + +static ssize_t mapped_or_empty_write(struct hexagonfs_fd *fd, size_t size, + const void *in) +{ + if (fd->data) + return mapped_write(fd, size, in); + else + return -ENOENT; +} + +static int mapped_or_empty_truncate(struct hexagonfs_fd *fd, off_t len) +{ + if (fd->data) + return mapped_truncate(fd, len); + else + return -ENOENT; +} + static int mapped_or_empty_seek(struct hexagonfs_fd *fd, off_t off, int whence) { if (fd->data) @@ -330,6 +511,11 @@ struct hexagonfs_file_ops hexagonfs_mapped_ops = { .readdir = mapped_readdir, .seek = mapped_seek, .stat = mapped_stat, + .create = mapped_create, + .write = mapped_write, + .truncate = mapped_truncate, + .unlink = mapped_unlink, + .rename = mapped_rename, }; struct hexagonfs_file_ops hexagonfs_mapped_or_empty_ops = { @@ -340,6 +526,11 @@ struct hexagonfs_file_ops hexagonfs_mapped_or_empty_ops = { .readdir = mapped_or_empty_readdir, .seek = mapped_or_empty_seek, .stat = mapped_or_empty_stat, + .create = mapped_or_empty_create, + .write = mapped_or_empty_write, + .truncate = mapped_or_empty_truncate, + .unlink = mapped_or_empty_unlink, + .rename = mapped_or_empty_rename, }; struct hexagonfs_file_ops hexagonfs_mapped_sysfs_ops = { diff --git a/hexagonrpcd/interface/adsp_listener.c b/hexagonrpcd/interface/adsp_listener.c index d2197df..507efdc 100644 --- a/hexagonrpcd/interface/adsp_listener.c +++ b/hexagonrpcd/interface/adsp_listener.c @@ -48,3 +48,19 @@ const struct hrpc_method_def_interp4 adsp_listener_next2_def = { .n_inner_types = 0, .inner_types = NULL, }; + +/* get_in_bufs2(ctx, offset, rout bufs, rout bufsLenReq), quic/fastrpc inc/adsp_listener.h */ +struct hrpc_arg_def_interp4 adsp_listener_get_in_bufs2_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 adsp_listener_get_in_bufs2_def = { + .msg_id = 5, + .n_args = HRPC_ARRAY_SIZE(adsp_listener_get_in_bufs2_args), + .args = adsp_listener_get_in_bufs2_args, + .n_inner_types = 0, + .inner_types = NULL, +}; diff --git a/hexagonrpcd/interface/adsp_listener.h b/hexagonrpcd/interface/adsp_listener.h index 362d775..d467bb8 100644 --- a/hexagonrpcd/interface/adsp_listener.h +++ b/hexagonrpcd/interface/adsp_listener.h @@ -51,4 +51,22 @@ static inline int adsp_listener_next2(int fd, inbufs_size, (void *) inbufs); } +/* + * Fetches the part of a request's input buffers that next2 could not + * inline: OFFSET bytes in, up to INBUFS_SIZE bytes into INBUFS + * (quic/fastrpc inc/adsp_listener.h: get_in_bufs2(ctx, offset, bufs, + * bufsLen, bufsLenReq)). + */ +extern struct hrpc_method_def_interp4 adsp_listener_get_in_bufs2_def; +static inline int adsp_listener_get_in_bufs2(int fd, + uint32_t rctx, + uint32_t offset, + uint32_t *inbufs_len, + uint32_t inbufs_size, char *inbufs) +{ + return hexagonrpc(&adsp_listener_get_in_bufs2_def, fd, ADSP_LISTENER, + rctx, offset, inbufs_len, + inbufs_size, (void *) inbufs); +} + #endif /* INTERFACE_ADSP_LISTENER_H */ diff --git a/hexagonrpcd/interface/apps_std.c b/hexagonrpcd/interface/apps_std.c index 67b3ace..a2b3910 100644 --- a/hexagonrpcd/interface/apps_std.c +++ b/hexagonrpcd/interface/apps_std.c @@ -22,6 +22,20 @@ #include #include +static struct hrpc_arg_def_interp4 apps_std_fopen_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fopen_def = { + .msg_id = 0, + .n_args = HRPC_ARRAY_SIZE(apps_std_fopen_args), + .args = apps_std_fopen_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_freopen_args[] = { { HRPC_ARG_WORD, sizeof(uint32_t) }, { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, @@ -75,6 +89,21 @@ const struct hrpc_method_def_interp4 apps_std_fread_def = { .inner_types = NULL, }; +static struct hrpc_arg_def_interp4 apps_std_fwrite_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fwrite_def = { + .msg_id = 5, + .n_args = HRPC_ARRAY_SIZE(apps_std_fwrite_args), + .args = apps_std_fwrite_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_fseek_args[] = { { HRPC_ARG_WORD, sizeof(uint32_t) }, { HRPC_ARG_WORD, sizeof(uint32_t) }, @@ -105,6 +134,30 @@ const struct hrpc_method_def_interp4 apps_std_fopen_with_env_def = { .inner_types = NULL, }; +static struct hrpc_arg_def_interp4 apps_std_fsync_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fsync_def = { + .msg_id = 23, + .n_args = HRPC_ARRAY_SIZE(apps_std_fsync_args), + .args = apps_std_fsync_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + +static struct hrpc_arg_def_interp4 apps_std_fremove_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fremove_def = { + .msg_id = 24, + .n_args = HRPC_ARRAY_SIZE(apps_std_fremove_args), + .args = apps_std_fremove_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_opendir_args[] = { { HRPC_ARG_BLOB_SEQ, sizeof(char) }, { HRPC_ARG_OUT_BLOB, sizeof(uint64_t) }, @@ -182,3 +235,29 @@ const struct hrpc_method_def_interp4 apps_std_stat_def = { .n_inner_types = 0, .inner_types = NULL, }; + +static struct hrpc_arg_def_interp4 apps_std_ftrunc_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_WORD, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_ftrunc_def = { + .msg_id = 32, + .n_args = HRPC_ARRAY_SIZE(apps_std_ftrunc_args), + .args = apps_std_ftrunc_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + +static struct hrpc_arg_def_interp4 apps_std_frename_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 apps_std_frename_def = { + .msg_id = 33, + .n_args = HRPC_ARRAY_SIZE(apps_std_frename_args), + .args = apps_std_frename_args, + .n_inner_types = 0, + .inner_types = NULL, +}; diff --git a/hexagonrpcd/interface/apps_std.h b/hexagonrpcd/interface/apps_std.h index b80a1ca..7f2ac06 100644 --- a/hexagonrpcd/interface/apps_std.h +++ b/hexagonrpcd/interface/apps_std.h @@ -24,16 +24,22 @@ #include +extern struct hrpc_method_def_interp4 apps_std_fopen_def; extern struct hrpc_method_def_interp4 apps_std_freopen_def; extern struct hrpc_method_def_interp4 apps_std_fflush_def; extern struct hrpc_method_def_interp4 apps_std_fclose_def; extern struct hrpc_method_def_interp4 apps_std_fread_def; +extern struct hrpc_method_def_interp4 apps_std_fwrite_def; extern struct hrpc_method_def_interp4 apps_std_fseek_def; extern struct hrpc_method_def_interp4 apps_std_fopen_with_env_def; +extern struct hrpc_method_def_interp4 apps_std_fsync_def; +extern struct hrpc_method_def_interp4 apps_std_fremove_def; extern struct hrpc_method_def_interp4 apps_std_opendir_def; extern struct hrpc_method_def_interp4 apps_std_closedir_def; extern struct hrpc_method_def_interp4 apps_std_readdir_def; extern struct hrpc_method_def_interp4 apps_std_mkdir_def; extern struct hrpc_method_def_interp4 apps_std_stat_def; +extern struct hrpc_method_def_interp4 apps_std_ftrunc_def; +extern struct hrpc_method_def_interp4 apps_std_frename_def; #endif /* INTERFACE_APPS_STD_H */ diff --git a/hexagonrpcd/iobuffer.c b/hexagonrpcd/iobuffer.c index 0c36df6..b4769b6 100644 --- a/hexagonrpcd/iobuffer.c +++ b/hexagonrpcd/iobuffer.c @@ -199,9 +199,15 @@ size_t outbufs_calculate_size(size_t n_outbufs, const struct fastrpc_io_buffer * for (i = 0; i < n_outbufs; i++) { size += 4; - if (size & 0x7) - size += 8 - (size & 0x7); - size += outbufs[i].s; + /* + * outbufs_encode pads only when there is a payload; padding + * an empty buffer would ship uninitialised bytes. + */ + if (outbufs[i].s) { + if (size & 0x7) + size += 8 - (size & 0x7); + size += outbufs[i].s; + } } return size; diff --git a/hexagonrpcd/listener.c b/hexagonrpcd/listener.c index 3414974..5b3ba6e 100644 --- a/hexagonrpcd/listener.c +++ b/hexagonrpcd/listener.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "interface/adsp_listener.h" #include "iobuffer.h" @@ -297,6 +299,13 @@ static struct fastrpc_io_buffer *alloc_outbufs4(const struct hrpc_method_def_int if (out == NULL) return NULL; + /* + * Extended methods carry their id in the first primitive word; step + * over it as count_sizes4 does. + */ + if (def->msg_id > 30) + prim_in = (const uint32_t *) prim_in + 1; + for (i = 0; i < def->n_args; i++) { if (def->args[i].t == HRPC_ARG_OUT_BLOB || def->args[i].t == HRPC_ARG_OUT_TYPE) { @@ -416,6 +425,54 @@ static struct fastrpc_io_buffer *alloc_outbufs4(const struct hrpc_method_def_int return NULL; } +/* + * adsp_listener_next2 inlines at most this many bytes and reports the full + * length; the rest is fetched with adsp_listener_get_in_bufs2, as Qualcomm's + * listener does. + */ +#define LISTENER_INLINE_INBUFS 256 +#define LISTENER_MAX_INBUFS (16 * 1024 * 1024) + +static int fetch_large_inbufs(int fd, uint32_t rctx, uint32_t inbufs_len, + const char *head, char **out) +{ + uint32_t rest = inbufs_len - LISTENER_INLINE_INBUFS; + uint32_t got = 0; + char *buf; + int ret; + + if (inbufs_len > LISTENER_MAX_INBUFS) { + fprintf(stderr, "Refusing %" PRIu32 " bytes of input buffers\n", + inbufs_len); + return -1; + } + + buf = malloc(inbufs_len); + if (buf == NULL) { + perror("Could not allocate large input buffers"); + return -1; + } + + memcpy(buf, head, LISTENER_INLINE_INBUFS); + + ret = adsp_listener_get_in_bufs2(fd, rctx, LISTENER_INLINE_INBUFS, + &got, rest, + buf + LISTENER_INLINE_INBUFS); + if (ret) { + if (ret == -1) + perror("Could not fetch large input buffers"); + else + fprintf(stderr, "Could not fetch large input buffers: %d\n", ret); + + free(buf); + return -1; + } + + *out = buf; + + return 0; +} + static int return_for_next_invoke(int fd, uint32_t result, uint32_t *rctx, @@ -425,8 +482,10 @@ static int return_for_next_invoke(int fd, struct fastrpc_io_buffer **decoded) { struct fastrpc_decoder_context *ctx; - char inbufs[256]; + char inbufs[LISTENER_INLINE_INBUFS]; char *outbufs = NULL; + char *large = NULL; + const char *src; uint32_t inbufs_len; uint32_t outbufs_len; int ret; @@ -447,7 +506,7 @@ static int return_for_next_invoke(int fd, *rctx, result, outbufs_len, outbufs, rctx, handle, sc, - &inbufs_len, 256, inbufs); + &inbufs_len, LISTENER_INLINE_INBUFS, inbufs); if (ret) { if (ret == -1) perror("Could not fetch next FastRPC message"); @@ -457,38 +516,69 @@ static int return_for_next_invoke(int fd, goto err_free_outbufs; } - if (inbufs_len > 256) { - fprintf(stderr, "Large (>256B) input buffers aren't implemented\n"); - ret = -1; - goto err_free_outbufs; + src = inbufs; + if (inbufs_len > LISTENER_INLINE_INBUFS) { + ret = fetch_large_inbufs(fd, *rctx, inbufs_len, inbufs, &large); + if (ret) + goto err_free_outbufs; + + src = large; } ctx = inbuf_decode_start(*sc); if (!ctx) { perror("Could not start decoding"); ret = -1; - goto err_free_outbufs; + goto err_free_large; } - ret = inbuf_decode(ctx, inbufs_len, inbufs); + ret = inbuf_decode(ctx, inbufs_len, src); if (ret) { perror("Could not decode"); - goto err_free_outbufs; + goto err_free_large; } if (!inbuf_decode_is_complete(ctx)) { fprintf(stderr, "Expected more input buffers\n"); ret = -1; - goto err_free_outbufs; + goto err_free_large; } *decoded = inbuf_decode_finish(ctx); +err_free_large: + free(large); err_free_outbufs: free(outbufs); return ret; } +/* + * A request this server cannot serve is answered with an error and empty + * output buffers, so that the DSP sees a failed call rather than a vanished + * listener: the sensor framework retries or continues on an error but aborts + * without the answer. + */ +static int decline_request(uint32_t sc, uint32_t error, uint32_t *result, + struct fastrpc_io_buffer **returned) +{ + uint8_t n_outbufs = REMOTE_SCALARS_OUTBUFS(sc); + + *result = error; + *returned = NULL; + + if (n_outbufs == 0) + return 0; + + *returned = calloc(n_outbufs, sizeof(**returned)); + if (*returned == NULL) { + perror("Could not allocate empty output buffers"); + return 1; + } + + return 0; +} + static int invoke_requested_procedure(size_t n_ifaces, struct fastrpc_interface **ifaces, uint32_t handle, @@ -508,51 +598,44 @@ static int invoke_requested_procedure(size_t n_ifaces, method = *(const uint32_t *) decoded[0].p; } else { fprintf(stderr, "Expected extended method ID, but got none\n"); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } if (sc & 0xff) { fprintf(stderr, "Handles are not supported, but got %u in, %u out\n", (sc & 0xf0) >> 4, sc & 0xf); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } if (handle >= n_ifaces) { fprintf(stderr, "Unsupported handle: %u\n", handle); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } if (method >= ifaces[handle]->n_procs) { fprintf(stderr, "Unsupported method: %u (%08x)\n", method, sc); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } impl = &ifaces[handle]->procs[method]; if (impl->def == NULL || impl->impl == NULL) { fprintf(stderr, "Unsupported method: %u (%08x)\n", method, sc); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } ret = count_sizes4(impl->def, REMOTE_SCALARS_INBUFS(sc), REMOTE_SCALARS_OUTBUFS(sc), decoded); if (ret) { fprintf(stderr, "Method invoked with less arguments than expected\n"); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } *returned = alloc_outbufs4(impl->def, decoded, REMOTE_SCALARS_OUTBUFS(sc)); if (*returned == NULL && impl->def->n_args > 0) { perror("Could not allocate output buffers"); - *result = AEE_ENOMEMORY; - return 1; + return decline_request(sc, AEE_ENOMEMORY, result, returned); } *result = impl->impl(ifaces[handle]->data, decoded, *returned); diff --git a/hexagonrpcd/rpcd_builder.c b/hexagonrpcd/rpcd_builder.c index d374958..58a4fad 100644 --- a/hexagonrpcd/rpcd_builder.c +++ b/hexagonrpcd/rpcd_builder.c @@ -109,6 +109,59 @@ static struct hexagonfs_dirent *hfs_map_or_empty(const char *name, const char *p * * TODO: Make this free()-able with reference counts */ +#define SENSORS_PERSIST "/sensors/persist/" +#define SENSORS_PERSIST_REG "/sensors/persist/registry" + +/* + * The framework rewrites entries below /persist/sensors/registry/registry and + * renames a temporary file into it, so that tree has to be physical. When + * DIR/sensors/persist/registry exists it backs the whole of + * /persist/sensors/registry; otherwise the layout is as before. + */ +static struct hexagonfs_dirent *build_persist_dir(const char *prefix, + const char *sns_reg) +{ + struct stat st; + char *sns_persist, *probe; + size_t n_prefix = strlen(prefix); + + sns_persist = malloc(n_prefix + strlen(SENSORS_PERSIST) + 1); + probe = malloc(n_prefix + strlen(SENSORS_PERSIST_REG) + 1); + + if (sns_persist != NULL && probe != NULL) { + strcpy(sns_persist, prefix); + strcat(sns_persist, SENSORS_PERSIST); + strcpy(probe, prefix); + strcat(probe, SENSORS_PERSIST_REG); + + /* + * The registry itself has to be there, not just its parent: + * mapping a persist directory that does not contain one hides + * the packaged registry, and nothing can create it at runtime. + */ + if (stat(probe, &st) == 0 && S_ISDIR(st.st_mode)) { + free(probe); + + return hfs_mkdir("persist", 1, + hfs_mkdir("sensors", 1, + hfs_map("registry", sns_persist) + ) + ); + } + } + + free(probe); + free(sns_persist); + + return hfs_mkdir("persist", 1, + hfs_mkdir("sensors", 1, + hfs_mkdir("registry", 1, + hfs_map("registry", sns_reg) + ) + ) + ); +} + struct hexagonfs_dirent *construct_root_dir(const char *prefix, const char *dsp) { char *acdbdata, *dsp_libs, *sns_cfg, *sns_reg, *sns_reg_config, *socinfo; @@ -160,13 +213,7 @@ struct hexagonfs_dirent *construct_root_dir(const char *prefix, const char *dsp) * Some platforms need this in / and some need it in /mnt/vendor. Form * a hard link between both locations. */ - persist_dir = hfs_mkdir("persist", 1, - hfs_mkdir("sensors", 1, - hfs_mkdir("registry", 1, - hfs_map("registry", sns_reg) - ) - ) - ); + persist_dir = build_persist_dir(prefix, sns_reg); /* * Some platforms need vendor in / and some need it in /system. Form