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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt