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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt