• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/fs.c

  1 /*
  2  * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
  3  *
  4  * Permission to use, copy, modify, and/or distribute this software for any
  5  * purpose with or without fee is hereby granted, provided that the above
  6  * copyright notice and this permission notice appear in all copies.
  7  *
  8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15  */
 16 
 17 /**
 18  * # Filesystem Access
 19  *
 20  * The `fs` module provides functions for interacting with the file system.
 21  *
 22  * Functions can be individually imported and directly accessed using the
 23  * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
 24  * syntax:
 25  *
 26  *   ```
 27  *   import { readlink, popen } from 'fs';
 28  *
 29  *   let dest = readlink('/sys/class/net/eth0');
 30  *   let proc = popen('ps ww');
 31  *   ```
 32  *
 33  * Alternatively, the module namespace can be imported
 34  * using a wildcard import statement:
 35  *
 36  *   ```
 37  *   import * as fs from 'fs';
 38  *
 39  *   let dest = fs.readlink('/sys/class/net/eth0');
 40  *   let proc = fs.popen('ps ww');
 41  *   ```
 42  *
 43  * Additionally, the filesystem module namespace may also be imported by invoking
 44  * the `ucode` interpreter with the `-lfs` switch.
 45  *
 46  * @module fs
 47  */
 48 
 49 #include <errno.h>
 50 #include <dirent.h>
 51 #include <unistd.h>
 52 #include <sys/stat.h>
 53 #include <sys/wait.h>
 54 #include <sys/file.h>
 55 #include <grp.h>
 56 #include <pwd.h>
 57 #include <glob.h>
 58 #include <fnmatch.h>
 59 #include <limits.h>
 60 #include <fcntl.h>
 61 #include <sys/statvfs.h>  /* statvfs(3) */
 62 
 63 #if defined(__linux__)
 64 #include <sys/statfs.h>   /* statfs() for f_type */
 65 #define HAS_IOCTL
 66 #endif
 67 
 68 #if defined(__APPLE__)
 69 #define HAS_MAC_IOCTL
 70 #endif
 71 
 72 #ifdef HAS_IOCTL
 73 #include <sys/ioctl.h>
 74 
 75 #define IOC_DIR_NONE    (_IOC_NONE)
 76 #define IOC_DIR_READ    (_IOC_READ)
 77 #define IOC_DIR_WRITE   (_IOC_WRITE)
 78 #define IOC_DIR_RW              (_IOC_READ | _IOC_WRITE)
 79 
 80 #define IOCTL_CMD(dir, type, num, size) _IOC((dir), (type), (num), (size))
 81 #endif
 82 
 83 #ifdef HAS_MAC_IOCTL
 84 #include <sys/ioctl.h>
 85 #include <sys/ioccom.h>
 86 
 87 #define IOC_DIR_NONE    0
 88 #define IOC_DIR_READ    1
 89 #define IOC_DIR_WRITE   2
 90 #define IOC_DIR_RW              (IOC_DIR_READ | IOC_DIR_WRITE)
 91 
 92 static unsigned long
 93 mac_ioctl_cmd(unsigned int dir, unsigned int type, unsigned int num, size_t size)
 94 {
 95         if (num == 0)
 96                 return type;
 97 
 98         switch (dir) {
 99         case IOC_DIR_NONE:
100                 return _IO(type, num);
101         case IOC_DIR_READ:
102                 return _IOR(type, num, void *);
103         case IOC_DIR_WRITE:
104                 return _IOW(type, num, void *);
105         case IOC_DIR_RW:
106                 return _IOWR(type, num, void *);
107         default:
108                 return 0;
109         }
110 }
111 
112 #define IOCTL_CMD(dir, type, num, size) mac_ioctl_cmd((dir), (type), (num), (size))
113 #endif
114 
115 #include "ucode/module.h"
116 #include "ucode/internal/platform.h"
117 
118 #define err_return(err) do { \
119         uc_vm_registry_set(vm, "fs.last_error", ucv_int64_new(err)); \
120         return NULL; \
121 } while(0)
122 
123 typedef struct {
124         FILE *fp;
125         pid_t pid;
126 } uc_proc_t;
127 
128 static int
129 get_fd(uc_vm_t *vm, uc_value_t *val)
130 {
131         uc_value_t *fn;
132         int64_t n;
133 
134         fn = ucv_property_get(val, "fileno");
135         errno = 0;
136 
137         if (ucv_is_callable(fn)) {
138                 uc_vm_stack_push(vm, ucv_get(val));
139                 uc_vm_stack_push(vm, ucv_get(fn));
140 
141                 if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
142                         return -1;
143 
144                 val = uc_vm_stack_pop(vm);
145                 n = ucv_int64_get(val);
146                 ucv_put(val);
147         }
148         else {
149                 n = ucv_int64_get(val);
150         }
151 
152         if (errno || n < 0 || n > (int64_t)INT_MAX)
153                 return -1;
154 
155         return (int)n;
156 }
157 
158 
159 /**
160  * Query error information.
161  *
162  * Returns a string containing a description of the last occurred error or
163  * `null` if there is no error information.
164  *
165  * @function module:fs#error
166  *
167  *
168  * @returns {?string}
169  *
170  * @example
171  * // Trigger file system error
172  * unlink('/path/does/not/exist');
173  *
174  * // Print error (should yield "No such file or directory")
175  * print(error(), "\n");
176  */
177 static uc_value_t *
178 uc_fs_error(uc_vm_t *vm, size_t nargs)
179 {
180         int last_error = ucv_int64_get(uc_vm_registry_get(vm, "fs.last_error"));
181 
182         if (last_error == 0)
183                 return NULL;
184 
185         uc_vm_registry_set(vm, "fs.last_error", ucv_int64_new(0));
186 
187         return ucv_string_new(strerror(last_error));
188 }
189 
190 static uc_value_t *
191 uc_fs_read_common(uc_vm_t *vm, size_t nargs, FILE **fp)
192 {
193         uc_value_t *limit = uc_fn_arg(0);
194         uc_value_t *rv = NULL;
195         char buf[128], *p = NULL, *tmp;
196         size_t rlen, len = 0;
197         const char *lstr;
198         int64_t lsize;
199         ssize_t llen;
200 
201         if (!fp || !*fp)
202                 err_return(EBADF);
203 
204         if (ucv_type(limit) == UC_STRING) {
205                 lstr = ucv_string_get(limit);
206                 llen = ucv_string_length(limit);
207 
208                 if (llen == 4 && !strcmp(lstr, "line")) {
209                         llen = getline(&p, &rlen, *fp);
210 
211                         if (llen == -1) {
212                                 free(p);
213                                 err_return(errno);
214                         }
215 
216                         len = (size_t)llen;
217                 }
218                 else if (llen == 3 && !strcmp(lstr, "all")) {
219                         while (true) {
220                                 rlen = fread(buf, 1, sizeof(buf), *fp);
221 
222                                 tmp = realloc(p, len + rlen);
223 
224                                 if (!tmp) {
225                                         free(p);
226                                         err_return(ENOMEM);
227                                 }
228 
229                                 memcpy(tmp + len, buf, rlen);
230 
231                                 p = tmp;
232                                 len += rlen;
233 
234                                 if (rlen == 0)
235                                         break;
236                         }
237                 }
238                 else if (llen == 1) {
239                         llen = getdelim(&p, &rlen, *lstr, *fp);
240 
241                         if (llen == -1) {
242                                 free(p);
243                                 err_return(errno);
244                         }
245 
246                         len = (size_t)llen;
247                 }
248                 else {
249                         return NULL;
250                 }
251         }
252         else if (ucv_type(limit) == UC_INTEGER) {
253                 lsize = ucv_int64_get(limit);
254 
255                 if (lsize <= 0)
256                         return NULL;
257 
258                 p = calloc(1, lsize);
259 
260                 if (!p)
261                         err_return(ENOMEM);
262 
263                 len = fread(p, 1, lsize, *fp);
264 
265                 if (ferror(*fp)) {
266                         free(p);
267                         err_return(errno);
268                 }
269         }
270         else {
271                 err_return(EINVAL);
272         }
273 
274         rv = ucv_string_new_length(p, len);
275         free(p);
276 
277         return rv;
278 }
279 
280 static uc_value_t *
281 uc_fs_write_common(uc_vm_t *vm, size_t nargs, FILE **fp)
282 {
283         uc_value_t *data = uc_fn_arg(0);
284         size_t len, wsize;
285         char *str;
286 
287         if (!fp || !*fp)
288                 err_return(EBADF);
289 
290         if (ucv_type(data) == UC_STRING) {
291                 len = ucv_string_length(data);
292                 wsize = fwrite(ucv_string_get(data), 1, len, *fp);
293         }
294         else {
295                 str = ucv_to_jsonstring(vm, data);
296                 len = str ? strlen(str) : 0;
297                 wsize = fwrite(str, 1, len, *fp);
298                 free(str);
299         }
300 
301         if (wsize < len && ferror(*fp))
302                 err_return(errno);
303 
304         return ucv_int64_new(wsize);
305 }
306 
307 static uc_value_t *
308 uc_fs_flush_common(uc_vm_t *vm, size_t nargs, FILE **fp)
309 {
310         if (!fp || !*fp)
311                 err_return(EBADF);
312 
313         if (fflush(*fp) == EOF)
314                 err_return(errno);
315 
316         return ucv_boolean_new(true);
317 }
318 
319 static uc_value_t *
320 uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, FILE **fp)
321 {
322         int fd;
323 
324         if (!fp || !*fp)
325                 err_return(EBADF);
326 
327         fd = fileno(*fp);
328 
329         if (fd == -1)
330                 err_return(errno);
331 
332         return ucv_int64_new(fd);
333 }
334 
335 
336 /**
337  * Represents a handle for interacting with a program launched by `popen()`.
338  *
339  * @class module:fs.proc
340  * @hideconstructor
341  *
342  * @borrows module:fs#error as module:fs.proc#error
343  *
344  * @see {@link module:fs#popen|popen()}
345  *
346  * @example
347  *
348  * const handle = popen(…);
349  *
350  * handle.read(…);
351  * handle.write(…);
352  * handle.flush();
353  *
354  * handle.fileno();
355  *
356  * handle.close();
357  *
358  * handle.error();
359  */
360 
361 /**
362  * Closes the program handle and awaits program termination.
363  *
364  * Upon calling `close()` on the handle, the program's input or output stream
365  * (depending on the open mode) is closed. Afterwards, the function awaits the
366  * termination of the underlying program and returns its exit code.
367  *
368  * - When the program was terminated by a signal, the return value will be the
369  *   negative signal number, e.g. `-9` for SIGKILL.
370  *
371  * - When the program terminated normally, the return value will be the positive
372  *   exit code of the program.
373  *
374  * Returns a negative signal number if the program was terminated by a signal.
375  *
376  * Returns a positive exit code if the program terminated normally.
377  *
378  * Returns `null` if an error occurred.
379  *
380  * @function module:fs.proc#close
381  *
382  * @returns {?number}
383  */
384 static uc_value_t *
385 uc_fs_pclose(uc_vm_t *vm, size_t nargs)
386 {
387         uc_proc_t *proc = uc_fn_thisval("fs.proc");
388         int rc, status;
389 
390         if (!proc || !proc->fp)
391                 err_return(EBADF);
392 
393         fclose(proc->fp);
394         proc->fp = NULL;
395 
396         do {
397                 rc = waitpid(proc->pid, &status, 0);
398         } while (rc == -1 && errno == EINTR);
399 
400         if (rc == -1)
401                 err_return(errno);
402 
403         if (WIFEXITED(status))
404                 return ucv_int64_new(WEXITSTATUS(status));
405 
406         if (WIFSIGNALED(status))
407                 return ucv_int64_new(-WTERMSIG(status));
408 
409         return ucv_int64_new(0);
410 }
411 
412 /**
413  * Reads a chunk of data from the program handle.
414  *
415  * The length argument may be either a positive number of bytes to read, in
416  * which case the read call returns up to that many bytes, or a string to
417  * specify a dynamic read size.
418  *
419  *  - If length is a number, the method will read the specified number of bytes
420  *    from the handle. Reading stops after the given amount of bytes or after
421  *    encountering EOF, whatever comes first.
422  *
423  *  - If length is the string "line", the method will read an entire line,
424  *    terminated by "\n" (a newline), from the handle. Reading stops at the next
425  *    newline or when encountering EOF. The returned data will contain the
426  *    terminating newline character if one was read.
427  *
428  *  - If length is the string "all", the method will read from the handle until
429  *    encountering EOF and return the complete contents.
430  *
431  *  - If length is a single character string, the method will read from the
432  *    handle until encountering the specified character or upon encountering
433  *    EOF. The returned data will contain the terminating character if one was
434  *    read.
435  *
436  * Returns a string containing the read data.
437  *
438  * Returns an empty string on EOF.
439  *
440  * Returns `null` if a read error occurred.
441  *
442  * @function module:fs.proc#read
443  *
444  * @param {number|string} length
445  * The length of data to read. Can be a number, the string "line", the string
446  * "all", or a single character string.
447  *
448  * @returns {?string}
449  *
450  * @example
451  * const fp = popen("command", "r");
452  *
453  * // Example 1: Read 10 bytes from the handle
454  * const chunk = fp.read(10);
455  *
456  * // Example 2: Read the handle line by line
457  * for (let line = fp.read("line"); length(line); line = fp.read("line"))
458  *   print(line);
459  *
460  * // Example 3: Read the complete contents from the handle
461  * const content = fp.read("all");
462  *
463  * // Example 4: Read until encountering the character ':'
464  * const field = fp.read(":");
465  */
466 static uc_value_t *
467 uc_fs_pread(uc_vm_t *vm, size_t nargs)
468 {
469         uc_proc_t *proc = uc_fn_thisval("fs.proc");
470         return uc_fs_read_common(vm, nargs, proc ? &proc->fp : NULL);
471 }
472 
473 /**
474  * Writes a chunk of data to the program handle.
475  *
476  * In case the given data is not a string, it is converted to a string before
477  * being written to the program's stdin. String values are written as-is,
478  * integer and double values are written in decimal notation, boolean values are
479  * written as `true` or `false` while arrays and objects are converted to their
480  * JSON representation before being written. The `null` value is represented by
481  * an empty string so `proc.write(null)` would be a no-op. Resource values are
482  * written in the form `<type address>`, e.g. `<fs.file 0x7f60f0981760>`.
483  *
484  * If resource, array or object values contain a `tostring()` function in their
485  * prototypes, then this function is invoked to obtain an alternative string
486  * representation of the value.
487  *
488  * Returns the number of bytes written.
489  *
490  * Returns `null` if a write error occurred.
491  *
492  * @function module:fs.proc#write
493  *
494  * @param {*} data
495  * The data to be written.
496  *
497  * @returns {?number}
498  *
499  * @example
500  * const fp = popen("command", "w");
501  *
502  * fp.write("Hello world!\n");
503  */
504 static uc_value_t *
505 uc_fs_pwrite(uc_vm_t *vm, size_t nargs)
506 {
507         uc_proc_t *proc = uc_fn_thisval("fs.proc");
508         return uc_fs_write_common(vm, nargs, proc ? &proc->fp : NULL);
509 }
510 
511 /**
512  * Forces a write of all buffered data to the underlying handle.
513  *
514  * Returns `true` if the data was successfully flushed.
515  *
516  * Returns `null` on error.
517  *
518  * @function module:fs.proc#flush
519  *
520  * @returns {?boolean}
521  *
522  */
523 static uc_value_t *
524 uc_fs_pflush(uc_vm_t *vm, size_t nargs)
525 {
526         uc_proc_t *proc = uc_fn_thisval("fs.proc");
527         return uc_fs_flush_common(vm, nargs, proc ? &proc->fp : NULL);
528 }
529 
530 /**
531  * Obtains the number of the handle's underlying file descriptor.
532  *
533  * Returns the descriptor number.
534  *
535  * Returns `null` on error.
536  *
537  * @function module:fs.proc#fileno
538  *
539  * @returns {?number}
540  */
541 static uc_value_t *
542 uc_fs_pfileno(uc_vm_t *vm, size_t nargs)
543 {
544         uc_proc_t *proc = uc_fn_thisval("fs.proc");
545         return uc_fs_fileno_common(vm, nargs, proc ? &proc->fp : NULL);
546 }
547 
548 /**
549  * Starts a process and returns a handle representing the executed process.
550  *
551  * The handle will be connected to the process stdin or stdout, depending on the
552  * value of the mode argument.
553  *
554  * The mode argument may be either "r" to open the process for reading (connect
555  * to its stdout) or "w" to open the process for writing (connect to its stdin).
556  *
557  * The mode character "r" or "w" may be optionally followed by "e" to apply the
558  * FD_CLOEXEC flag onto the open descriptor.
559  *
560  * Returns a process handle referring to the executed process.
561  *
562  * Returns `null` if an error occurred.
563  *
564  * @function module:fs#popen
565  *
566  * @param {string|Array<*>} command
567  * The command to be executed, either as a plain shell command string or as an
568  * array of arguments. When an array is provided the process is started directly
569  * via execvp() without involving a shell, so argument values are never
570  * interpreted as shell syntax. Non-string array elements are converted to their
571  * string representation. A string command is passed to /bin/sh -c as usual.
572  *
573  * @param {string} [mode="r"]
574  * The open mode of the process handle.
575  *
576  * @returns {?module:fs.proc}
577  *
578  * @example
579  * // Open a process with a command string (interpreted by the shell)
580  * const process = popen('ls -la /tmp', 'r');
581  *
582  * @example
583  * // Open a process with an argument array (no shell involved)
584  * const process = popen(['ls', '-la', '/tmp'], 'r');
585  */
586 static uc_value_t *
587 uc_fs_popen(uc_vm_t *vm, size_t nargs)
588 {
589         uc_value_t *comm = uc_fn_arg(0);
590         uc_value_t *mode = uc_fn_arg(1);
591         const char *modestr;
592         int pfds[2], write_mode, err;
593         size_t argc = 0;
594         uc_proc_t *proc;
595         pid_t pid;
596         FILE *fp;
597 
598         switch (ucv_type(comm)) {
599         case UC_STRING:
600                 break;
601 
602         case UC_ARRAY:
603                 argc = ucv_array_length(comm);
604 
605                 if (argc == 0)
606                         err_return(EINVAL);
607 
608                 /* Pre-fork validation of argv[0] to provide meaningful errno */
609                 {
610                         char *name = ucv_to_string(vm, ucv_array_get(comm, 0));
611                         char pathbuf[PATH_MAX];
612                         struct stat st;
613 
614                         err = ENOENT;
615 
616                         if (name && strchr(name, '/')) {
617                                 if (stat(name, &st) == -1)
618                                         err = errno;
619                                 else if (S_ISDIR(st.st_mode))
620                                         err = EISDIR;
621                                 else if (access(name, X_OK) == -1)
622                                         err = errno;
623                                 else
624                                         err = 0;
625                         } else if (name) {
626                                 const char *path = getenv("PATH");
627                                 const char *end;
628 
629                                 if (!path)
630                                         path = "/usr/local/bin:/usr/bin:/bin";
631 
632                                 for (; *path; path = (*end ? end + 1 : end)) {
633                                         end = strchrnul(path, ':');
634 
635                                         if (end == path)
636                                                 continue;
637 
638                                         if (snprintf(pathbuf, sizeof(pathbuf), "%.*s/%s",
639                                                      (int)(end - path), path, name) >= (int)sizeof(pathbuf))
640                                                 continue;
641 
642                                         if (stat(pathbuf, &st) == -1)
643                                                 continue;
644 
645                                         if (S_ISDIR(st.st_mode))
646                                                 err = EISDIR;
647                                         else if (access(pathbuf, X_OK) == -1)
648                                                 err = errno;
649                                         else
650                                                 err = 0;
651 
652                                         break;
653                                 }
654                         }
655 
656                         free(name);
657 
658                         if (err)
659                                 err_return(err);
660                 }
661                 break;
662 
663         default:
664                 err_return(EINVAL);
665         }
666 
667         modestr    = ucv_type(mode) == UC_STRING ? ucv_string_get(mode) : "r";
668         write_mode = (modestr[0] == 'w');
669 
670         if (pipe2(pfds, O_CLOEXEC) == -1)
671                 err_return(errno);
672 
673         pid = fork();
674 
675         if (pid == -1) {
676                 err = errno;
677                 close(pfds[0]);
678                 close(pfds[1]);
679                 err_return(err);
680         }
681 
682         if (pid == 0) {
683                 dup2(write_mode ? pfds[0] : pfds[1],
684                      write_mode ? STDIN_FILENO : STDOUT_FILENO);
685 
686                 close(pfds[0]);
687                 close(pfds[1]);
688 
689                 if (ucv_type(comm) == UC_ARRAY) {
690                         char **argv = calloc(argc + 1, sizeof(char *));
691 
692                         if (argv) {
693                                 size_t n = 0;
694 
695                                 for (size_t i = 0; i < argc; i++) {
696                                         argv[i] = ucv_to_string(vm, ucv_array_get(comm, i));
697                                         n += !!argv[i];
698                                 }
699 
700                                 if (n > 0 && n == argc)
701                                         execvp(argv[0], argv);
702                         }
703                 } else {
704                         execl("/bin/sh", "sh", "-c", ucv_string_get(comm), NULL);
705                 }
706 
707                 _exit(127);
708         }
709 
710         close(write_mode ? pfds[0] : pfds[1]);
711 
712         fp = fdopen(write_mode ? pfds[1] : pfds[0], write_mode ? "w" : "r");
713 
714         if (!fp) {
715                 err = errno;
716                 close(write_mode ? pfds[1] : pfds[0]);
717                 kill(pid, SIGKILL);
718                 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
719                 err_return(err);
720         }
721 
722         uc_value_t *res = ucv_resource_create_ex(vm, "fs.proc", (void **)&proc, 0, sizeof(*proc));
723 
724         if (!res) {
725                 fclose(fp);
726                 kill(pid, SIGKILL);
727                 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
728                 err_return(ENOMEM);
729         }
730 
731         proc->fp  = fp;
732         proc->pid = pid;
733 
734         return res;
735 }
736 
737 
738 /**
739  * Represents a handle for interacting with a file opened by one of the file
740  * open functions.
741  *
742  * @class module:fs.file
743  * @hideconstructor
744  *
745  * @borrows module:fs#error as module:fs.file#error
746  *
747  * @see {@link module:fs#open|open()}
748  * @see {@link module:fs#fdopen|fdopen()}
749  * @see {@link module:fs#mkstemp|mkstemp()}
750  * @see {@link module:fs#pipe|pipe()}
751  *
752  * @example
753  *
754  * const handle = open(…);
755  *
756  * handle.read(…);
757  * handle.write(…);
758  * handle.flush();
759  *
760  * handle.seek(…);
761  * handle.tell();
762  *
763  * handle.isatty();
764  * handle.fileno();
765  *
766  * handle.close();
767  *
768  * handle.error();
769  */
770 
771 /**
772  * Closes the file handle.
773  *
774  * Upon calling `close()` on the handle, buffered data is flushed and the
775  * underlying file descriptor is closed.
776  *
777  * Returns `true` if the handle was properly closed.
778  *
779  * Returns `null` if an error occurred.
780  *
781  * @function module:fs.file#close
782  *
783  * @returns {?boolean}
784  */
785 static uc_value_t *
786 uc_fs_close(uc_vm_t *vm, size_t nargs)
787 {
788         FILE **fp = uc_fn_this("fs.file");
789 
790         if (!fp || !*fp)
791                 err_return(EBADF);
792 
793         fclose(*fp);
794         *fp = NULL;
795 
796         return ucv_boolean_new(true);
797 }
798 
799 /**
800  * Reads a chunk of data from the file handle.
801  *
802  * The length argument may be either a positive number of bytes to read, in
803  * which case the read call returns up to that many bytes, or a string to
804  * specify a dynamic read size.
805  *
806  *  - If length is a number, the method will read the specified number of bytes
807  *    from the handle. Reading stops after the given amount of bytes or after
808  *    encountering EOF, whatever comes first.
809  *
810  *  - If length is the string "line", the method will read an entire line,
811  *    terminated by "\n" (a newline), from the handle. Reading stops at the next
812  *    newline or when encountering EOF. The returned data will contain the
813  *    terminating newline character if one was read.
814  *
815  *  - If length is the string "all", the method will read from the handle until
816  *    encountering EOF and return the complete contents.
817  *
818  *  - If length is a single character string, the method will read from the
819  *    handle until encountering the specified character or upon encountering
820  *    EOF. The returned data will contain the terminating character if one was
821  *    read.
822  *
823  * Returns a string containing the read data.
824  *
825  * Returns an empty string on EOF.
826  *
827  * Returns `null` if a read error occurred.
828  *
829  * @function module:fs.file#read
830  *
831  * @param {number|string} length
832  * The length of data to read. Can be a number, the string "line", the string
833  * "all", or a single character string.
834  *
835  * @returns {?string}
836  *
837  * @example
838  * const fp = open("file.txt", "r");
839  *
840  * // Example 1: Read 10 bytes from the handle
841  * const chunk = fp.read(10);
842  *
843  * // Example 2: Read the handle line by line
844  * for (let line = fp.read("line"); length(line); line = fp.read("line"))
845  *   print(line);
846  *
847  * // Example 3: Read the complete contents from the handle
848  * const content = fp.read("all");
849  *
850  * // Example 4: Read until encountering the character ':'
851  * const field = fp.read(":");
852  */
853 static uc_value_t *
854 uc_fs_read(uc_vm_t *vm, size_t nargs)
855 {
856         return uc_fs_read_common(vm, nargs, uc_fn_this("fs.file"));
857 }
858 
859 /**
860  * Writes a chunk of data to the file handle.
861  *
862  * In case the given data is not a string, it is converted to a string before
863  * being written into the file. String values are written as-is, integer and
864  * double values are written in decimal notation, boolean values are written as
865  * `true` or `false` while arrays and objects are converted to their JSON
866  * representation before being written. The `null` value is represented by an
867  * empty string so `file.write(null)` would be a no-op. Resource values are
868  * written in the form `<type address>`, e.g. `<fs.file 0x7f60f0981760>`.
869  *
870  * If resource, array or object values contain a `tostring()` function in their
871  * prototypes, then this function is invoked to obtain an alternative string
872  * representation of the value.
873  *
874  * Returns the number of bytes written.
875  *
876  * Returns `null` if a write error occurred.
877  *
878  * @function module:fs.file#write
879  *
880  * @param {*} data
881  * The data to be written.
882  *
883  * @returns {?number}
884  *
885  * @example
886  * const fp = open("file.txt", "w");
887  *
888  * fp.write("Hello world!\n");
889  */
890 static uc_value_t *
891 uc_fs_write(uc_vm_t *vm, size_t nargs)
892 {
893         return uc_fs_write_common(vm, nargs, uc_fn_this("fs.file"));
894 }
895 
896 /**
897  * Set file read position.
898  *
899  * Set the read position of the open file handle to the given offset and
900  * position.
901  *
902  * Returns `true` if the read position was set.
903  *
904  * Returns `null` if an error occurred.
905  *
906  * @function module:fs.file#seek
907  *
908  * @param {number} [offset=0]
909  * The offset in bytes.
910  *
911  * @param {number} [position=0]
912  * The position of the offset.
913  *
914  * | Position | Description                                                                                  |
915  * |----------|----------------------------------------------------------------------------------------------|
916  * | `0`      | The given offset is relative to the start of the file. This is the default value if omitted. |
917  * | `1`      | The given offset is relative to the current read position.                                   |
918  * | `2`      | The given offset is relative to the end of the file.                                         |
919  *
920  * @returns {?boolean}
921  *
922  * @example
923  * const fp = open("file.txt", "r");
924  *
925  * print(fp.read(100), "\n");  // read 100 bytes...
926  * fp.seek(0, 0);              // ... and reset position to start of file
927  * print(fp.read(100), "\n");  // ... read same 100 bytes again
928  *
929  * fp.seek(10, 1);  // skip 10 bytes forward, relative to current offset ...
930  * fp.tell();       // ... position is at 110 now
931  *
932  * fp.seek(-10, 2);            // set position to ten bytes before EOF ...
933  * print(fp.read(100), "\n");  // ... reads 10 bytes at most
934  */
935 static uc_value_t *
936 uc_fs_seek(uc_vm_t *vm, size_t nargs)
937 {
938         uc_value_t *ofs = uc_fn_arg(0);
939         uc_value_t *how = uc_fn_arg(1);
940         int whence, res;
941         off_t offset;
942 
943         FILE **fp = uc_fn_this("fs.file");
944 
945         if (!fp || !*fp)
946                 err_return(EBADF);
947 
948         if (!ofs)
949                 offset = 0;
950         else if (ucv_type(ofs) != UC_INTEGER)
951                 err_return(EINVAL);
952         else
953                 offset = (off_t)ucv_int64_get(ofs);
954 
955         if (!how)
956                 whence = 0;
957         else if (ucv_type(how) != UC_INTEGER)
958                 err_return(EINVAL);
959         else
960                 whence = (int)ucv_int64_get(how);
961 
962         res = fseeko(*fp, offset, whence);
963 
964         if (res < 0)
965                 err_return(errno);
966 
967         return ucv_boolean_new(true);
968 }
969 
970 /**
971  * Truncate file to a given size
972  *
973  * Returns `true` if the file was successfully truncated.
974  *
975  * Returns `null` if an error occurred.
976  *
977  * @function module:fs.file#truncate
978  *
979  * @param {number} [offset=0]
980  * The offset in bytes.
981  *
982  * @returns {?boolean}
983  */
984 static uc_value_t *
985 uc_fs_truncate(uc_vm_t *vm, size_t nargs)
986 {
987         FILE *fp = uc_fn_thisval("fs.file");
988         uc_value_t *ofs = uc_fn_arg(0);
989         off_t offset;
990 
991         if (!fp)
992                 err_return(EBADF);
993 
994         if (!ofs)
995                 offset = 0;
996         else if (ucv_type(ofs) != UC_INTEGER)
997                 err_return(EINVAL);
998         else
999                 offset = (off_t)ucv_int64_get(ofs);
1000 
1001         if (ftruncate(fileno(fp), offset) < 0)
1002                 err_return(errno);
1003 
1004         return ucv_boolean_new(true);
1005 }
1006 
1007 /**
1008  * Locks or unlocks a file.
1009  *
1010  * The mode argument specifies lock/unlock operation flags.
1011  *
1012  * | Flag    | Description                  |
1013  * |---------|------------------------------|
1014  * | "s"     | shared lock                  |
1015  * | "x"     | exclusive lock               |
1016  * | "n"     | don't block when locking     |
1017  * | "u"     | unlock                       |
1018  *
1019  * Returns `true` if the file was successfully locked/unlocked.
1020  *
1021  * Returns `null` if an error occurred.
1022  *
1023  * @function module:fs.file#lock
1024  *
1025  * @param {string} [op]
1026  * The lock operation flags
1027  *
1028  * @returns {?boolean}
1029  */
1030 static uc_value_t *
1031 uc_fs_lock(uc_vm_t *vm, size_t nargs)
1032 {
1033         FILE *fp = uc_fn_thisval("fs.file");
1034         uc_value_t *mode = uc_fn_arg(0);
1035         int i, op = 0;
1036         char *m;
1037 
1038         if (!fp)
1039                 err_return(EBADF);
1040 
1041         if (ucv_type(mode) != UC_STRING)
1042                 err_return(EINVAL);
1043 
1044         m = ucv_string_get(mode);
1045         for (i = 0; m[i]; i++) {
1046                 switch (m[i]) {
1047                 case 's': op |= LOCK_SH; break;
1048                 case 'x': op |= LOCK_EX; break;
1049                 case 'n': op |= LOCK_NB; break;
1050                 case 'u': op |= LOCK_UN; break;
1051                 default: err_return(EINVAL);
1052                 }
1053         }
1054 
1055         if (flock(fileno(fp), op) < 0)
1056                 err_return(errno);
1057 
1058         return ucv_boolean_new(true);
1059 }
1060 
1061 /**
1062  * Obtain current read position.
1063  *
1064  * Obtains the current, absolute read position of the open file.
1065  *
1066  * Returns an integer containing the current read offset in bytes.
1067  *
1068  * Returns `null` if an error occurred.
1069  *
1070  * @function module:fs.file#tell
1071  *
1072  * @returns {?number}
1073  */
1074 static uc_value_t *
1075 uc_fs_tell(uc_vm_t *vm, size_t nargs)
1076 {
1077         off_t offset;
1078 
1079         FILE **fp = uc_fn_this("fs.file");
1080 
1081         if (!fp || !*fp)
1082                 err_return(EBADF);
1083 
1084         offset = ftello(*fp);
1085 
1086         if (offset < 0)
1087                 err_return(errno);
1088 
1089         return ucv_int64_new(offset);
1090 }
1091 
1092 /**
1093  * Check for TTY.
1094  *
1095  * Checks whether the open file handle refers to a TTY (terminal) device.
1096  *
1097  * Returns `true` if the handle refers to a terminal.
1098  *
1099  * Returns `false` if the handle refers to another kind of file.
1100  *
1101  * Returns `null` on error.
1102  *
1103  * @function module:fs.file#isatty
1104  *
1105  * @returns {?boolean}
1106  *
1107  */
1108 static uc_value_t *
1109 uc_fs_isatty(uc_vm_t *vm, size_t nargs)
1110 {
1111         FILE **fp = uc_fn_this("fs.file");
1112         int fd;
1113 
1114         if (!fp || !*fp)
1115                 err_return(EBADF);
1116 
1117         fd = fileno(*fp);
1118 
1119         if (fd == -1)
1120                 err_return(errno);
1121 
1122         return ucv_boolean_new(isatty(fd) == 1);
1123 }
1124 
1125 /**
1126  * Forces a write of all buffered data to the underlying handle.
1127  *
1128  * Returns `true` if the data was successfully flushed.
1129  *
1130  * Returns `null` on error.
1131  *
1132  * @function module:fs.file#flush
1133  *
1134  * @returns {?boolean}
1135  *
1136  */
1137 static uc_value_t *
1138 uc_fs_flush(uc_vm_t *vm, size_t nargs)
1139 {
1140         return uc_fs_flush_common(vm, nargs, uc_fn_this("fs.file"));
1141 }
1142 
1143 /**
1144  * Obtains the number of the handle's underlying file descriptor.
1145  *
1146  * Returns the descriptor number.
1147  *
1148  * Returns `null` on error.
1149  *
1150  * @function module:fs.file#fileno
1151  *
1152  * @returns {?number}
1153  */
1154 static uc_value_t *
1155 uc_fs_fileno(uc_vm_t *vm, size_t nargs)
1156 {
1157         return uc_fs_fileno_common(vm, nargs, uc_fn_this("fs.file"));
1158 }
1159 
1160 #if defined(HAS_IOCTL) || defined(HAS_MAC_IOCTL)
1161 
1162 /**
1163  * Performs an ioctl operation on the file.
1164  *
1165  * The direction parameter specifies who is reading and writing,
1166  * from the user's point of view. It can be one of the following values:
1167  *
1168  * | Direction      | Description                                                                       |
1169  * |----------------|-----------------------------------------------------------------------------------|
1170  * | IOC_DIR_NONE   | neither userspace nor kernel is writing, ioctl is executed without passing data.  |
1171  * | IOC_DIR_WRITE  | userspace is writing and kernel is reading.                                       |
1172  * | IOC_DIR_READ   | kernel is writing and userspace is reading.                                       |
1173  * | IOC_DIR_RW     | userspace is writing and kernel is writing back into the data structure.          |
1174  *
1175  * Returns the result of the ioctl operation; for `IOC_DIR_READ` and
1176  * `IOC_DIR_RW` this is a string containing the data, otherwise a number as
1177  * return code.
1178  *
1179  * In case of an error, null is returned and error details are available via
1180  * {@link module:fs#error|error()}.
1181  *
1182  * @function module:fs.file#ioctl
1183  *
1184  * @param {number} direction
1185  * The direction of the ioctl operation. Use constants IOC_DIR_*.
1186  *
1187  * @param {number|null} type
1188  * The ioctl type (see https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html)
1189  *
1190  * @param {number} num
1191  * The ioctl sequence number.
1192  *
1193  * @param {number|string} [value]
1194  * The value to pass to the ioctl system call. For `IOC_DIR_NONE`, this argument
1195  * is ignored. With `IOC_DIR_READ`, the value should be a positive integer
1196  * specifying the number of bytes to expect from the kernel. For the other
1197  * directions, `IOC_DIR_WRITE` and `IOC_DIR_RW`, that value parameter must be a
1198  * string, serving as buffer for the data to send.
1199  *
1200  * @returns {?number|?string}
1201  */
1202 static uc_value_t *
1203 uc_fs_ioctl(uc_vm_t *vm, size_t nargs)
1204 {
1205         FILE *fp = uc_fn_thisval("fs.file");
1206         uc_value_t *direction = uc_fn_arg(0);
1207         uc_value_t *type = uc_fn_arg(1);
1208         uc_value_t *num = uc_fn_arg(2);
1209         uc_value_t *value = uc_fn_arg(3);
1210         uc_value_t *mem = NULL;
1211         char *buf = NULL;
1212         unsigned long req = 0;
1213         unsigned int dir, ty, nr;
1214         size_t sz = 0;
1215         int fd, ret;
1216 
1217         if (!fp)
1218                 err_return(EBADF);
1219 
1220         fd = fileno(fp);
1221         if (fd == -1)
1222                 err_return(EBADF);
1223 
1224         if (ucv_type(direction) != UC_INTEGER || ucv_type(type) != UC_INTEGER ||
1225             (ucv_type(num) != UC_INTEGER && ucv_type(num) != UC_NULL))
1226                 err_return(EINVAL);
1227 
1228         dir = ucv_uint64_get(direction);
1229         ty = ucv_uint64_get(type);
1230 
1231         switch (dir) {
1232         case IOC_DIR_NONE:
1233                 break;
1234 
1235         case IOC_DIR_WRITE:
1236                 if (ucv_type(value) != UC_STRING)
1237                         err_return(EINVAL);
1238 
1239                 sz = ucv_string_length(value);
1240                 buf = ucv_string_get(value);
1241                 break;
1242 
1243         case IOC_DIR_READ:
1244                 if (ucv_type(value) != UC_INTEGER)
1245                         err_return(EINVAL);
1246 
1247                 sz = ucv_to_unsigned(value);
1248 
1249                 if (errno != 0)
1250                         err_return(errno);
1251 
1252                 mem = xalloc(sizeof(uc_string_t) + sz + 1);
1253                 mem->type = UC_STRING;
1254                 mem->refcount = 1;
1255                 buf = ucv_string_get(mem);
1256                 ((uc_string_t *)mem)->length = sz;
1257                 break;
1258 
1259         case IOC_DIR_RW:
1260                 if (ucv_type(value) != UC_STRING)
1261                         err_return(EINVAL);
1262 
1263                 sz = ucv_string_length(value);
1264                 mem = ucv_string_new_length(ucv_string_get(value), sz);
1265                 buf = ucv_string_get(mem);
1266                 break;
1267 
1268         default:
1269                 err_return(EINVAL);
1270         }
1271 
1272         if (ucv_type(num) == UC_NULL) {
1273                 req = ty;
1274         }
1275         else {
1276                 nr = ucv_uint64_get(num);
1277                 req = IOCTL_CMD(dir, ty, nr, sz);
1278         }
1279 
1280         ret = ioctl(fd, req, buf);
1281 
1282         if (ret < 0) {
1283                 ucv_put(mem);
1284                 err_return(errno);
1285         }
1286 
1287         return mem ? mem : ucv_uint64_new(ret);
1288 }
1289 
1290 #endif
1291 
1292 /**
1293  * Opens a file.
1294  *
1295  * The mode argument specifies the way the file is opened, it may
1296  * start with one of the following values:
1297  *
1298  * | Mode    | Description                                                                                                   |
1299  * |---------|---------------------------------------------------------------------------------------------------------------|
1300  * | "r"     | Opens a file for reading. The file must exist.                                                                 |
1301  * | "w"     | Opens a file for writing. If the file exists, it is truncated. If the file does not exist, it is created.     |
1302  * | "a"     | Opens a file for appending. Data is written at the end of the file. If the file does not exist, it is created. |
1303  * | "r+"    | Opens a file for both reading and writing. The file must exist.                                              |
1304  * | "w+"    | Opens a file for both reading and writing. If the file exists, it is truncated. If the file does not exist, it is created. |
1305  * | "a+"    | Opens a file for both reading and appending. Data can be read and written at the end of the file. If the file does not exist, it is created. |
1306  *
1307  * Additionally, the following flag characters may be appended to
1308  * the mode value:
1309  *
1310  * | Flag    | Description                                                                                                   |
1311  * |---------|---------------------------------------------------------------------------------------------------------------|
1312  * | "x"     | Opens a file for exclusive creation. If the file exists, the `open` call fails.                             |
1313  * | "e"     | Opens a file with the `O_CLOEXEC` flag set, ensuring that the file descriptor is closed on `exec` calls.      |
1314  *
1315  * If the mode is one of `"w…"` or `"a…"`, the permission argument
1316  * controls the filesystem permissions bits used when creating
1317  * the file.
1318  *
1319  * Returns a file handle object associated with the opened file.
1320  *
1321  * @function module:fs#open
1322  *
1323  * @param {string} path
1324  * The path to the file.
1325  *
1326  * @param {string} [mode="r"]
1327  * The file opening mode.
1328  *
1329  * @param {number} [perm=0o666]
1330  * The file creation permissions (for modes `w…` and `a…`)
1331  *
1332  * @returns {?module:fs.file}
1333  *
1334  * @example
1335  * // Open a file in read-only mode
1336  * const fileHandle = open('file.txt', 'r');
1337  */
1338 static uc_value_t *
1339 uc_fs_open(uc_vm_t *vm, size_t nargs)
1340 {
1341         int open_mode, open_flags, fd, i;
1342         uc_value_t *path = uc_fn_arg(0);
1343         uc_value_t *mode = uc_fn_arg(1);
1344         uc_value_t *perm = uc_fn_arg(2);
1345         mode_t open_perm = 0666;
1346         FILE *fp;
1347         char *m;
1348 
1349         if (ucv_type(path) != UC_STRING)
1350                 err_return(EINVAL);
1351 
1352         m = (ucv_type(mode) == UC_STRING) ? ucv_string_get(mode) : "r";
1353 
1354         switch (*m) {
1355         case 'r':
1356                 open_mode = O_RDONLY;
1357                 open_flags = 0;
1358                 break;
1359 
1360         case 'w':
1361                 open_mode = O_WRONLY;
1362                 open_flags = O_CREAT | O_TRUNC;
1363                 break;
1364 
1365         case 'a':
1366                 open_mode = O_WRONLY;
1367                 open_flags = O_CREAT | O_APPEND;
1368                 break;
1369 
1370         default:
1371                 err_return(EINVAL);
1372         }
1373 
1374         for (i = 1; m[i]; i++) {
1375                 switch (m[i]) {
1376                 case '+': open_mode = O_RDWR;      break;
1377                 case 'x': open_flags |= O_EXCL;    break;
1378                 case 'e': open_flags |= O_CLOEXEC; break;
1379                 }
1380         }
1381 
1382         if (perm) {
1383                 if (ucv_type(perm) != UC_INTEGER)
1384                         err_return(EINVAL);
1385 
1386                 open_perm = ucv_int64_get(perm);
1387         }
1388 
1389 #ifdef O_LARGEFILE
1390         open_flags |= open_mode | O_LARGEFILE;
1391 #else
1392         open_flags |= open_mode;
1393 #endif
1394 
1395         fd = open(ucv_string_get(path), open_flags, open_perm);
1396 
1397         if (fd < 0)
1398                 err_return(errno);
1399 
1400         fp = fdopen(fd, m);
1401 
1402         if (!fp) {
1403                 i = errno;
1404                 close(fd);
1405                 err_return(i);
1406         }
1407 
1408         return ucv_resource_create(vm, "fs.file", fp);
1409 }
1410 
1411 /**
1412  * Associates a file descriptor number with a file handle object.
1413  *
1414  * The mode argument controls how the file handle object is opened
1415  * and must match the open mode of the underlying descriptor.
1416  *
1417  * It may be set to one of the following values:
1418  *
1419  * | Mode    | Description                                                                                                  |
1420  * |---------|--------------------------------------------------------------------------------------------------------------|
1421  * | "r"     | Opens a file stream for reading. The file descriptor must be valid and opened in read mode.                  |
1422  * | "w"     | Opens a file stream for writing. The file descriptor must be valid and opened in write mode.                 |
1423  * | "a"     | Opens a file stream for appending. The file descriptor must be valid and opened in write mode.               |
1424  * | "r+"    | Opens a file stream for both reading and writing. The file descriptor must be valid and opened in read/write mode. |
1425  * | "w+"    | Opens a file stream for both reading and writing. The file descriptor must be valid and opened in read/write mode. |
1426  * | "a+"    | Opens a file stream for both reading and appending. The file descriptor must be valid and opened in read/write mode. |
1427  *
1428  * Returns the file handle object associated with the file descriptor.
1429  *
1430  * @function module:fs#fdopen
1431  *
1432  * @param {number} fd
1433  * The file descriptor.
1434  *
1435  * @param {string} [mode="r"]
1436  * The open mode.
1437  *
1438  * @returns {Object}
1439  *
1440  * @example
1441  * // Associate file descriptors of stdin and stdout with handles
1442  * const stdinHandle = fdopen(0, 'r');
1443  * const stdoutHandle = fdopen(1, 'w');
1444  */
1445 static uc_value_t *
1446 uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
1447 {
1448         uc_value_t *fdno = uc_fn_arg(0);
1449         uc_value_t *mode = uc_fn_arg(1);
1450         int64_t n;
1451         FILE *fp;
1452 
1453         if (ucv_type(fdno) != UC_INTEGER)
1454                 err_return(EINVAL);
1455 
1456         n = ucv_int64_get(fdno);
1457 
1458         if (n < 0 || n > INT_MAX)
1459                 err_return(EBADF);
1460 
1461         fp = fdopen((int)n,
1462                 ucv_type(mode) == UC_STRING ? ucv_string_get(mode) : "r");
1463 
1464         if (!fp)
1465                 err_return(errno);
1466 
1467         return ucv_resource_create(vm, "fs.file", fp);
1468 }
1469 
1470 /**
1471  * Duplicates a file descriptor.
1472  *
1473  * This function duplicates the file descriptor `oldfd` to `newfd`. If `newfd`
1474  * was previously open, it is silently closed before being reused.
1475  *
1476  * Returns `true` on success.
1477  * Returns `null` on error.
1478  *
1479  * @function module:fs#dup2
1480  *
1481  * @param {number} oldfd
1482  * The file descriptor to duplicate.
1483  *
1484  * @param {number} newfd
1485  * The file descriptor number to duplicate to.
1486  *
1487  * @returns {?boolean}
1488  *
1489  * @example
1490  * // Redirect stderr to a log file
1491  * const logfile = open('/tmp/error.log', 'w');
1492  * dup2(logfile.fileno(), 2);
1493  * logfile.close();
1494  */
1495 static uc_value_t *
1496 uc_fs_dup2(uc_vm_t *vm, size_t nargs)
1497 {
1498         uc_value_t *oldfd_arg = uc_fn_arg(0);
1499         uc_value_t *newfd_arg = uc_fn_arg(1);
1500         int oldfd, newfd;
1501 
1502         oldfd = get_fd(vm, oldfd_arg);
1503 
1504         if (oldfd == -1)
1505                 err_return(errno ? errno : EBADF);
1506 
1507         newfd = get_fd(vm, newfd_arg);
1508 
1509         if (newfd == -1)
1510                 err_return(errno ? errno : EBADF);
1511 
1512         if (dup2(oldfd, newfd) == -1)
1513                 err_return(errno);
1514 
1515         return ucv_boolean_new(true);
1516 }
1517 
1518 
1519 /**
1520  * Represents a handle for interacting with a directory opened by `opendir()`.
1521  *
1522  * @class module:fs.dir
1523  * @hideconstructor
1524  *
1525  * @borrows module:fs#error as module:fs.dir#error
1526  *
1527  * @see {@link module:fs#opendir|opendir()}
1528  *
1529  * @example
1530  *
1531  * const handle = opendir(…);
1532  *
1533  * handle.read();
1534  *
1535  * handle.tell();
1536  * handle.seek(…);
1537  *
1538  * handle.close();
1539  *
1540  * handle.error();
1541  */
1542 
1543 /**
1544  * Obtains the number of the handle's underlying file descriptor.
1545  *
1546  * Returns the descriptor number.
1547  *
1548  * Returns `null` on error.
1549  *
1550  * @function module:fs.dir#fileno
1551  *
1552  * @returns {?number}
1553  */
1554 static uc_value_t *
1555 uc_fs_dfileno(uc_vm_t *vm, size_t nargs)
1556 {
1557         DIR *dp = uc_fn_thisval("fs.dir");
1558         int fd;
1559 
1560         if (!dp)
1561                 err_return(EBADF);
1562 
1563         fd = dirfd(dp);
1564 
1565         if (fd == -1)
1566                 err_return(errno);
1567 
1568         return ucv_int64_new(fd);
1569 }
1570 
1571 /**
1572  * Read the next entry from the open directory.
1573  *
1574  * Returns a string containing the entry name.
1575  *
1576  * Returns `null` if there are no more entries to read.
1577  *
1578  * Returns `null` if an error occurred.
1579  *
1580  * @function module:fs.dir#read
1581  *
1582  * @returns {?string}
1583  */
1584 static uc_value_t *
1585 uc_fs_readdir(uc_vm_t *vm, size_t nargs)
1586 {
1587         DIR **dp = uc_fn_this("fs.dir");
1588         struct dirent *e;
1589 
1590         if (!dp || !*dp)
1591                 err_return(EINVAL);
1592 
1593         errno = 0;
1594         e = readdir(*dp);
1595 
1596         if (!e)
1597                 err_return(errno);
1598 
1599         return ucv_string_new(e->d_name);
1600 }
1601 
1602 /**
1603  * Obtain current read position.
1604  *
1605  * Returns the current read position in the open directory handle which can be
1606  * passed back to the `seek()` function to return to this position. This is
1607  * mainly useful to read an open directory handle (or specific items) multiple
1608  * times.
1609  *
1610  * Returns an integer referring to the current position.
1611  *
1612  * Returns `null` if an error occurred.
1613  *
1614  * @function module:fs.dir#tell
1615  *
1616  * @returns {?number}
1617  */
1618 static uc_value_t *
1619 uc_fs_telldir(uc_vm_t *vm, size_t nargs)
1620 {
1621         DIR **dp = uc_fn_this("fs.dir");
1622         long position;
1623 
1624         if (!dp || !*dp)
1625                 err_return(EBADF);
1626 
1627         position = telldir(*dp);
1628 
1629         if (position == -1)
1630                 err_return(errno);
1631 
1632         return ucv_int64_new((int64_t)position);
1633 }
1634 
1635 /**
1636  * Set read position.
1637  *
1638  * Sets the read position within the open directory handle to the given offset
1639  * value. The offset value should be obtained by a previous call to `tell()` as
1640  * the specific integer values are implementation defined.
1641  *
1642  * Returns `true` if the read position was set.
1643  *
1644  * Returns `null` if an error occurred.
1645  *
1646  * @function module:fs.dir#seek
1647  *
1648  * @param {number} offset
1649  * Position value obtained by `tell()`.
1650  *
1651  * @returns {?boolean}
1652  *
1653  * @example
1654  *
1655  * const handle = opendir("/tmp");
1656  * const begin = handle.tell();
1657  *
1658  * print(handle.read(), "\n");
1659  *
1660  * handle.seek(begin);
1661  *
1662  * print(handle.read(), "\n");  // prints the first entry again
1663  */
1664 static uc_value_t *
1665 uc_fs_seekdir(uc_vm_t *vm, size_t nargs)
1666 {
1667         uc_value_t *ofs = uc_fn_arg(0);
1668         DIR **dp = uc_fn_this("fs.dir");
1669         long position;
1670 
1671         if (ucv_type(ofs) != UC_INTEGER)
1672                 err_return(EINVAL);
1673 
1674         if (!dp || !*dp)
1675                 err_return(EBADF);
1676 
1677         position = (long)ucv_int64_get(ofs);
1678 
1679         seekdir(*dp, position);
1680 
1681         return ucv_boolean_new(true);
1682 }
1683 
1684 /**
1685  * Closes the directory handle.
1686  *
1687  * Closes the underlying file descriptor referring to the opened directory.
1688  *
1689  * Returns `true` if the handle was properly closed.
1690  *
1691  * Returns `null` if an error occurred.
1692  *
1693  * @function module:fs.dir#close
1694  *
1695  * @returns {?boolean}
1696  */
1697 static uc_value_t *
1698 uc_fs_closedir(uc_vm_t *vm, size_t nargs)
1699 {
1700         DIR **dp = uc_fn_this("fs.dir");
1701 
1702         if (!dp || !*dp)
1703                 err_return(EBADF);
1704 
1705         closedir(*dp);
1706         *dp = NULL;
1707 
1708         return ucv_boolean_new(true);
1709 }
1710 
1711 /**
1712  * Opens a directory and returns a directory handle associated with the open
1713  * directory descriptor.
1714  *
1715  * Returns a director handle referring to the open directory.
1716  *
1717  * Returns `null` if an error occurred.
1718  *
1719  * @function module:fs#opendir
1720  *
1721  * @param {string} path
1722  * The path to the directory.
1723  *
1724  * @returns {?module:fs.dir}
1725  *
1726  * @example
1727  * // Open a directory
1728  * const directory = opendir('path/to/directory');
1729  */
1730 static uc_value_t *
1731 uc_fs_opendir(uc_vm_t *vm, size_t nargs)
1732 {
1733         uc_value_t *path = uc_fn_arg(0);
1734         DIR *dp;
1735 
1736         if (ucv_type(path) != UC_STRING)
1737                 err_return(EINVAL);
1738 
1739         dp = opendir(ucv_string_get(path));
1740 
1741         if (!dp)
1742                 err_return(errno);
1743 
1744         return ucv_resource_create(vm, "fs.dir", dp);
1745 }
1746 
1747 /**
1748  * Reads the target path of a symbolic link.
1749  *
1750  * Returns a string containing the target path.
1751  *
1752  * Returns `null` if an error occurred.
1753  *
1754  * @function module:fs#readlink
1755  *
1756  * @param {string} path
1757  * The path to the symbolic link.
1758  *
1759  * @returns {?string}
1760  *
1761  * @example
1762  * // Read the value of a symbolic link
1763  * const targetPath = readlink('symbolicLink');
1764  */
1765 static uc_value_t *
1766 uc_fs_readlink(uc_vm_t *vm, size_t nargs)
1767 {
1768         uc_value_t *path = uc_fn_arg(0);
1769         uc_value_t *res;
1770         ssize_t buflen = 0, rv;
1771         char *buf = NULL, *tmp;
1772 
1773         if (ucv_type(path) != UC_STRING)
1774                 err_return(EINVAL);
1775 
1776         do {
1777                 buflen += 128;
1778                 tmp = realloc(buf, buflen);
1779 
1780                 if (!tmp) {
1781                         free(buf);
1782                         err_return(ENOMEM);
1783                 }
1784 
1785                 buf = tmp;
1786                 rv = readlink(ucv_string_get(path), buf, buflen);
1787 
1788                 if (rv == -1) {
1789                         free(buf);
1790                         err_return(errno);
1791                 }
1792 
1793                 if (rv < buflen)
1794                         break;
1795         }
1796         while (true);
1797 
1798         res = ucv_string_new_length(buf, rv);
1799 
1800         free(buf);
1801 
1802         return res;
1803 }
1804 
1805 /**
1806  * @typedef {Object} module:fs.FileStatResult
1807  * @property {Object} dev - The device information.
1808  * @property {number} dev.major - The major device number.
1809  * @property {number} dev.minor - The minor device number.
1810  * @property {Object} perm - The file permissions.
1811  * @property {boolean} perm.setuid - Whether the setuid bit is set.
1812  * @property {boolean} perm.setgid - Whether the setgid bit is set.
1813  * @property {boolean} perm.sticky - Whether the sticky bit is set.
1814  * @property {boolean} perm.user_read - Whether the file is readable by the owner.
1815  * @property {boolean} perm.user_write - Whether the file is writable by the owner.
1816  * @property {boolean} perm.user_exec - Whether the file is executable by the owner.
1817  * @property {boolean} perm.group_read - Whether the file is readable by the group.
1818  * @property {boolean} perm.group_write - Whether the file is writable by the group.
1819  * @property {boolean} perm.group_exec - Whether the file is executable by the group.
1820  * @property {boolean} perm.other_read - Whether the file is readable by others.
1821  * @property {boolean} perm.other_write - Whether the file is writable by others.
1822  * @property {boolean} perm.other_exec - Whether the file is executable by others.
1823  * @property {number} inode - The inode number.
1824  * @property {number} mode - The file mode.
1825  * @property {number} nlink - The number of hard links.
1826  * @property {number} uid - The user ID of the owner.
1827  * @property {number} gid - The group ID of the owner.
1828  * @property {number} size - The file size in bytes.
1829  * @property {number} blksize - The block size for file system I/O.
1830  * @property {number} blocks - The number of 512-byte blocks allocated for the file.
1831  * @property {number} atime - The timestamp when the file was last accessed.
1832  * @property {number} mtime - The timestamp when the file was last modified.
1833  * @property {number} ctime - The timestamp when the file status was last changed.
1834  * @property {string} type - The type of the file ("directory", "file", etc.).
1835  */
1836 
1837 static uc_value_t *
1838 uc_fs_stat_common(uc_vm_t *vm, size_t nargs, bool use_lstat)
1839 {
1840         uc_value_t *path = uc_fn_arg(0);
1841         uc_value_t *res, *o;
1842         struct stat st;
1843         int rv;
1844 
1845         if (ucv_type(path) != UC_STRING)
1846                 err_return(EINVAL);
1847 
1848         rv = (use_lstat ? lstat : stat)(ucv_string_get(path), &st);
1849 
1850         if (rv == -1)
1851                 err_return(errno);
1852 
1853         res = ucv_object_new(vm);
1854 
1855         if (!res)
1856                 err_return(ENOMEM);
1857 
1858         o = ucv_object_new(vm);
1859 
1860         if (o) {
1861                 ucv_object_add(o, "major", ucv_int64_new(major(st.st_dev)));
1862                 ucv_object_add(o, "minor", ucv_int64_new(minor(st.st_dev)));
1863 
1864                 ucv_object_add(res, "dev", o);
1865         }
1866 
1867         o = ucv_object_new(vm);
1868 
1869         if (o) {
1870                 ucv_object_add(o, "setuid", ucv_boolean_new(st.st_mode & S_ISUID));
1871                 ucv_object_add(o, "setgid", ucv_boolean_new(st.st_mode & S_ISGID));
1872                 ucv_object_add(o, "sticky", ucv_boolean_new(st.st_mode & S_ISVTX));
1873 
1874                 ucv_object_add(o, "user_read", ucv_boolean_new(st.st_mode & S_IRUSR));
1875                 ucv_object_add(o, "user_write", ucv_boolean_new(st.st_mode & S_IWUSR));
1876                 ucv_object_add(o, "user_exec", ucv_boolean_new(st.st_mode & S_IXUSR));
1877 
1878                 ucv_object_add(o, "group_read", ucv_boolean_new(st.st_mode & S_IRGRP));
1879                 ucv_object_add(o, "group_write", ucv_boolean_new(st.st_mode & S_IWGRP));
1880                 ucv_object_add(o, "group_exec", ucv_boolean_new(st.st_mode & S_IXGRP));
1881 
1882                 ucv_object_add(o, "other_read", ucv_boolean_new(st.st_mode & S_IROTH));
1883                 ucv_object_add(o, "other_write", ucv_boolean_new(st.st_mode & S_IWOTH));
1884                 ucv_object_add(o, "other_exec", ucv_boolean_new(st.st_mode & S_IXOTH));
1885 
1886                 ucv_object_add(res, "perm", o);
1887         }
1888 
1889         ucv_object_add(res, "inode", ucv_int64_new((int64_t)st.st_ino));
1890         ucv_object_add(res, "mode", ucv_int64_new((int64_t)st.st_mode & ~S_IFMT));
1891         ucv_object_add(res, "nlink", ucv_int64_new((int64_t)st.st_nlink));
1892         ucv_object_add(res, "uid", ucv_int64_new((int64_t)st.st_uid));
1893         ucv_object_add(res, "gid", ucv_int64_new((int64_t)st.st_gid));
1894         ucv_object_add(res, "size", ucv_int64_new((int64_t)st.st_size));
1895         ucv_object_add(res, "blksize", ucv_int64_new((int64_t)st.st_blksize));
1896         ucv_object_add(res, "blocks", ucv_int64_new((int64_t)st.st_blocks));
1897         ucv_object_add(res, "atime", ucv_int64_new((int64_t)st.st_atime));
1898         ucv_object_add(res, "mtime", ucv_int64_new((int64_t)st.st_mtime));
1899         ucv_object_add(res, "ctime", ucv_int64_new((int64_t)st.st_ctime));
1900 
1901         if (S_ISREG(st.st_mode))
1902                 ucv_object_add(res, "type", ucv_string_new("file"));
1903         else if (S_ISDIR(st.st_mode))
1904                 ucv_object_add(res, "type", ucv_string_new("directory"));
1905         else if (S_ISCHR(st.st_mode))
1906                 ucv_object_add(res, "type", ucv_string_new("char"));
1907         else if (S_ISBLK(st.st_mode))
1908                 ucv_object_add(res, "type", ucv_string_new("block"));
1909         else if (S_ISFIFO(st.st_mode))
1910                 ucv_object_add(res, "type", ucv_string_new("fifo"));
1911         else if (S_ISLNK(st.st_mode))
1912                 ucv_object_add(res, "type", ucv_string_new("link"));
1913         else if (S_ISSOCK(st.st_mode))
1914                 ucv_object_add(res, "type", ucv_string_new("socket"));
1915         else
1916                 ucv_object_add(res, "type", ucv_string_new("unknown"));
1917 
1918         return res;
1919 }
1920 
1921 /**
1922  * Retrieves information about a file or directory.
1923  *
1924  * Returns an object containing information about the file or directory.
1925  *
1926  * Returns `null` if an error occurred, e.g. due to insufficient permissions.
1927  *
1928  * @function module:fs#stat
1929  *
1930  * @param {string} path
1931  * The path to the file or directory.
1932  *
1933  * @returns {?module:fs.FileStatResult}
1934  *
1935  * @example
1936  * // Get information about a file
1937  * const fileInfo = stat('path/to/file');
1938  */
1939 static uc_value_t *
1940 uc_fs_stat(uc_vm_t *vm, size_t nargs)
1941 {
1942         return uc_fs_stat_common(vm, nargs, false);
1943 }
1944 
1945 /**
1946  * Retrieves information about a file or directory, without following symbolic
1947  * links.
1948  *
1949  * Returns an object containing information about the file or directory.
1950  *
1951  * Returns `null` if an error occurred, e.g. due to insufficient permissions.
1952  *
1953  * @function module:fs#lstat
1954  *
1955  * @param {string} path
1956  * The path to the file or directory.
1957  *
1958  * @returns {?module:fs.FileStatResult}
1959  *
1960  * @example
1961  * // Get information about a directory
1962  * const dirInfo = lstat('path/to/directory');
1963  */
1964 static uc_value_t *
1965 uc_fs_lstat(uc_vm_t *vm, size_t nargs)
1966 {
1967         return uc_fs_stat_common(vm, nargs, true);
1968 }
1969 
1970 /**
1971  * @typedef {Object} module:fs.StatVFSResult
1972  * @property {number} bsize file system block size
1973  * @property {number} frsize fragment size
1974  * @property {number} blocks total blocks
1975  * @property {number} bfree free blocks
1976  * @property {number} bavail free blocks available to unprivileged users
1977  * @property {number} files total file nodes (inodes)
1978  * @property {number} ffree free file nodes
1979  * @property {number} favail free nodes available to unprivileged users
1980  * @property {number} fsid file system id
1981  * @property {module:fs.ST_FLAGS} flag mount flags
1982  * @property {number} namemax maximum filename length
1983  * @property {number} freesize free space in bytes (calculated as `frsize * bfree`)
1984  * @property {number} totalsize total size of the filesystem (calculated as `frsize * blocks`)
1985  * @property {number} type (Linux only) magic number of the filesystem, obtained from `statfs`
1986  */
1987 
1988 /**
1989  * Query filesystem statistics for a given pathname.
1990  *
1991  * The returned object mirrors the members of `struct statvfs`.
1992  * Convenience properties `freesize` and `totalsize` are added,
1993  * which are calculated as:
1994  * - `frsize * bfree` to provide the free space in bytes and
1995  * - `frsize * blocks` to provide the total size of the filesystem.
1996  *
1997  * On Linux an additional `type` field (magic number from `statfs`) is
1998  * provided if the call succeeds.
1999  *
2000  * Returns `null` on failure (and sets `fs.last_error`).
2001  *
2002  * @function module:fs#statvfs
2003  *
2004  * @param {string} path
2005  * The path to the directory or file with which to query the filesystem.
2006  *
2007  * @returns {?module:fs.StatVFSResult}
2008  *
2009  * @example
2010  * // Get filesystem statistics for a path
2011  * const stats = statvfs('path/to/directory');
2012  * print(stats.bsize); // file system block size
2013  */
2014 static uc_value_t *
2015 uc_fs_statvfs(uc_vm_t *vm, size_t nargs)
2016 {
2017         uc_value_t *path = uc_fn_arg(0);
2018         struct statvfs sv;
2019 
2020         if (ucv_type(path) != UC_STRING)
2021                 err_return(EINVAL);
2022 
2023         if (statvfs(ucv_string_get(path), &sv) == -1)
2024                 err_return(errno);
2025 
2026         uc_value_t *o = ucv_object_new(vm);
2027 
2028         ucv_object_add(o, "bsize",    ucv_int64_new(sv.f_bsize));
2029         ucv_object_add(o, "frsize",   ucv_int64_new(sv.f_frsize));
2030         ucv_object_add(o, "blocks",   ucv_int64_new(sv.f_blocks));
2031         ucv_object_add(o, "bfree",    ucv_int64_new(sv.f_bfree));
2032         ucv_object_add(o, "bavail",   ucv_int64_new(sv.f_bavail));
2033         ucv_object_add(o, "files",    ucv_int64_new(sv.f_files));
2034         ucv_object_add(o, "ffree",    ucv_int64_new(sv.f_ffree));
2035         ucv_object_add(o, "favail",   ucv_int64_new(sv.f_favail));
2036         ucv_object_add(o, "fsid",     ucv_int64_new(sv.f_fsid));
2037         ucv_object_add(o, "flag",     ucv_int64_new(sv.f_flag));
2038         ucv_object_add(o, "namemax",  ucv_int64_new(sv.f_namemax));
2039         ucv_object_add(o, "freesize", ucv_int64_new(sv.f_frsize * sv.f_bfree));
2040         ucv_object_add(o, "totalsize",ucv_int64_new(sv.f_frsize * sv.f_blocks));
2041 
2042 #ifdef __linux__
2043         /* Call statfs to expose the magic number (`f_type`) */
2044         struct statfs sf;
2045         if (statfs(ucv_string_get(path), &sf) == 0) {
2046                 ucv_object_add(o, "type", ucv_int64_new(sf.f_type));
2047         }
2048 #endif
2049 
2050         return o;
2051 }
2052 
2053 /**
2054  * Creates a new directory.
2055  *
2056  * Returns `true` if the directory was successfully created.
2057  *
2058  * Returns `null` if an error occurred, e.g. due to non-existent path.
2059  *
2060  * @function module:fs#mkdir
2061  *
2062  * @param {string} path
2063  * The path to the new directory.
2064  *
2065  * @returns {?boolean}
2066  *
2067  * @example
2068  * // Create a directory
2069  * mkdir('path/to/new-directory');
2070  */
2071 static uc_value_t *
2072 uc_fs_mkdir(uc_vm_t *vm, size_t nargs)
2073 {
2074         uc_value_t *path = uc_fn_arg(0);
2075         uc_value_t *mode = uc_fn_arg(1);
2076 
2077         if (ucv_type(path) != UC_STRING ||
2078             (mode && ucv_type(mode) != UC_INTEGER))
2079                 err_return(EINVAL);
2080 
2081         if (mkdir(ucv_string_get(path), (mode_t)(mode ? ucv_int64_get(mode) : 0777)) == -1)
2082                 err_return(errno);
2083 
2084         return ucv_boolean_new(true);
2085 }
2086 
2087 /**
2088  * Removes the specified directory.
2089  *
2090  * Returns `true` if the directory was successfully removed.
2091  *
2092  * Returns `null` if an error occurred, e.g. due to non-existent path.
2093  *
2094  * @function module:fs#rmdir
2095  *
2096  * @param {string} path
2097  * The path to the directory to be removed.
2098  *
2099  * @returns {?boolean}
2100  *
2101  * @example
2102  * // Remove a directory
2103  * rmdir('path/to/directory');
2104  */
2105 static uc_value_t *
2106 uc_fs_rmdir(uc_vm_t *vm, size_t nargs)
2107 {
2108         uc_value_t *path = uc_fn_arg(0);
2109 
2110         if (ucv_type(path) != UC_STRING)
2111                 err_return(EINVAL);
2112 
2113         if (rmdir(ucv_string_get(path)) == -1)
2114                 err_return(errno);
2115 
2116         return ucv_boolean_new(true);
2117 }
2118 
2119 /**
2120  * Creates a new symbolic link.
2121  *
2122  * Returns `true` if the symlink was successfully created.
2123  *
2124  * Returns `null` if an error occurred, e.g. due to non-existent path.
2125  *
2126  * @function module:fs#symlink
2127  *
2128  * @param {string} target
2129  * The target of the symbolic link.
2130  *
2131  * @param {string} path
2132  * The path of the symbolic link.
2133  *
2134  * @returns {?boolean}
2135  *
2136  * @example
2137  * // Create a symbolic link
2138  * symlink('target', 'path/to/symlink');
2139  */
2140 static uc_value_t *
2141 uc_fs_symlink(uc_vm_t *vm, size_t nargs)
2142 {
2143         uc_value_t *dest = uc_fn_arg(0);
2144         uc_value_t *path = uc_fn_arg(1);
2145 
2146         if (ucv_type(dest) != UC_STRING ||
2147             ucv_type(path) != UC_STRING)
2148                 err_return(EINVAL);
2149 
2150         if (symlink(ucv_string_get(dest), ucv_string_get(path)) == -1)
2151                 err_return(errno);
2152 
2153         return ucv_boolean_new(true);
2154 }
2155 
2156 /**
2157  * Removes the specified file or symbolic link.
2158  *
2159  * Returns `true` if the unlink operation was successful.
2160  *
2161  * Returns `null` if an error occurred, e.g. due to non-existent path.
2162  *
2163  * @function module:fs#unlink
2164  *
2165  * @param {string} path
2166  * The path to the file or symbolic link.
2167  *
2168  * @returns {?boolean}
2169  *
2170  * @example
2171  * // Remove a file
2172  * unlink('path/to/file');
2173  */
2174 static uc_value_t *
2175 uc_fs_unlink(uc_vm_t *vm, size_t nargs)
2176 {
2177         uc_value_t *path = uc_fn_arg(0);
2178 
2179         if (ucv_type(path) != UC_STRING)
2180                 err_return(EINVAL);
2181 
2182         if (unlink(ucv_string_get(path)) == -1)
2183                 err_return(errno);
2184 
2185         return ucv_boolean_new(true);
2186 }
2187 
2188 /**
2189  * Retrieves the current working directory.
2190  *
2191  * Returns a string containing the current working directory path.
2192  *
2193  * Returns `null` if an error occurred.
2194  *
2195  * @function module:fs#getcwd
2196  *
2197  * @returns {?string}
2198  *
2199  * @example
2200  * // Get the current working directory
2201  * const cwd = getcwd();
2202  */
2203 static uc_value_t *
2204 uc_fs_getcwd(uc_vm_t *vm, size_t nargs)
2205 {
2206         uc_value_t *res;
2207         char *buf = NULL, *tmp;
2208         size_t buflen = 0;
2209 
2210         do {
2211                 buflen += 128;
2212                 tmp = realloc(buf, buflen);
2213 
2214                 if (!tmp) {
2215                         free(buf);
2216                         err_return(ENOMEM);
2217                 }
2218 
2219                 buf = tmp;
2220 
2221                 if (getcwd(buf, buflen) != NULL)
2222                         break;
2223 
2224                 if (errno == ERANGE)
2225                         continue;
2226 
2227                 free(buf);
2228                 err_return(errno);
2229         }
2230         while (true);
2231 
2232         res = ucv_string_new(buf);
2233 
2234         free(buf);
2235 
2236         return res;
2237 }
2238 
2239 /**
2240  * Changes the current working directory to the specified path.
2241  *
2242  * Returns `true` if the permission change was successful.
2243  *
2244  * Returns `null` if an error occurred, e.g. due to insufficient permissions or
2245  * invalid arguments.
2246  *
2247  * @function module:fs#chdir
2248  *
2249  * @param {string} path
2250  * The path to the new working directory.
2251  *
2252  * @returns {?boolean}
2253  *
2254  * @example
2255  * // Change the current working directory
2256  * chdir('new-directory');
2257  */
2258 static uc_value_t *
2259 uc_fs_chdir(uc_vm_t *vm, size_t nargs)
2260 {
2261         uc_value_t *path = uc_fn_arg(0);
2262 
2263         if (ucv_type(path) == UC_STRING) {
2264                 if (chdir(ucv_string_get(path)) == -1)
2265                         err_return(errno);
2266         }
2267         else {
2268                 int fd = get_fd(vm, path);
2269 
2270                 if (fd < 0)
2271                         err_return(EINVAL);
2272 
2273                 if (fchdir(fd) == -1)
2274                         err_return(errno);
2275         }
2276 
2277         return ucv_boolean_new(true);
2278 }
2279 
2280 /**
2281  * Changes the permission mode bits of a file or directory.
2282  *
2283  * Returns `true` if the permission change was successful.
2284  *
2285  * Returns `null` if an error occurred, e.g. due to insufficient permissions or
2286  * invalid arguments.
2287  *
2288  * @function module:fs#chmod
2289  *
2290  * @param {string} path
2291  * The path to the file or directory.
2292  *
2293  * @param {number} mode
2294  * The new mode (permissions).
2295  *
2296  * @returns {?boolean}
2297  *
2298  * @example
2299  * // Change the mode of a file
2300  * chmod('path/to/file', 0o644);
2301  */
2302 static uc_value_t *
2303 uc_fs_chmod(uc_vm_t *vm, size_t nargs)
2304 {
2305         uc_value_t *path = uc_fn_arg(0);
2306         uc_value_t *mode = uc_fn_arg(1);
2307 
2308         if (ucv_type(path) != UC_STRING ||
2309             ucv_type(mode) != UC_INTEGER)
2310                 err_return(EINVAL);
2311 
2312         if (chmod(ucv_string_get(path), (mode_t)ucv_int64_get(mode)) == -1)
2313                 err_return(errno);
2314 
2315         return ucv_boolean_new(true);
2316 }
2317 
2318 static bool
2319 uc_fs_resolve_user(uc_value_t *v, uid_t *uid)
2320 {
2321         struct passwd *pw = NULL;
2322         int64_t n;
2323         char *s;
2324 
2325         *uid = (uid_t)-1;
2326 
2327         switch (ucv_type(v)) {
2328         case UC_INTEGER:
2329                 n = ucv_int64_get(v);
2330 
2331                 if (n < -1) {
2332                         errno = ERANGE;
2333 
2334                         return false;
2335                 }
2336 
2337                 *uid = (uid_t)n;
2338 
2339                 return true;
2340 
2341         case UC_STRING:
2342                 s = ucv_string_get(v);
2343                 pw = getpwnam(s);
2344 
2345                 if (!pw) {
2346                         errno = ENOENT;
2347 
2348                         return false;
2349                 }
2350 
2351                 *uid = pw->pw_uid;
2352 
2353                 return true;
2354 
2355         case UC_NULL:
2356                 return true;
2357 
2358         default:
2359                 errno = EINVAL;
2360 
2361                 return false;
2362         }
2363 }
2364 
2365 static bool
2366 uc_fs_resolve_group(uc_value_t *v, gid_t *gid)
2367 {
2368         struct group *gr = NULL;
2369         int64_t n;
2370         char *s;
2371 
2372         *gid = (gid_t)-1;
2373 
2374         switch (ucv_type(v)) {
2375         case UC_INTEGER:
2376                 n = ucv_int64_get(v);
2377 
2378                 if (n < -1) {
2379                         errno = ERANGE;
2380 
2381                         return false;
2382                 }
2383 
2384                 *gid = (gid_t)n;
2385 
2386                 return true;
2387 
2388         case UC_STRING:
2389                 s = ucv_string_get(v);
2390                 gr = getgrnam(s);
2391 
2392                 if (!gr) {
2393                         errno = ENOENT;
2394 
2395                         return false;
2396                 }
2397 
2398                 *gid = gr->gr_gid;
2399 
2400                 return true;
2401 
2402         case UC_NULL:
2403                 return true;
2404 
2405         default:
2406                 errno = EINVAL;
2407 
2408                 return false;
2409         }
2410 }
2411 
2412 /**
2413  * Changes the owner and group of a file or directory.
2414  *
2415  * The user and group may be specified either as uid or gid number respectively,
2416  * or as a string containing the user or group name, in which case it is
2417  * resolved to the proper uid/gid first.
2418  *
2419  * If either the user or group parameter is omitted or given as `-1`,
2420  * it is not changed.
2421  *
2422  * Returns `true` if the ownership change was successful.
2423  *
2424  * Returns `null` if an error occurred or if a user/group name cannot be
2425  * resolved to a uid/gid value.
2426  *
2427  * @function module:fs#chown
2428  *
2429  * @param {string} path
2430  * The path to the file or directory.
2431  *
2432  * @param {number|string} [uid=-1]
2433  * The new owner's user ID. When given as number, it is used as-is, when given
2434  * as string, the user name is resolved to the corresponding uid first.
2435  *
2436  * @param {number|string} [gid=-1]
2437  * The new group's ID. When given as number, it is used as-is, when given as
2438  * string, the group name is resolved to the corresponding gid first.
2439  *
2440  * @returns {?boolean}
2441  *
2442  * @example
2443  * // Change the owner of a file
2444  * chown('path/to/file', 1000);
2445  *
2446  * // Change the group of a directory
2447  * chown('/htdocs/', null, 'www-data');
2448  */
2449 static uc_value_t *
2450 uc_fs_chown(uc_vm_t *vm, size_t nargs)
2451 {
2452         uc_value_t *path = uc_fn_arg(0);
2453         uc_value_t *user = uc_fn_arg(1);
2454         uc_value_t *group = uc_fn_arg(2);
2455         uid_t uid;
2456         gid_t gid;
2457 
2458         if (ucv_type(path) != UC_STRING)
2459             err_return(EINVAL);
2460 
2461         if (!uc_fs_resolve_user(user, &uid) ||
2462             !uc_fs_resolve_group(group, &gid))
2463                 err_return(errno);
2464 
2465         if (chown(ucv_string_get(path), uid, gid) == -1)
2466                 err_return(errno);
2467 
2468         return ucv_boolean_new(true);
2469 }
2470 
2471 /**
2472  * Renames or moves a file or directory.
2473  *
2474  * Returns `true` if the rename operation was successful.
2475  *
2476  * Returns `null` if an error occurred.
2477  *
2478  * @function module:fs#rename
2479  *
2480  * @param {string} oldPath
2481  * The current path of the file or directory.
2482  *
2483  * @param {string} newPath
2484  * The new path of the file or directory.
2485  *
2486  * @returns {?boolean}
2487  *
2488  * @example
2489  * // Rename a file
2490  * rename('old-name.txt', 'new-name.txt');
2491  */
2492 static uc_value_t *
2493 uc_fs_rename(uc_vm_t *vm, size_t nargs)
2494 {
2495         uc_value_t *oldpath = uc_fn_arg(0);
2496         uc_value_t *newpath = uc_fn_arg(1);
2497 
2498         if (ucv_type(oldpath) != UC_STRING ||
2499             ucv_type(newpath) != UC_STRING)
2500                 err_return(EINVAL);
2501 
2502         if (rename(ucv_string_get(oldpath), ucv_string_get(newpath)))
2503                 err_return(errno);
2504 
2505         return ucv_boolean_new(true);
2506 }
2507 
2508 /**
2509  * Takes an arbitrary number of glob patterns and
2510  * resolves matching files for each one. In case of multiple patterns,
2511  * no efforts are made to remove duplicates or to globally sort the combined
2512  * match list. The list of matches for each individual pattern is sorted.
2513  * Returns an array containing all matched file paths.
2514  *
2515  * @function module:fs#glob
2516  *
2517  * @param {...Arguments} pattern
2518  *
2519  * @returns {?string[]}
2520  *
2521  * @example
2522  *
2523  * import { chdir, glob } from 'fs';
2524  * chdir('/etc/ssl/certs/');
2525  * for (let cert in glob('*.crt', '*.pem')) {
2526  *      if (cert != null)
2527  *              print(cert, '\n');
2528  * }
2529  * // ACCVRAIZ1.crt
2530  * // AC_RAIZ_FNMT-RCM.crt
2531  * // AC_RAIZ_FNMT-RCM_SERVIDORES_SEGUROS.crt
2532  * // ...
2533  */
2534 static uc_value_t *
2535 uc_fs_glob(uc_vm_t *vm, size_t nargs)
2536 {
2537         uc_value_t *pat, *arr;
2538         glob_t gl = { 0 };
2539         size_t i;
2540 
2541         for (i = 0; i < nargs; i++) {
2542                 pat = uc_fn_arg(i);
2543 
2544                 if (ucv_type(pat) != UC_STRING) {
2545                         globfree(&gl);
2546                         err_return(EINVAL);
2547                 }
2548 
2549                 glob(ucv_string_get(pat), i ? GLOB_APPEND : 0, NULL, &gl);
2550         }
2551 
2552         arr = ucv_array_new(vm);
2553 
2554         for (i = 0; i < gl.gl_pathc; i++)
2555                 ucv_array_push(arr, ucv_string_new(gl.gl_pathv[i]));
2556 
2557         globfree(&gl);
2558 
2559         return arr;
2560 }
2561 
2562 /**
2563  * Retrieves the directory name of a path.
2564  *
2565  * Returns the directory name component of the specified path.
2566  *
2567  * Returns `null` if the path argument is not a string.
2568  *
2569  * @function module:fs#dirname
2570  *
2571  * @param {string} path
2572  * The path to extract the directory name from.
2573  *
2574  * @returns {?string}
2575  *
2576  * @example
2577  * // Get the directory name of a path
2578  * const directoryName = dirname('/path/to/file.txt');
2579  */
2580 static uc_value_t *
2581 uc_fs_dirname(uc_vm_t *vm, size_t nargs)
2582 {
2583         uc_value_t *path = uc_fn_arg(0);
2584         size_t i;
2585         char *s;
2586 
2587         if (ucv_type(path) != UC_STRING)
2588                 err_return(EINVAL);
2589 
2590         i = ucv_string_length(path);
2591         s = ucv_string_get(path);
2592 
2593         if (i == 0)
2594                 return ucv_string_new(".");
2595 
2596         for (i--; s[i] == '/'; i--)
2597                 if (i == 0)
2598                         return ucv_string_new("/");
2599 
2600         for (; s[i] != '/'; i--)
2601                 if (i == 0)
2602                         return ucv_string_new(".");
2603 
2604         for (; s[i] == '/'; i--)
2605                 if (i == 0)
2606                         return ucv_string_new("/");
2607 
2608         return ucv_string_new_length(s, i + 1);
2609 }
2610 
2611 /**
2612  * Retrieves the base name of a path.
2613  *
2614  * Returns the base name component of the specified path.
2615  *
2616  * Returns `null` if the path argument is not a string.
2617  *
2618  * @function module:fs#basename
2619  *
2620  * @param {string} path
2621  * The path to extract the base name from.
2622  *
2623  * @returns {?string}
2624  *
2625  * @example
2626  * // Get the base name of a path
2627  * const baseName = basename('/path/to/file.txt');
2628  */
2629 static uc_value_t *
2630 uc_fs_basename(uc_vm_t *vm, size_t nargs)
2631 {
2632         uc_value_t *path = uc_fn_arg(0);
2633         size_t i, len, skip;
2634         char *s;
2635 
2636         if (ucv_type(path) != UC_STRING)
2637                 err_return(EINVAL);
2638 
2639         len = ucv_string_length(path);
2640         s = ucv_string_get(path);
2641 
2642         if (len == 0)
2643                 return ucv_string_new(".");
2644 
2645         for (i = len - 1, skip = 0; i > 0 && s[i] == '/'; i--, skip++)
2646                 ;
2647 
2648         for (; i > 0 && s[i - 1] != '/'; i--)
2649                 ;
2650 
2651         return ucv_string_new_length(s + i, len - i - skip);
2652 }
2653 
2654 static int
2655 uc_fs_lsdir_sort_fn(const void *k1, const void *k2)
2656 {
2657         uc_value_t * const *v1 = k1;
2658         uc_value_t * const *v2 = k2;
2659 
2660         return strcmp(ucv_string_get(*v1), ucv_string_get(*v2));
2661 }
2662 
2663 /**
2664  * Lists the content of a directory.
2665  *
2666  * Returns a sorted array of the names of files and directories in the specified
2667  * directory.
2668  *
2669  * Returns `null` if an error occurred, e.g. if the specified directory cannot
2670  * be opened.
2671  *
2672  * @function module:fs#lsdir
2673  *
2674  * @param {string} path
2675  * The path to the directory.
2676  *
2677  * @returns {?string[]}
2678  *
2679  * @example
2680  * // List the content of a directory
2681  * const fileList = lsdir('/path/to/directory');
2682  */
2683 static uc_value_t *
2684 uc_fs_lsdir(uc_vm_t *vm, size_t nargs)
2685 {
2686         uc_value_t *path = uc_fn_arg(0);
2687         uc_value_t *pat = uc_fn_arg(1);
2688         uc_value_t *res = NULL;
2689         uc_regexp_t *reg;
2690         struct dirent *e;
2691         DIR *d;
2692 
2693         if (ucv_type(path) != UC_STRING)
2694                 err_return(EINVAL);
2695 
2696         switch (ucv_type(pat)) {
2697         case UC_NULL:
2698         case UC_STRING:
2699         case UC_REGEXP:
2700                 break;
2701 
2702         default:
2703                 err_return(EINVAL);
2704         }
2705 
2706         d = opendir(ucv_string_get(path));
2707 
2708         if (!d)
2709                 err_return(errno);
2710 
2711         res = ucv_array_new(vm);
2712 
2713         while ((e = readdir(d)) != NULL) {
2714                 if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
2715                         continue;
2716 
2717                 if (ucv_type(pat) == UC_REGEXP) {
2718                         reg = (uc_regexp_t *)pat;
2719 
2720                         if (regexec(&reg->regexp, e->d_name, 0, NULL, 0) == REG_NOMATCH)
2721                                 continue;
2722                 }
2723                 else if (ucv_type(pat) == UC_STRING) {
2724                         if (fnmatch(ucv_string_get(pat), e->d_name, 0) == FNM_NOMATCH)
2725                                 continue;
2726                 }
2727 
2728                 ucv_array_push(res, ucv_string_new(e->d_name));
2729         }
2730 
2731         closedir(d);
2732 
2733         ucv_array_sort(res, uc_fs_lsdir_sort_fn);
2734 
2735         return res;
2736 }
2737 
2738 /**
2739  * Creates a unique, ephemeral temporary file.
2740  *
2741  * Creates a new temporary file, opens it in read and write mode, unlinks it and
2742  * returns a file handle object referring to the yet open but deleted file.
2743  *
2744  * Upon closing the handle, the associated file will automatically vanish from
2745  * the system.
2746  *
2747  * The optional path template argument may be used to override the path and name
2748  * chosen for the temporary file. If the path template contains no path element,
2749  * `/tmp/` is prepended, if it does not end with `XXXXXX`, then  * `.XXXXXX` is
2750  * appended to it. The `XXXXXX` sequence is replaced with a random value
2751  * ensuring uniqueness of the temporary file name.
2752  *
2753  * Returns a file handle object referring to the ephemeral file on success.
2754  *
2755  * Returns `null` if an error occurred, e.g. on insufficient permissions or
2756  * inaccessible directory.
2757  *
2758  * @function module:fs#mkstemp
2759  *
2760  * @param {string} [template="/tmp/XXXXXX"]
2761  * The path template to use when forming the temporary file name.
2762  *
2763  * @returns {?module:fs.file}
2764  *
2765  * @example
2766  * // Create a unique temporary file in the current working directory
2767  * const tempFile = mkstemp('./data-XXXXXX');
2768  */
2769 static uc_value_t *
2770 uc_fs_mkstemp(uc_vm_t *vm, size_t nargs)
2771 {
2772         uc_value_t *template = uc_fn_arg(0);
2773         bool ends_with_template = false;
2774         char *path, *t;
2775         FILE *fp;
2776         size_t l;
2777         int fd;
2778 
2779         if (template && ucv_type(template) != UC_STRING)
2780                 err_return(EINVAL);
2781 
2782         t = ucv_string_get(template);
2783         l = ucv_string_length(template);
2784 
2785         ends_with_template = (l >= 6 && strcmp(&t[l - 6], "XXXXXX") == 0);
2786 
2787         if (t && strchr(t, '/')) {
2788                 if (ends_with_template)
2789                         xasprintf(&path, "%s", t);
2790                 else
2791                         xasprintf(&path, "%s.XXXXXX", t);
2792         }
2793         else if (t) {
2794                 if (ends_with_template)
2795                         xasprintf(&path, "/tmp/%s", t);
2796                 else
2797                         xasprintf(&path, "/tmp/%s.XXXXXX", t);
2798         }
2799         else {
2800                 xasprintf(&path, "/tmp/XXXXXX");
2801         }
2802 
2803         do {
2804                 fd = mkstemp(path);
2805         }
2806         while (fd == -1 && errno == EINTR);
2807 
2808         if (fd == -1) {
2809                 free(path);
2810                 err_return(errno);
2811         }
2812 
2813         unlink(path);
2814         free(path);
2815 
2816         fp = fdopen(fd, "r+");
2817 
2818         if (!fp) {
2819                 close(fd);
2820                 err_return(errno);
2821         }
2822 
2823         return ucv_resource_create(vm, "fs.file", fp);
2824 }
2825 
2826 /**
2827  * Creates a unique temporary directory based on the given template.
2828  *
2829  * If the template argument is given and contains a relative path, the created
2830  * directory will be placed relative to the current working directory.
2831  *
2832  * If the template argument is given and contains an absolute path, the created
2833  * directory will be placed relative to the given directory.
2834  *
2835  * If the template argument is given but does not contain a directory separator,
2836  * the directory will be placed in `/tmp/`.
2837  *
2838  * If no template argument is given, the default `/tmp/XXXXXX` is used.
2839  *
2840  * The template argument must end with six consecutive X characters (`XXXXXX`),
2841  * which will be replaced with a random string to create the unique directory name.
2842  * If the template does not end with `XXXXXX`, it will be automatically appended.
2843  *
2844  * Returns a string containing the path of the created directory on success.
2845  *
2846  * Returns `null` if an error occurred, e.g. on insufficient permissions or
2847  * inaccessible directory.
2848  *
2849  * @function module:fs#mkdtemp
2850  *
2851  * @param {string} [template="/tmp/XXXXXX"]
2852  * The path template to use when forming the temporary directory name.
2853  *
2854  * @returns {?string}
2855  *
2856  * @example
2857  * // Create a unique temporary directory in the current working directory
2858  * const tempDir = mkdtemp('./data-XXXXXX');
2859  */
2860 static uc_value_t *
2861 uc_fs_mkdtemp(uc_vm_t *vm, size_t nargs)
2862 {
2863         uc_value_t *template = uc_fn_arg(0);
2864         bool ends_with_template = false;
2865         char *path, *t, *result;
2866         uc_value_t *rv;
2867         size_t l;
2868 
2869         if (template && ucv_type(template) != UC_STRING)
2870                 err_return(EINVAL);
2871 
2872         t = ucv_string_get(template);
2873         l = ucv_string_length(template);
2874 
2875         ends_with_template = (l >= 6 && strcmp(&t[l - 6], "XXXXXX") == 0);
2876 
2877         if (t && strchr(t, '/')) {
2878                 if (ends_with_template)
2879                         xasprintf(&path, "%s", t);
2880                 else
2881                         xasprintf(&path, "%s.XXXXXX", t);
2882         }
2883         else if (t) {
2884                 if (ends_with_template)
2885                         xasprintf(&path, "/tmp/%s", t);
2886                 else
2887                         xasprintf(&path, "/tmp/%s.XXXXXX", t);
2888         }
2889         else {
2890                 xasprintf(&path, "/tmp/XXXXXX");
2891         }
2892 
2893         result = mkdtemp(path);
2894 
2895         if (!result) {
2896                 free(path);
2897                 err_return(errno);
2898         }
2899 
2900         rv = ucv_string_new(result);
2901         free(path);
2902 
2903         return rv;
2904 }
2905 
2906 /**
2907  * Checks the accessibility of a file or directory.
2908  *
2909  * The optional modes argument specifies the access modes which should be
2910  * checked. A file is only considered accessible if all access modes specified
2911  * in the modes argument are possible.
2912  *
2913  * The following modes are recognized:
2914  *
2915  * | Mode | Description                           |
2916  * |------|---------------------------------------|
2917  * | "r"  | Tests whether the file is readable.   |
2918  * | "w"  | Tests whether the file is writable.   |
2919  * | "x"  | Tests whether the file is executable. |
2920  * | "f"  | Tests whether the file exists.        |
2921  *
2922  * Returns `true` if the given path is accessible or `false` when it is not.
2923  *
2924  * Returns `null` if an error occurred, e.g. due to inaccessible intermediate
2925  * path components, invalid path arguments etc.
2926  *
2927  * @function module:fs#access
2928  *
2929  * @param {string} path
2930  * The path to the file or directory.
2931  *
2932  * @param {number} [mode="f"]
2933  * Optional access mode.
2934  *
2935  * @returns {?boolean}
2936  *
2937  * @example
2938  * // Check file read and write accessibility
2939  * const isAccessible = access('path/to/file', 'rw');
2940  *
2941  * // Check execute permissions
2942  * const mayExecute = access('/usr/bin/example', 'x');
2943  */
2944 static uc_value_t *
2945 uc_fs_access(uc_vm_t *vm, size_t nargs)
2946 {
2947         uc_value_t *path = uc_fn_arg(0);
2948         uc_value_t *test = uc_fn_arg(1);
2949         int mode = F_OK;
2950         char *p;
2951 
2952         if (ucv_type(path) != UC_STRING)
2953                 err_return(EINVAL);
2954 
2955         if (test && ucv_type(test) != UC_STRING)
2956                 err_return(EINVAL);
2957 
2958         for (p = ucv_string_get(test); p && *p; p++) {
2959                 switch (*p) {
2960                 case 'r':
2961                         mode |= R_OK;
2962                         break;
2963 
2964                 case 'w':
2965                         mode |= W_OK;
2966                         break;
2967 
2968                 case 'x':
2969                         mode |= X_OK;
2970                         break;
2971 
2972                 case 'f':
2973                         mode |= F_OK;
2974                         break;
2975 
2976                 default:
2977                         err_return(EINVAL);
2978                 }
2979         }
2980 
2981         if (access(ucv_string_get(path), mode) == -1)
2982                 err_return(errno);
2983 
2984         return ucv_boolean_new(true);
2985 }
2986 
2987 /**
2988  * Reads the content of a file, optionally limited to the given amount of bytes.
2989  *
2990  * Returns a string containing the file contents.
2991  *
2992  * Returns `null` if an error occurred, e.g. due to insufficient permissions.
2993  *
2994  * @function module:fs#readfile
2995  *
2996  * @param {string} path
2997  * The path to the file.
2998  *
2999  * @param {number} [limit]
3000  * Number of bytes to limit the result to. When omitted, the entire content is
3001  * returned.
3002  *
3003  * @returns {?string}
3004  *
3005  * @example
3006  * // Read first 100 bytes of content
3007  * const content = readfile('path/to/file', 100);
3008  *
3009  * // Read entire file content
3010  * const content = readfile('path/to/file');
3011  */
3012 static uc_value_t *
3013 uc_fs_readfile(uc_vm_t *vm, size_t nargs)
3014 {
3015         uc_value_t *path = uc_fn_arg(0);
3016         uc_value_t *size = uc_fn_arg(1);
3017         uc_value_t *res = NULL;
3018         uc_stringbuf_t *buf;
3019         ssize_t limit = -1;
3020         size_t rlen, blen;
3021         FILE *fp;
3022 
3023         if (ucv_type(path) != UC_STRING)
3024                 err_return(EINVAL);
3025 
3026         if (size) {
3027                 if (ucv_type(size) != UC_INTEGER)
3028                         err_return(EINVAL);
3029 
3030                 limit = ucv_int64_get(size);
3031         }
3032 
3033         fp = fopen(ucv_string_get(path), "r");
3034 
3035         if (!fp)
3036                 err_return(errno);
3037 
3038         buf = ucv_stringbuf_new();
3039 
3040         if (limit > -1 && limit < BUFSIZ)
3041                 setvbuf(fp, NULL, _IONBF, 0);
3042 
3043         while (limit != 0) {
3044                 blen = 1024;
3045 
3046                 if (limit > 0 && blen > (size_t)limit)
3047                         blen = (size_t)limit;
3048 
3049                 printbuf_memset(buf, printbuf_length(buf) + blen - 1, 0, 1);
3050 
3051                 buf->bpos -= blen;
3052                 rlen = fread(buf->buf + buf->bpos, 1, blen, fp);
3053                 buf->bpos += rlen;
3054 
3055                 if (rlen < blen)
3056                         break;
3057 
3058                 if (limit > 0)
3059                         limit -= rlen;
3060         }
3061 
3062         if (ferror(fp)) {
3063                 fclose(fp);
3064                 printbuf_free(buf);
3065                 err_return(errno);
3066         }
3067 
3068         fclose(fp);
3069 
3070         /* add sentinel null byte but don't count it towards the string length */
3071         printbuf_memappend_fast(buf, "\0", 1);
3072         res = ucv_stringbuf_finish(buf);
3073         ((uc_string_t *)res)->length--;
3074 
3075         return res;
3076 }
3077 
3078 /**
3079  * Writes the given data to a file, optionally truncated to the given amount
3080  * of bytes.
3081  *
3082  * In case the given data is not a string, it is converted to a string before
3083  * being written into the file. String values are written as-is, integer and
3084  * double values are written in decimal notation, boolean values are written as
3085  * `true` or `false` while arrays and objects are converted to their JSON
3086  * representation before being written into the file. The `null` value is
3087  * represented by an empty string so `writefile(…, null)` would write an empty
3088  * file. Resource values are written in the form `<type address>`, e.g.
3089  * `<fs.file 0x7f60f0981760>`.
3090  *
3091  * If resource, array or object values contain a `tostring()` function in their
3092  * prototypes, then this function is invoked to obtain an alternative string
3093  * representation of the value.
3094  *
3095  * If a file already exists at the given path, it is truncated. If no file
3096  * exists, it is created with default permissions 0o666 masked by the currently
3097  * effective umask.
3098  *
3099  * Returns the number of bytes written.
3100  *
3101  * Returns `null` if an error occurred, e.g. due to insufficient permissions.
3102  *
3103  * @function module:fs#writefile
3104  *
3105  * @param {string} path
3106  * The path to the file.
3107  *
3108  * @param {*} data
3109  * The data to be written.
3110  *
3111  * @param {number} [limit]
3112  * Truncates the amount of data to be written to the specified amount of bytes.
3113  * When omitted, the entire content is written.
3114  *
3115  * @returns {?number}
3116  *
3117  * @example
3118  * // Write string to a file
3119  * const bytesWritten = writefile('path/to/file', 'Hello, World!');
3120  *
3121  * // Write object as JSON to a file and limit to 1024 bytes at most
3122  * const obj = { foo: "Hello world", bar: true, baz: 123 };
3123  * const bytesWritten = writefile('debug.txt', obj, 1024);
3124  */
3125 static uc_value_t *
3126 uc_fs_writefile(uc_vm_t *vm, size_t nargs)
3127 {
3128         uc_value_t *path = uc_fn_arg(0);
3129         uc_value_t *data = uc_fn_arg(1);
3130         uc_value_t *size = uc_fn_arg(2);
3131         uc_stringbuf_t *buf = NULL;
3132         ssize_t limit = -1;
3133         size_t wlen = 0;
3134         int err = 0;
3135         FILE *fp;
3136 
3137         if (ucv_type(path) != UC_STRING)
3138                 err_return(EINVAL);
3139 
3140         if (size) {
3141                 if (ucv_type(size) != UC_INTEGER)
3142                         err_return(EINVAL);
3143 
3144                 limit = ucv_int64_get(size);
3145         }
3146 
3147         fp = fopen(ucv_string_get(path), "w");
3148 
3149         if (!fp)
3150                 err_return(errno);
3151 
3152         if (data && ucv_type(data) != UC_STRING) {
3153                 buf = xprintbuf_new();
3154                 ucv_to_stringbuf_formatted(vm, buf, data, 0, '\0', 0);
3155 
3156                 if (limit < 0 || limit > printbuf_length(buf))
3157                         limit = printbuf_length(buf);
3158 
3159                 wlen = fwrite(buf->buf, 1, limit, fp);
3160 
3161                 if (wlen < (size_t)limit)
3162                         err = errno;
3163 
3164                 printbuf_free(buf);
3165         }
3166         else if (data) {
3167                 if (limit < 0 || (size_t)limit > ucv_string_length(data))
3168                         limit = ucv_string_length(data);
3169 
3170                 wlen = fwrite(ucv_string_get(data), 1, limit, fp);
3171 
3172                 if (wlen < (size_t)limit)
3173                         err = errno;
3174         }
3175 
3176         fclose(fp);
3177 
3178         if (err)
3179                 err_return(err);
3180 
3181         return ucv_uint64_new(wlen);
3182 }
3183 
3184 /**
3185  * Resolves the absolute path of a file or directory.
3186  *
3187  * Returns a string containing the resolved path.
3188  *
3189  * Returns `null` if an error occurred, e.g. due to insufficient permissions.
3190  *
3191  * @function module:fs#realpath
3192  *
3193  * @param {string} path
3194  * The path to the file or directory.
3195  *
3196  * @returns {?string}
3197  *
3198  * @example
3199  * // Resolve the absolute path of a file
3200  * const absolutePath = realpath('path/to/file', 'utf8');
3201  */
3202 static uc_value_t *
3203 uc_fs_realpath(uc_vm_t *vm, size_t nargs)
3204 {
3205         uc_value_t *path = uc_fn_arg(0), *rv;
3206         char *resolved;
3207 
3208         if (ucv_type(path) != UC_STRING)
3209                 err_return(EINVAL);
3210 
3211         resolved = realpath(ucv_string_get(path), NULL);
3212 
3213         if (!resolved)
3214                 err_return(errno);
3215 
3216         rv = ucv_string_new(resolved);
3217 
3218         free(resolved);
3219 
3220         return rv;
3221 }
3222 
3223 /**
3224  * Creates a pipe and returns file handle objects associated with the read- and
3225  * write end of the pipe respectively.
3226  *
3227  * Returns a two element array containing both a file handle object open in read
3228  * mode referring to the read end of the pipe and a file handle object open in
3229  * write mode referring to the write end of the pipe.
3230  *
3231  * Returns `null` if an error occurred.
3232  *
3233  * @function module:fs#pipe
3234  *
3235  * @returns {?module:fs.file[]}
3236  *
3237  * @example
3238  * // Create a pipe
3239  * const pipeHandles = pipe();
3240  * pipeHandles[1].write("Hello world\n");
3241  * print(pipeHandles[0].read("line"));
3242  */
3243 static uc_value_t *
3244 uc_fs_pipe(uc_vm_t *vm, size_t nargs)
3245 {
3246         int pfds[2], err;
3247         FILE *rfp, *wfp;
3248         uc_value_t *rv;
3249 
3250         if (pipe(pfds) == -1)
3251                 err_return(errno);
3252 
3253         rfp = fdopen(pfds[0], "r");
3254 
3255         if (!rfp) {
3256                 err = errno;
3257                 close(pfds[0]);
3258                 close(pfds[1]);
3259                 err_return(err);
3260         }
3261 
3262         wfp = fdopen(pfds[1], "w");
3263 
3264         if (!wfp) {
3265                 err = errno;
3266                 fclose(rfp);
3267                 close(pfds[1]);
3268                 err_return(err);
3269         }
3270 
3271         rv = ucv_array_new_length(vm, 2);
3272 
3273         ucv_array_push(rv, ucv_resource_create(vm, "fs.file", rfp));
3274         ucv_array_push(rv, ucv_resource_create(vm, "fs.file", wfp));
3275 
3276         return rv;
3277 }
3278 
3279 
3280 static const uc_function_list_t proc_fns[] = {
3281         { "read",               uc_fs_pread },
3282         { "write",              uc_fs_pwrite },
3283         { "close",              uc_fs_pclose },
3284         { "flush",              uc_fs_pflush },
3285         { "fileno",             uc_fs_pfileno },
3286         { "error",              uc_fs_error },
3287 };
3288 
3289 static const uc_function_list_t file_fns[] = {
3290         { "read",               uc_fs_read },
3291         { "write",              uc_fs_write },
3292         { "seek",               uc_fs_seek },
3293         { "tell",               uc_fs_tell },
3294         { "close",              uc_fs_close },
3295         { "flush",              uc_fs_flush },
3296         { "fileno",             uc_fs_fileno },
3297         { "error",              uc_fs_error },
3298         { "isatty",             uc_fs_isatty },
3299         { "truncate",   uc_fs_truncate },
3300         { "lock",               uc_fs_lock },
3301 #if defined(__linux__) || defined(__APPLE__)
3302         { "ioctl",              uc_fs_ioctl },
3303 #endif
3304 };
3305 
3306 static const uc_function_list_t dir_fns[] = {
3307         { "fileno",             uc_fs_dfileno },
3308         { "read",               uc_fs_readdir },
3309         { "seek",               uc_fs_seekdir },
3310         { "tell",               uc_fs_telldir },
3311         { "close",              uc_fs_closedir },
3312         { "error",              uc_fs_error },
3313 };
3314 
3315 static const uc_function_list_t global_fns[] = {
3316         { "error",              uc_fs_error },
3317         { "open",               uc_fs_open },
3318         { "fdopen",             uc_fs_fdopen },
3319         { "dup2",               uc_fs_dup2 },
3320         { "opendir",    uc_fs_opendir },
3321         { "popen",              uc_fs_popen },
3322         { "readlink",   uc_fs_readlink },
3323         { "stat",               uc_fs_stat },
3324         { "statvfs",    uc_fs_statvfs },
3325         { "lstat",              uc_fs_lstat },
3326         { "mkdir",              uc_fs_mkdir },
3327         { "rmdir",              uc_fs_rmdir },
3328         { "symlink",    uc_fs_symlink },
3329         { "unlink",             uc_fs_unlink },
3330         { "getcwd",             uc_fs_getcwd },
3331         { "chdir",              uc_fs_chdir },
3332         { "chmod",              uc_fs_chmod },
3333         { "chown",              uc_fs_chown },
3334         { "rename",             uc_fs_rename },
3335         { "glob",               uc_fs_glob },
3336         { "dirname",    uc_fs_dirname },
3337         { "basename",   uc_fs_basename },
3338         { "lsdir",              uc_fs_lsdir },
3339         { "mkstemp",    uc_fs_mkstemp },
3340         { "mkdtemp",    uc_fs_mkdtemp },
3341         { "access",             uc_fs_access },
3342         { "readfile",   uc_fs_readfile },
3343         { "writefile",  uc_fs_writefile },
3344         { "realpath",   uc_fs_realpath },
3345         { "pipe",               uc_fs_pipe },
3346 };
3347 
3348 
3349 static void close_proc(void *ud)
3350 {
3351         uc_proc_t *proc = ud;
3352 
3353         if (!proc || !proc->fp)
3354                 return;
3355 
3356         fclose(proc->fp);
3357         while (waitpid(proc->pid, NULL, 0) == -1 && errno == EINTR);
3358 }
3359 
3360 static void close_file(void *ud)
3361 {
3362         FILE *fp = ud;
3363         int n;
3364 
3365         n = fp ? fileno(fp) : -1;
3366 
3367         if (n > 2)
3368                 fclose(fp);
3369 }
3370 
3371 static void close_dir(void *ud)
3372 {
3373         DIR *dp = ud;
3374 
3375         if (dp)
3376                 closedir(dp);
3377 }
3378 
3379 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
3380 {
3381         uc_function_list_register(scope, global_fns);
3382         #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
3383 
3384         /**
3385          * @typedef
3386          * @name module:fs.ST_FLAGS
3387          * @description Bitmask flags used at volume mount time (¹ - Linux only).
3388          * @property {number} ST_MANDLOCK - Mandatory locking.¹
3389          * @property {number} ST_NOATIME - Do not update access times.¹
3390          * @property {number} ST_NODEV - Do not allow device files.¹
3391          * @property {number} ST_NODIRATIME - Do not update directory access times.¹
3392          * @property {number} ST_NOEXEC - Do not allow execution of binaries.¹
3393          * @property {number} ST_NOSUID - Do not allow set-user-identifier or set-group-identifier bits.
3394          * @property {number} ST_RDONLY - Read-only filesystem.
3395          * @property {number} ST_RELATIME - Update access times relative to modification time.¹
3396          * @property {number} ST_SYNCHRONOUS - Synchronous writes.¹
3397          * @property {number} ST_NOSYMFOLLOW - Do not follow symbolic links.¹
3398          */
3399         #ifdef ST_MANDLOCK
3400         ADD_CONST(ST_MANDLOCK);
3401         #endif
3402         #ifdef ST_NOATIME
3403         ADD_CONST(ST_NOATIME);
3404         #endif
3405         #ifdef ST_NODEV
3406         ADD_CONST(ST_NODEV);
3407         #endif
3408         #ifdef ST_NODIRATIME
3409         ADD_CONST(ST_NODIRATIME);
3410         #endif
3411         #ifdef ST_NOEXEC
3412         ADD_CONST(ST_NOEXEC);
3413         #endif
3414         #ifdef ST_NOSUID
3415         ADD_CONST(ST_NOSUID);
3416         #endif
3417         #ifdef ST_RDONLY
3418         ADD_CONST(ST_RDONLY);
3419         #endif
3420         #ifdef ST_RELATIME
3421         ADD_CONST(ST_RELATIME);
3422         #endif
3423         #ifdef ST_SYNCHRONOUS
3424         ADD_CONST(ST_SYNCHRONOUS);
3425         #endif
3426         #ifdef ST_NOSYMFOLLOW
3427         ADD_CONST(ST_NOSYMFOLLOW);
3428         #endif
3429 
3430         uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
3431         uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
3432 
3433         uc_resource_type_t *file_type = uc_type_declare(vm, "fs.file", file_fns, close_file);
3434 
3435         ucv_object_add(scope, "stdin", uc_resource_new(file_type, stdin));
3436         ucv_object_add(scope, "stdout", uc_resource_new(file_type, stdout));
3437         ucv_object_add(scope, "stderr", uc_resource_new(file_type, stderr));
3438 
3439 #if defined(HAS_IOCTL) || defined(HAS_MAC_IOCTL)
3440         ADD_CONST(IOC_DIR_NONE);
3441         ADD_CONST(IOC_DIR_READ);
3442         ADD_CONST(IOC_DIR_WRITE);
3443         ADD_CONST(IOC_DIR_RW);
3444 #endif
3445 }
3446 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt