• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/fs.c

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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt