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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt