/* * Copyright (C) 2020-2021 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 #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __APPLE__ #include /* major(), minor() */ #endif #include "ucode/module.h" #define err_return(err) do { last_error = err; return NULL; } while(0) //static const uc_ops *ops; static uc_resource_type_t *file_type, *proc_type, *dir_type; static int last_error = 0; static uc_value_t * uc_fs_error(uc_vm_t *vm, size_t nargs) { uc_value_t *errmsg; if (last_error == 0) return NULL; errmsg = ucv_string_new(strerror(last_error)); last_error = 0; return errmsg; } static uc_value_t * uc_fs_read_common(uc_vm_t *vm, size_t nargs, const char *type) { uc_value_t *limit = uc_fn_arg(0); uc_value_t *rv = NULL; char buf[128], *p = NULL, *tmp; size_t rlen, len = 0; const char *lstr; int64_t lsize; ssize_t llen; FILE **fp = uc_fn_this(type); if (!fp || !*fp) err_return(EBADF); if (ucv_type(limit) == UC_STRING) { lstr = ucv_string_get(limit); llen = ucv_string_length(limit); if (llen == 4 && !strcmp(lstr, "line")) { llen = getline(&p, &rlen, *fp); if (llen == -1) err_return(errno); len = (size_t)llen; } else if (llen == 3 && !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 if (llen == 1) { llen = getdelim(&p, &rlen, *lstr, *fp); if (llen == -1) err_return(errno); len = (size_t)llen; } else { return NULL; } } else if (ucv_type(limit) == UC_INTEGER) { lsize = ucv_int64_get(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 = ucv_string_new_length(p, len); free(p); return rv; } static uc_value_t * uc_fs_write_common(uc_vm_t *vm, size_t nargs, const char *type) { uc_value_t *data = uc_fn_arg(0); size_t len, wsize; char *str; FILE **fp = uc_fn_this(type); if (!fp || !*fp) err_return(EBADF); if (ucv_type(data) == UC_STRING) { len = ucv_string_length(data); wsize = fwrite(ucv_string_get(data), 1, len, *fp); } else { str = ucv_to_jsonstring(vm, data); len = str ? strlen(str) : 0; wsize = fwrite(str, 1, len, *fp); free(str); } if (wsize < len && ferror(*fp)) err_return(errno); return ucv_int64_new(wsize); } static uc_value_t * uc_fs_flush_common(uc_vm_t *vm, size_t nargs, const char *type) { FILE **fp = uc_fn_this(type); if (!fp || !*fp) err_return(EBADF); if (fflush(*fp) != EOF) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type) { int fd; FILE **fp = uc_fn_this(type); if (!fp || !*fp) err_return(EBADF); fd = fileno(*fp); if (fd == -1) err_return(errno); return ucv_int64_new(fd); } static uc_value_t * uc_fs_pclose(uc_vm_t *vm, size_t nargs) { FILE **fp = uc_fn_this("fs.proc"); int rc; if (!fp || !*fp) err_return(EBADF); rc = pclose(*fp); *fp = NULL; if (rc == -1) err_return(errno); if (WIFEXITED(rc)) return ucv_int64_new(WEXITSTATUS(rc)); if (WIFSIGNALED(rc)) return ucv_int64_new(-WTERMSIG(rc)); return ucv_int64_new(0); } static uc_value_t * uc_fs_pread(uc_vm_t *vm, size_t nargs) { return uc_fs_read_common(vm, nargs, "fs.proc"); } static uc_value_t * uc_fs_pwrite(uc_vm_t *vm, size_t nargs) { return uc_fs_write_common(vm, nargs, "fs.proc"); } static uc_value_t * uc_fs_pflush(uc_vm_t *vm, size_t nargs) { return uc_fs_flush_common(vm, nargs, "fs.proc"); } static uc_value_t * uc_fs_pfileno(uc_vm_t *vm, size_t nargs) { return uc_fs_fileno_common(vm, nargs, "fs.proc"); } static uc_value_t * uc_fs_popen(uc_vm_t *vm, size_t nargs) { uc_value_t *comm = uc_fn_arg(0); uc_value_t *mode = uc_fn_arg(1); FILE *fp; if (ucv_type(comm) != UC_STRING) err_return(EINVAL); fp = popen(ucv_string_get(comm), ucv_type(mode) == UC_STRING ? ucv_string_get(mode) : "r"); if (!fp) err_return(errno); return uc_resource_new(proc_type, fp); } static uc_value_t * uc_fs_close(uc_vm_t *vm, size_t nargs) { FILE **fp = uc_fn_this("fs.file"); if (!fp || !*fp) err_return(EBADF); fclose(*fp); *fp = NULL; return ucv_boolean_new(true); } static uc_value_t * uc_fs_read(uc_vm_t *vm, size_t nargs) { return uc_fs_read_common(vm, nargs, "fs.file"); } static uc_value_t * uc_fs_write(uc_vm_t *vm, size_t nargs) { return uc_fs_write_common(vm, nargs, "fs.file"); } static uc_value_t * uc_fs_seek(uc_vm_t *vm, size_t nargs) { uc_value_t *ofs = uc_fn_arg(0); uc_value_t *how = uc_fn_arg(1); int whence, res; long offset; FILE **fp = uc_fn_this("fs.file"); if (!fp || !*fp) err_return(EBADF); if (!ofs) offset = 0; else if (ucv_type(ofs) != UC_INTEGER) err_return(EINVAL); else offset = (long)ucv_int64_get(ofs); if (!how) whence = 0; else if (ucv_type(how) != UC_INTEGER) err_return(EINVAL); else whence = (int)ucv_int64_get(how); res = fseek(*fp, offset, whence); if (res < 0) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_tell(uc_vm_t *vm, size_t nargs) { long offset; FILE **fp = uc_fn_this("fs.file"); if (!fp || !*fp) err_return(EBADF); offset = ftell(*fp); if (offset < 0) err_return(errno); return ucv_int64_new(offset); } static uc_value_t * uc_fs_isatty(uc_vm_t *vm, size_t nargs) { FILE **fp = uc_fn_this("fs.file"); int fd; if (!fp || !*fp) err_return(EBADF); fd = fileno(*fp); if (fd == -1) err_return(errno); return ucv_boolean_new(isatty(fd)); } static uc_value_t * uc_fs_flush(uc_vm_t *vm, size_t nargs) { return uc_fs_flush_common(vm, nargs, "fs.file"); } static uc_value_t * uc_fs_fileno(uc_vm_t *vm, size_t nargs) { return uc_fs_fileno_common(vm, nargs, "fs.file"); } static uc_value_t * uc_fs_open(uc_vm_t *vm, size_t nargs) { int open_mode, open_flags, fd, i; uc_value_t *path = uc_fn_arg(0); uc_value_t *mode = uc_fn_arg(1); uc_value_t *perm = uc_fn_arg(2); mode_t open_perm = 0666; FILE *fp; char *m; if (ucv_type(path) != UC_STRING) err_return(EINVAL); m = (ucv_type(mode) == UC_STRING) ? ucv_string_get(mode) : "r"; switch (*m) { case 'r': open_mode = O_RDONLY; open_flags = 0; break; case 'w': open_mode = O_WRONLY; open_flags = O_CREAT | O_TRUNC; break; case 'a': open_mode = O_WRONLY; open_flags = O_CREAT | O_APPEND; break; default: err_return(EINVAL); } for (i = 1; m[i]; i++) { switch (m[i]) { case '+': open_mode = O_RDWR; break; case 'x': open_flags |= O_EXCL; break; case 'e': open_flags |= O_CLOEXEC; break; } } if (perm) { if (ucv_type(perm) != UC_INTEGER) err_return(EINVAL); open_perm = ucv_int64_get(perm); } #ifdef O_LARGEFILE open_flags |= open_mode | O_LARGEFILE; #else open_flags |= open_mode; #endif fd = open(ucv_string_get(path), open_flags, open_perm); if (fd < 0) return NULL; fp = fdopen(fd, m); if (!fp) { i = errno; close(fd); err_return(i); } return uc_resource_new(file_type, fp); } static uc_value_t * uc_fs_fdopen(uc_vm_t *vm, size_t nargs) { uc_value_t *fdno = uc_fn_arg(0); uc_value_t *mode = uc_fn_arg(1); int64_t n; FILE *fp; if (ucv_type(fdno) != UC_INTEGER) err_return(EINVAL); n = ucv_int64_get(fdno); if (n < 0 || n > INT_MAX) err_return(EBADF); fp = fdopen((int)n, ucv_type(mode) == UC_STRING ? ucv_string_get(mode) : "r"); if (!fp) err_return(errno); return uc_resource_new(file_type, fp); } static uc_value_t * uc_fs_readdir(uc_vm_t *vm, size_t nargs) { DIR **dp = uc_fn_this("fs.dir"); struct dirent *e; if (!dp || !*dp) err_return(EINVAL); errno = 0; e = readdir(*dp); if (!e) err_return(errno); return ucv_string_new(e->d_name); } static uc_value_t * uc_fs_telldir(uc_vm_t *vm, size_t nargs) { DIR **dp = uc_fn_this("fs.dir"); long position; if (!dp || !*dp) err_return(EBADF); position = telldir(*dp); if (position == -1) err_return(errno); return ucv_int64_new((int64_t)position); } static uc_value_t * uc_fs_seekdir(uc_vm_t *vm, size_t nargs) { uc_value_t *ofs = uc_fn_arg(0); DIR **dp = uc_fn_this("fs.dir"); long position; if (ucv_type(ofs) != UC_INTEGER) err_return(EINVAL); if (!dp || !*dp) err_return(EBADF); position = (long)ucv_int64_get(ofs); seekdir(*dp, position); return ucv_boolean_new(true); } static uc_value_t * uc_fs_closedir(uc_vm_t *vm, size_t nargs) { DIR **dp = uc_fn_this("fs.dir"); if (!dp || !*dp) err_return(EBADF); closedir(*dp); *dp = NULL; return ucv_boolean_new(true); } static uc_value_t * uc_fs_opendir(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); DIR *dp; if (ucv_type(path) != UC_STRING) err_return(EINVAL); dp = opendir(ucv_string_get(path)); if (!dp) err_return(errno); return uc_resource_new(dir_type, dp); } static uc_value_t * uc_fs_readlink(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *res; ssize_t buflen = 0, rv; char *buf = NULL, *tmp; if (ucv_type(path) != UC_STRING) err_return(EINVAL); do { buflen += 128; tmp = realloc(buf, buflen); if (!tmp) { free(buf); err_return(ENOMEM); } buf = tmp; rv = readlink(ucv_string_get(path), buf, buflen); if (rv == -1) { free(buf); err_return(errno); } if (rv < buflen) break; } while (true); res = ucv_string_new_length(buf, rv); free(buf); return res; } static uc_value_t * uc_fs_stat_common(uc_vm_t *vm, size_t nargs, bool use_lstat) { uc_value_t *path = uc_fn_arg(0); uc_value_t *res, *o; struct stat st; int rv; if (ucv_type(path) != UC_STRING) err_return(EINVAL); rv = (use_lstat ? lstat : stat)(ucv_string_get(path), &st); if (rv == -1) err_return(errno); res = ucv_object_new(vm); if (!res) err_return(ENOMEM); o = ucv_object_new(vm); if (o) { ucv_object_add(o, "major", ucv_int64_new(major(st.st_dev))); ucv_object_add(o, "minor", ucv_int64_new(minor(st.st_dev))); ucv_object_add(res, "dev", o); } o = ucv_object_new(vm); if (o) { ucv_object_add(o, "setuid", ucv_boolean_new(st.st_mode & S_ISUID)); ucv_object_add(o, "setgid", ucv_boolean_new(st.st_mode & S_ISGID)); ucv_object_add(o, "sticky", ucv_boolean_new(st.st_mode & S_ISVTX)); ucv_object_add(o, "user_read", ucv_boolean_new(st.st_mode & S_IRUSR)); ucv_object_add(o, "user_write", ucv_boolean_new(st.st_mode & S_IWUSR)); ucv_object_add(o, "user_exec", ucv_boolean_new(st.st_mode & S_IXUSR)); ucv_object_add(o, "group_read", ucv_boolean_new(st.st_mode & S_IRGRP)); ucv_object_add(o, "group_write", ucv_boolean_new(st.st_mode & S_IWGRP)); ucv_object_add(o, "group_exec", ucv_boolean_new(st.st_mode & S_IXGRP)); ucv_object_add(o, "other_read", ucv_boolean_new(st.st_mode & S_IROTH)); ucv_object_add(o, "other_write", ucv_boolean_new(st.st_mode & S_IWOTH)); ucv_object_add(o, "other_exec", ucv_boolean_new(st.st_mode & S_IXOTH)); ucv_object_add(res, "perm", o); } ucv_object_add(res, "inode", ucv_int64_new((int64_t)st.st_ino)); ucv_object_add(res, "mode", ucv_int64_new((int64_t)st.st_mode & ~S_IFMT)); ucv_object_add(res, "nlink", ucv_int64_new((int64_t)st.st_nlink)); ucv_object_add(res, "uid", ucv_int64_new((int64_t)st.st_uid)); ucv_object_add(res, "gid", ucv_int64_new((int64_t)st.st_gid)); ucv_object_add(res, "size", ucv_int64_new((int64_t)st.st_size)); ucv_object_add(res, "blksize", ucv_int64_new((int64_t)st.st_blksize)); ucv_object_add(res, "blocks", ucv_int64_new((int64_t)st.st_blocks)); ucv_object_add(res, "atime", ucv_int64_new((int64_t)st.st_atime)); ucv_object_add(res, "mtime", ucv_int64_new((int64_t)st.st_mtime)); ucv_object_add(res, "ctime", ucv_int64_new((int64_t)st.st_ctime)); if (S_ISREG(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("file")); else if (S_ISDIR(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("directory")); else if (S_ISCHR(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("char")); else if (S_ISBLK(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("block")); else if (S_ISFIFO(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("fifo")); else if (S_ISLNK(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("link")); else if (S_ISSOCK(st.st_mode)) ucv_object_add(res, "type", ucv_string_new("socket")); else ucv_object_add(res, "type", ucv_string_new("unknown")); return res; } static uc_value_t * uc_fs_stat(uc_vm_t *vm, size_t nargs) { return uc_fs_stat_common(vm, nargs, false); } static uc_value_t * uc_fs_lstat(uc_vm_t *vm, size_t nargs) { return uc_fs_stat_common(vm, nargs, true); } static uc_value_t * uc_fs_mkdir(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *mode = uc_fn_arg(1); if (ucv_type(path) != UC_STRING || (mode && ucv_type(mode) != UC_INTEGER)) err_return(EINVAL); if (mkdir(ucv_string_get(path), (mode_t)(mode ? ucv_int64_get(mode) : 0777)) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_rmdir(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (rmdir(ucv_string_get(path)) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_symlink(uc_vm_t *vm, size_t nargs) { uc_value_t *dest = uc_fn_arg(0); uc_value_t *path = uc_fn_arg(1); if (ucv_type(dest) != UC_STRING || ucv_type(path) != UC_STRING) err_return(EINVAL); if (symlink(ucv_string_get(dest), ucv_string_get(path)) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_unlink(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (unlink(ucv_string_get(path)) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_getcwd(uc_vm_t *vm, size_t nargs) { uc_value_t *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; free(buf); err_return(errno); } while (true); res = ucv_string_new(buf); free(buf); return res; } static uc_value_t * uc_fs_chdir(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (chdir(ucv_string_get(path)) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_chmod(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *mode = uc_fn_arg(1); if (ucv_type(path) != UC_STRING || ucv_type(mode) != UC_INTEGER) err_return(EINVAL); if (chmod(ucv_string_get(path), (mode_t)ucv_int64_get(mode)) == -1) err_return(errno); return ucv_boolean_new(true); } static bool uc_fs_resolve_user(uc_value_t *v, uid_t *uid) { struct passwd *pw = NULL; int64_t n; char *s; *uid = (uid_t)-1; switch (ucv_type(v)) { case UC_INTEGER: n = ucv_int64_get(v); if (n < -1) { errno = ERANGE; return false; } *uid = (uid_t)n; return true; case UC_STRING: s = ucv_string_get(v); pw = getpwnam(s); if (!pw) { errno = ENOENT; return false; } *uid = pw->pw_uid; return true; case UC_NULL: return true; default: errno = EINVAL; return false; } } static bool uc_fs_resolve_group(uc_value_t *v, gid_t *gid) { struct group *gr = NULL; int64_t n; char *s; *gid = (gid_t)-1; switch (ucv_type(v)) { case UC_INTEGER: n = ucv_int64_get(v); if (n < -1) { errno = ERANGE; return false; } *gid = (gid_t)n; return true; case UC_STRING: s = ucv_string_get(v); gr = getgrnam(s); if (!gr) { errno = ENOENT; return false; } *gid = gr->gr_gid; return true; case UC_NULL: return true; default: errno = EINVAL; return false; } } static uc_value_t * uc_fs_chown(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *user = uc_fn_arg(1); uc_value_t *group = uc_fn_arg(2); uid_t uid; gid_t gid; if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (!uc_fs_resolve_user(user, &uid) || !uc_fs_resolve_group(group, &gid)) err_return(errno); if (chown(ucv_string_get(path), uid, gid) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_rename(uc_vm_t *vm, size_t nargs) { uc_value_t *oldpath = uc_fn_arg(0); uc_value_t *newpath = uc_fn_arg(1); if (ucv_type(oldpath) != UC_STRING || ucv_type(newpath) != UC_STRING) err_return(EINVAL); if (rename(ucv_string_get(oldpath), ucv_string_get(newpath))) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_glob(uc_vm_t *vm, size_t nargs) { uc_value_t *pat, *arr; glob_t gl = { 0 }; size_t i; for (i = 0; i < nargs; i++) { pat = uc_fn_arg(i); if (ucv_type(pat) != UC_STRING) { globfree(&gl); err_return(EINVAL); } glob(ucv_string_get(pat), i ? GLOB_APPEND : 0, NULL, &gl); } arr = ucv_array_new(vm); for (i = 0; i < gl.gl_pathc; i++) ucv_array_push(arr, ucv_string_new(gl.gl_pathv[i])); globfree(&gl); return arr; } static uc_value_t * uc_fs_dirname(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); size_t i; char *s; if (ucv_type(path) != UC_STRING) err_return(EINVAL); i = ucv_string_length(path); s = ucv_string_get(path); if (i == 0) return ucv_string_new("."); for (i--; s[i] == '/'; i--) if (i == 0) return ucv_string_new("/"); for (; s[i] != '/'; i--) if (i == 0) return ucv_string_new("."); for (; s[i] == '/'; i--) if (i == 0) return ucv_string_new("/"); return ucv_string_new_length(s, i + 1); } static uc_value_t * uc_fs_basename(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); size_t i, len, skip; char *s; if (ucv_type(path) != UC_STRING) err_return(EINVAL); len = ucv_string_length(path); s = ucv_string_get(path); if (len == 0) return ucv_string_new("."); for (i = len - 1, skip = 0; i > 0 && s[i] == '/'; i--, skip++) ; for (; i > 0 && s[i - 1] != '/'; i--) ; return ucv_string_new_length(s + i, len - i - skip); } static int uc_fs_lsdir_sort_fn(const void *k1, const void *k2) { uc_value_t * const *v1 = k1; uc_value_t * const *v2 = k2; return strcmp(ucv_string_get(*v1), ucv_string_get(*v2)); } static uc_value_t * uc_fs_lsdir(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *pat = uc_fn_arg(1); uc_value_t *res = NULL; uc_regexp_t *reg; struct dirent *e; DIR *d; if (ucv_type(path) != UC_STRING) err_return(EINVAL); switch (ucv_type(pat)) { case UC_NULL: case UC_STRING: case UC_REGEXP: break; default: err_return(EINVAL); } d = opendir(ucv_string_get(path)); if (!d) err_return(errno); res = ucv_array_new(vm); while ((e = readdir(d)) != NULL) { if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue; if (ucv_type(pat) == UC_REGEXP) { reg = (uc_regexp_t *)pat; if (regexec(®->regexp, e->d_name, 0, NULL, 0) == REG_NOMATCH) continue; } else if (ucv_type(pat) == UC_STRING) { if (fnmatch(ucv_string_get(pat), e->d_name, 0) == FNM_NOMATCH) continue; } ucv_array_push(res, ucv_string_new(e->d_name)); } closedir(d); ucv_array_sort(res, uc_fs_lsdir_sort_fn); return res; } static uc_value_t * uc_fs_mkstemp(uc_vm_t *vm, size_t nargs) { uc_value_t *template = uc_fn_arg(0); bool ends_with_template = false; char *path, *t; FILE *fp; size_t l; int fd; if (template && ucv_type(template) != UC_STRING) err_return(EINVAL); t = ucv_string_get(template); l = ucv_string_length(template); ends_with_template = (l >= 6 && strcmp(&t[l - 6], "XXXXXX") == 0); if (t && strchr(t, '/')) { if (ends_with_template) xasprintf(&path, "%s", t); else xasprintf(&path, "%s.XXXXXX", t); } else if (t) { if (ends_with_template) xasprintf(&path, "/tmp/%s", t); else xasprintf(&path, "/tmp/%s.XXXXXX", t); } else { xasprintf(&path, "/tmp/XXXXXX"); } do { fd = mkstemp(path); } while (fd == -1 && errno == EINTR); if (fd == -1) { free(path); err_return(errno); } unlink(path); free(path); fp = fdopen(fd, "r+"); if (!fp) { close(fd); err_return(errno); } return uc_resource_new(file_type, fp); } static uc_value_t * uc_fs_access(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *test = uc_fn_arg(1); int mode = F_OK; char *p; if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (test && ucv_type(test) != UC_STRING) err_return(EINVAL); for (p = ucv_string_get(test); p && *p; p++) { switch (*p) { case 'r': mode |= R_OK; break; case 'w': mode |= W_OK; break; case 'x': mode |= X_OK; break; case 'f': mode |= F_OK; break; default: err_return(EINVAL); } } if (access(ucv_string_get(path), mode) == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_fs_readfile(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *size = uc_fn_arg(1); uc_value_t *res = NULL; uc_stringbuf_t *buf; ssize_t limit = -1; size_t rlen, blen; FILE *fp; if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (size) { if (ucv_type(size) != UC_INTEGER) err_return(EINVAL); limit = ucv_int64_get(size); } fp = fopen(ucv_string_get(path), "r"); if (!fp) err_return(errno); buf = ucv_stringbuf_new(); if (limit > -1 && limit < BUFSIZ) setvbuf(fp, NULL, _IONBF, 0); while (limit != 0) { blen = 1024; if (limit > 0 && blen > (size_t)limit) blen = (size_t)limit; printbuf_memset(buf, printbuf_length(buf) + blen - 1, 0, 1); buf->bpos -= blen; rlen = fread(buf->buf + buf->bpos, 1, blen, fp); buf->bpos += rlen; if (rlen < blen) break; if (limit > 0) limit -= rlen; } if (ferror(fp)) { fclose(fp); printbuf_free(buf); err_return(errno); } fclose(fp); /* add sentinel null byte but don't count it towards the string length */ printbuf_memappend_fast(buf, "\0", 1); res = ucv_stringbuf_finish(buf); ((uc_string_t *)res)->length--; return res; } static uc_value_t * uc_fs_writefile(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); uc_value_t *data = uc_fn_arg(1); uc_value_t *size = uc_fn_arg(2); uc_stringbuf_t *buf = NULL; ssize_t limit = -1; size_t wlen = 0; int err = 0; FILE *fp; if (ucv_type(path) != UC_STRING) err_return(EINVAL); if (size) { if (ucv_type(size) != UC_INTEGER) err_return(EINVAL); limit = ucv_int64_get(size); } fp = fopen(ucv_string_get(path), "w"); if (!fp) err_return(errno); if (data && ucv_type(data) != UC_STRING) { buf = xprintbuf_new(); ucv_to_stringbuf_formatted(vm, buf, data, 0, '\0', 0); if (limit < 0 || limit > printbuf_length(buf)) limit = printbuf_length(buf); wlen = fwrite(buf->buf, 1, limit, fp); if (wlen < (size_t)limit) err = errno; printbuf_free(buf); } else if (data) { if (limit < 0 || (size_t)limit > ucv_string_length(data)) limit = ucv_string_length(data); wlen = fwrite(ucv_string_get(data), 1, limit, fp); if (wlen < (size_t)limit) err = errno; } fclose(fp); if (err) err_return(err); return ucv_uint64_new(wlen); } static uc_value_t * uc_fs_realpath(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0), *rv; char *resolved; if (ucv_type(path) != UC_STRING) err_return(EINVAL); resolved = realpath(ucv_string_get(path), NULL); if (!resolved) err_return(errno); rv = ucv_string_new(resolved); free(resolved); return rv; } static uc_value_t * uc_fs_pipe(uc_vm_t *vm, size_t nargs) { int pfds[2], err; FILE *rfp, *wfp; uc_value_t *rv; if (pipe(pfds) == -1) err_return(errno); rfp = fdopen(pfds[0], "r"); if (!rfp) { err = errno; close(pfds[0]); close(pfds[1]); err_return(err); } wfp = fdopen(pfds[1], "w"); if (!wfp) { err = errno; fclose(rfp); close(pfds[1]); err_return(err); } rv = ucv_array_new_length(vm, 2); ucv_array_push(rv, uc_resource_new(file_type, rfp)); ucv_array_push(rv, uc_resource_new(file_type, wfp)); return rv; } static const uc_function_list_t proc_fns[] = { { "read", uc_fs_pread }, { "write", uc_fs_pwrite }, { "close", uc_fs_pclose }, { "flush", uc_fs_pflush }, { "fileno", uc_fs_pfileno }, { "error", uc_fs_error }, }; static const uc_function_list_t file_fns[] = { { "read", uc_fs_read }, { "write", uc_fs_write }, { "seek", uc_fs_seek }, { "tell", uc_fs_tell }, { "close", uc_fs_close }, { "flush", uc_fs_flush }, { "fileno", uc_fs_fileno }, { "error", uc_fs_error }, { "isatty", uc_fs_isatty }, }; static const uc_function_list_t dir_fns[] = { { "read", uc_fs_readdir }, { "seek", uc_fs_seekdir }, { "tell", uc_fs_telldir }, { "close", uc_fs_closedir }, { "error", uc_fs_error }, }; static const uc_function_list_t global_fns[] = { { "error", uc_fs_error }, { "open", uc_fs_open }, { "fdopen", uc_fs_fdopen }, { "opendir", uc_fs_opendir }, { "popen", uc_fs_popen }, { "readlink", uc_fs_readlink }, { "stat", uc_fs_stat }, { "lstat", uc_fs_lstat }, { "mkdir", uc_fs_mkdir }, { "rmdir", uc_fs_rmdir }, { "symlink", uc_fs_symlink }, { "unlink", uc_fs_unlink }, { "getcwd", uc_fs_getcwd }, { "chdir", uc_fs_chdir }, { "chmod", uc_fs_chmod }, { "chown", uc_fs_chown }, { "rename", uc_fs_rename }, { "glob", uc_fs_glob }, { "dirname", uc_fs_dirname }, { "basename", uc_fs_basename }, { "lsdir", uc_fs_lsdir }, { "mkstemp", uc_fs_mkstemp }, { "access", uc_fs_access }, { "readfile", uc_fs_readfile }, { "writefile", uc_fs_writefile }, { "realpath", uc_fs_realpath }, { "pipe", uc_fs_pipe }, }; static void close_proc(void *ud) { FILE *fp = ud; if (fp) pclose(fp); } static void close_file(void *ud) { FILE *fp = ud; int n; n = fp ? fileno(fp) : -1; if (n > 2) fclose(fp); } static void close_dir(void *ud) { DIR *dp = ud; if (dp) closedir(dp); } void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); proc_type = uc_type_declare(vm, "fs.proc", proc_fns, close_proc); file_type = uc_type_declare(vm, "fs.file", file_fns, close_file); dir_type = uc_type_declare(vm, "fs.dir", dir_fns, close_dir); ucv_object_add(scope, "stdin", uc_resource_new(file_type, stdin)); ucv_object_add(scope, "stdout", uc_resource_new(file_type, stdout)); ucv_object_add(scope, "stderr", uc_resource_new(file_type, stderr)); }