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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt