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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt