/* * Copyright (C) 2020 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../module.h" #include #include #include #include #include #include #include #include #define err_return(err) do { last_error = err; return NULL; } while(0) static const struct ut_ops *ops; static int last_error = 0; static struct json_object * ut_fs_error(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *errmsg; if (last_error == 0) return NULL; errmsg = json_object_new_string(strerror(last_error)); last_error = 0; return errmsg; } static struct json_object * ut_fs_read_common(struct ut_state *s, uint32_t off, struct json_object *args, const char *type) { struct json_object *limit = json_object_array_get_idx(args, 0); struct json_object *rv = NULL; char buf[128], *p = NULL, *tmp; size_t rlen, len = 0; const char *lstr; int64_t lsize; FILE **fp = (FILE **)ops->get_type(s->ctx, type); if (!fp || !*fp) err_return(EBADF); if (json_object_is_type(limit, json_type_string)) { lstr = json_object_get_string(limit); if (!strcmp(lstr, "line")) { while (true) { if (!fgets(buf, sizeof(buf), *fp)) break; rlen = strlen(buf); tmp = realloc(p, len + rlen + 1); if (!tmp) { free(p); err_return(ENOMEM); } snprintf(tmp + len, rlen + 1, "%s", buf); p = tmp; len += rlen; if (rlen > 0 && buf[rlen - 1] == '\n') break; } } else if (!strcmp(lstr, "all")) { while (true) { rlen = fread(buf, 1, sizeof(buf), *fp); tmp = realloc(p, len + rlen); if (!tmp) { free(p); err_return(ENOMEM); } memcpy(tmp + len, buf, rlen); p = tmp; len += rlen; if (rlen == 0) break; } } else { return NULL; } } else if (json_object_is_type(limit, json_type_int)) { lsize = json_object_get_int64(limit); if (lsize <= 0) return NULL; p = calloc(1, lsize); if (!p) err_return(ENOMEM); len = fread(p, 1, lsize, *fp); if (ferror(*fp)) { free(p); err_return(errno); } } else { err_return(EINVAL); } rv = json_object_new_string_len(p, len); free(p); return rv; } static struct json_object * ut_fs_write_common(struct ut_state *s, uint32_t off, struct json_object *args, const char *type) { struct json_object *data = json_object_array_get_idx(args, 0); size_t len, wsize; const char *str; FILE **fp = (FILE **)ops->get_type(s->ctx, type); if (!fp || !*fp) err_return(EBADF); if (json_object_is_type(data, json_type_string)) { str = json_object_get_string(data); len = json_object_get_string_len(data); } else { str = json_object_to_json_string(data); len = str ? strlen(str) : 0; } wsize = fwrite(str, 1, len, *fp); if (wsize < len && ferror(*fp)) err_return(errno); return json_object_new_int64(wsize); } static struct json_object * ut_fs_pclose(struct ut_state *s, uint32_t off, struct json_object *args) { FILE **fp = (FILE **)ops->get_type(s->ctx, "fs.proc"); if (!fp || !*fp) err_return(EBADF); pclose(*fp); *fp = NULL; return json_object_new_boolean(true); } static struct json_object * ut_fs_pread(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_read_common(s, off, args, "fs.proc"); } static struct json_object * ut_fs_pwrite(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_write_common(s, off, args, "fs.proc"); } static struct json_object * ut_fs_popen(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *comm = json_object_array_get_idx(args, 0); struct json_object *mode = json_object_array_get_idx(args, 1); struct json_object *fo; FILE *fp; if (!json_object_is_type(comm, json_type_string)) err_return(EINVAL); fp = popen(json_object_get_string(comm), json_object_is_type(mode, json_type_string) ? json_object_get_string(mode) : "r"); if (!fp) err_return(errno); fo = json_object_new_object(); if (!fo) { pclose(fp); err_return(ENOMEM); } return ops->set_type(fo, "fs.proc", fp); } static struct json_object * ut_fs_close(struct ut_state *s, uint32_t off, struct json_object *args) { FILE **fp = (FILE **)ops->get_type(s->ctx, "fs.file"); if (!fp || !*fp) err_return(EBADF); fclose(*fp); *fp = NULL; return json_object_new_boolean(true); } static struct json_object * ut_fs_read(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_read_common(s, off, args, "fs.file"); } static struct json_object * ut_fs_write(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_write_common(s, off, args, "fs.file"); } static struct json_object * ut_fs_seek(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *ofs = json_object_array_get_idx(args, 0); struct json_object *how = json_object_array_get_idx(args, 1); int whence, res; long offset; FILE **fp = (FILE **)ops->get_type(s->ctx, "fs.file"); if (!fp || !*fp) err_return(EBADF); if (!ofs) offset = 0; else if (!json_object_is_type(ofs, json_type_int)) err_return(EINVAL); else offset = (long)json_object_get_int64(ofs); if (!how) whence = 0; else if (!json_object_is_type(how, json_type_int)) err_return(EINVAL); else whence = (int)json_object_get_int64(how); res = fseek(*fp, offset, whence); if (res < 0) err_return(errno); return json_object_new_boolean(true); } static struct json_object * ut_fs_tell(struct ut_state *s, uint32_t off, struct json_object *args) { long offset; FILE **fp = (FILE **)ops->get_type(s->ctx, "fs.file"); if (!fp || !*fp) err_return(EBADF); offset = ftell(*fp); if (offset < 0) err_return(errno); return json_object_new_int64(offset); } static struct json_object * ut_fs_open(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); struct json_object *mode = json_object_array_get_idx(args, 1); struct json_object *fo; FILE *fp; if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); fp = fopen(json_object_get_string(path), json_object_is_type(mode, json_type_string) ? json_object_get_string(mode) : "r"); if (!fp) err_return(errno); fo = json_object_new_object(); if (!fo) { fclose(fp); err_return(ENOMEM); } return ops->set_type(fo, "fs.file", fp); } static struct json_object * ut_fs_readdir(struct ut_state *s, uint32_t off, struct json_object *args) { DIR **dp = (DIR **)ops->get_type(s->ctx, "fs.dir"); struct dirent *e; if (!dp || !*dp) err_return(EINVAL); errno = 0; e = readdir(*dp); if (!e) err_return(errno); return json_object_new_string(e->d_name); } static struct json_object * ut_fs_telldir(struct ut_state *s, uint32_t off, struct json_object *args) { DIR **dp = (DIR **)ops->get_type(s->ctx, "fs.dir"); long position; if (!dp || !*dp) err_return(EBADF); position = telldir(*dp); if (position == -1) err_return(errno); return json_object_new_int64((int64_t)position); } static struct json_object * ut_fs_seekdir(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *ofs = json_object_array_get_idx(args, 0); DIR **dp = (DIR **)ops->get_type(s->ctx, "fs.dir"); long position; if (!json_object_is_type(ofs, json_type_int)) err_return(EINVAL); if (!dp || !*dp) err_return(EBADF); position = (long)json_object_get_int64(ofs); seekdir(*dp, position); return json_object_new_boolean(true); } static struct json_object * ut_fs_closedir(struct ut_state *s, uint32_t off, struct json_object *args) { DIR **dp = (DIR **)ops->get_type(s->ctx, "fs.dir"); if (!dp || !*dp) err_return(EBADF); closedir(*dp); *dp = NULL; return json_object_new_boolean(true); } static struct json_object * ut_fs_opendir(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); struct json_object *diro; DIR *dp; if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); dp = opendir(json_object_get_string(path)); if (!dp) err_return(errno); diro = json_object_new_object(); if (!diro) { closedir(dp); err_return(ENOMEM); } return ops->set_type(diro, "fs.dir", dp); } static struct json_object * ut_fs_readlink(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); struct json_object *res; ssize_t buflen = 0, rv; char *buf = NULL, *tmp; if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); do { buflen += 128; tmp = realloc(buf, buflen); if (!tmp) { free(buf); err_return(ENOMEM); } buf = tmp; rv = readlink(json_object_get_string(path), buf, buflen); if (rv == -1) { free(buf); err_return(errno); } if (rv < buflen) break; } while (true); res = json_object_new_string_len(buf, buflen); free(buf); return res; } static struct json_object * ut_fs_stat_common(struct ut_state *s, uint32_t off, struct json_object *args, bool use_lstat) { struct json_object *path = json_object_array_get_idx(args, 0); struct json_object *res, *o; struct stat st; int rv; if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); rv = (use_lstat ? lstat : stat)(json_object_get_string(path), &st); if (rv == -1) err_return(errno); res = json_object_new_object(); if (!res) err_return(ENOMEM); o = json_object_new_object(); if (o) { json_object_object_add(o, "major", json_object_new_int64(major(st.st_dev))); json_object_object_add(o, "minor", json_object_new_int64(minor(st.st_dev))); json_object_object_add(res, "dev", o); } o = json_object_new_object(); if (o) { json_object_object_add(o, "setuid", json_object_new_boolean(st.st_mode & S_ISUID)); json_object_object_add(o, "setgid", json_object_new_boolean(st.st_mode & S_ISGID)); json_object_object_add(o, "sticky", json_object_new_boolean(st.st_mode & S_ISVTX)); json_object_object_add(o, "user_read", json_object_new_boolean(st.st_mode & S_IRUSR)); json_object_object_add(o, "user_write", json_object_new_boolean(st.st_mode & S_IWUSR)); json_object_object_add(o, "user_exec", json_object_new_boolean(st.st_mode & S_IXUSR)); json_object_object_add(o, "group_read", json_object_new_boolean(st.st_mode & S_IRGRP)); json_object_object_add(o, "group_write", json_object_new_boolean(st.st_mode & S_IWGRP)); json_object_object_add(o, "group_exec", json_object_new_boolean(st.st_mode & S_IXGRP)); json_object_object_add(o, "other_read", json_object_new_boolean(st.st_mode & S_IROTH)); json_object_object_add(o, "other_write", json_object_new_boolean(st.st_mode & S_IWOTH)); json_object_object_add(o, "other_exec", json_object_new_boolean(st.st_mode & S_IXOTH)); json_object_object_add(res, "perm", o); } json_object_object_add(res, "inode", json_object_new_int64((int64_t)st.st_ino)); json_object_object_add(res, "mode", json_object_new_int64((int64_t)st.st_mode & ~S_IFMT)); json_object_object_add(res, "nlink", json_object_new_int64((int64_t)st.st_nlink)); json_object_object_add(res, "uid", json_object_new_int64((int64_t)st.st_uid)); json_object_object_add(res, "gid", json_object_new_int64((int64_t)st.st_gid)); json_object_object_add(res, "size", json_object_new_int64((int64_t)st.st_size)); json_object_object_add(res, "blksize", json_object_new_int64((int64_t)st.st_blksize)); json_object_object_add(res, "blocks", json_object_new_int64((int64_t)st.st_blocks)); json_object_object_add(res, "atime", json_object_new_int64((int64_t)st.st_atime)); json_object_object_add(res, "mtime", json_object_new_int64((int64_t)st.st_mtime)); json_object_object_add(res, "ctime", json_object_new_int64((int64_t)st.st_ctime)); if (S_ISREG(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("file")); else if (S_ISDIR(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("directory")); else if (S_ISCHR(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("char")); else if (S_ISBLK(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("block")); else if (S_ISFIFO(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("fifo")); else if (S_ISLNK(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("link")); else if (S_ISSOCK(st.st_mode)) json_object_object_add(res, "type", json_object_new_string("socket")); else json_object_object_add(res, "type", json_object_new_string("unknown")); return res; } static struct json_object * ut_fs_stat(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_stat_common(s, off, args, false); } static struct json_object * ut_fs_lstat(struct ut_state *s, uint32_t off, struct json_object *args) { return ut_fs_stat_common(s, off, args, true); } static struct json_object * ut_fs_mkdir(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); struct json_object *mode = json_object_array_get_idx(args, 1); if (!json_object_is_type(path, json_type_string) || (mode && !json_object_is_type(mode, json_type_int))) err_return(EINVAL); if (mkdir(json_object_get_string(path), (mode_t)(mode ? json_object_get_int64(mode) : 0777)) == -1) err_return(errno); return json_object_new_boolean(true); } static struct json_object * ut_fs_rmdir(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); if (rmdir(json_object_get_string(path)) == -1) err_return(errno); return json_object_new_boolean(true); } static struct json_object * ut_fs_symlink(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *dest = json_object_array_get_idx(args, 0); struct json_object *path = json_object_array_get_idx(args, 1); if (!json_object_is_type(dest, json_type_string) || !json_object_is_type(path, json_type_string)) err_return(EINVAL); if (symlink(json_object_get_string(dest), json_object_get_string(path)) == -1) err_return(errno); return json_object_new_boolean(true); } static struct json_object * ut_fs_unlink(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); if (unlink(json_object_get_string(path)) == -1) err_return(errno); return json_object_new_boolean(true); } static struct json_object * ut_fs_getcwd(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *res; char *buf = NULL, *tmp; size_t buflen = 0; do { buflen += 128; tmp = realloc(buf, buflen); if (!tmp) { free(buf); err_return(ENOMEM); } buf = tmp; if (getcwd(buf, buflen) != NULL) break; if (errno == ERANGE) continue; err_return(errno); } while (true); res = json_object_new_string(buf); free(buf); return res; } static struct json_object * ut_fs_chdir(struct ut_state *s, uint32_t off, struct json_object *args) { struct json_object *path = json_object_array_get_idx(args, 0); if (!json_object_is_type(path, json_type_string)) err_return(EINVAL); if (chdir(json_object_get_string(path)) == -1) err_return(errno); return json_object_new_boolean(true); } static const struct { const char *name; ut_c_fn *func; } proc_fns[] = { { "read", ut_fs_pread }, { "write", ut_fs_pwrite }, { "close", ut_fs_pclose }, }; static const struct { const char *name; ut_c_fn *func; } file_fns[] = { { "read", ut_fs_read }, { "write", ut_fs_write }, { "seek", ut_fs_seek }, { "tell", ut_fs_tell }, { "close", ut_fs_close }, }; static const struct { const char *name; ut_c_fn *func; } dir_fns[] = { { "read", ut_fs_readdir }, { "seek", ut_fs_seekdir }, { "tell", ut_fs_telldir }, { "close", ut_fs_closedir }, }; static const struct { const char *name; ut_c_fn *func; } global_fns[] = { { "error", ut_fs_error }, { "open", ut_fs_open }, { "opendir", ut_fs_opendir }, { "popen", ut_fs_popen }, { "readlink", ut_fs_readlink }, { "stat", ut_fs_stat }, { "lstat", ut_fs_lstat }, { "mkdir", ut_fs_mkdir }, { "rmdir", ut_fs_rmdir }, { "symlink", ut_fs_symlink }, { "unlink", ut_fs_unlink }, { "getcwd", ut_fs_getcwd }, { "chdir", ut_fs_chdir }, }; static void close_proc(void *ud) { pclose((FILE *)ud); } static void close_file(void *ud) { fclose((FILE *)ud); } static void close_dir(void *ud) { closedir((DIR *)ud); } void ut_module_init(const struct ut_ops *ut, struct ut_state *s, struct json_object *scope) { struct json_object *proc_proto, *file_proto, *dir_proto; ops = ut; proc_proto = ops->new_object(NULL); file_proto = ops->new_object(NULL); dir_proto = ops->new_object(NULL); register_functions(s, ops, global_fns, scope); register_functions(s, ops, proc_fns, proc_proto); register_functions(s, ops, file_fns, file_proto); register_functions(s, ops, dir_fns, dir_proto); ops->register_type("fs.proc", proc_proto, close_proc); ops->register_type("fs.file", file_proto, close_file); ops->register_type("fs.dir", dir_proto, close_dir); json_object_object_add(scope, "stdin", ops->set_type(xjs_new_object(), "fs.file", stdin)); json_object_object_add(scope, "stdout", ops->set_type(xjs_new_object(), "fs.file", stdout)); json_object_object_add(scope, "stderr", ops->set_type(xjs_new_object(), "fs.file", stderr)); }