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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt