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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt