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

Sources/ucode/lib/io.c

  1 /*
  2  * Copyright (C) 2025 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  * # I/O Operations
 19  *
 20  * The `io` module provides object-oriented access to UNIX file descriptors.
 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 { open, O_RDWR } from 'io';
 28  *
 29  *   let handle = open('/tmp/test.txt', O_RDWR);
 30  *   handle.write('Hello World\n');
 31  *   handle.close();
 32  *   ```
 33  *
 34  * Alternatively, the module namespace can be imported
 35  * using a wildcard import statement:
 36  *
 37  *   ```
 38  *   import * as io from 'io';
 39  *
 40  *   let handle = io.open('/tmp/test.txt', io.O_RDWR);
 41  *   handle.write('Hello World\n');
 42  *   handle.close();
 43  *   ```
 44  *
 45  * Additionally, the io module namespace may also be imported by invoking
 46  * the `ucode` interpreter with the `-lio` switch.
 47  *
 48  * @module io
 49  */
 50 
 51 #include <stdio.h>
 52 #include <errno.h>
 53 #include <string.h>
 54 #include <unistd.h>
 55 #include <fcntl.h>
 56 #include <sys/types.h>
 57 #include <sys/stat.h>
 58 #include <limits.h>
 59 
 60 #if defined(__linux__)
 61 #define HAS_IOCTL
 62 #endif
 63 
 64 #ifdef HAS_IOCTL
 65 #include <sys/ioctl.h>
 66 
 67 #define IOC_DIR_NONE    (_IOC_NONE)
 68 #define IOC_DIR_READ    (_IOC_READ)
 69 #define IOC_DIR_WRITE   (_IOC_WRITE)
 70 #define IOC_DIR_RW              (_IOC_READ | _IOC_WRITE)
 71 
 72 #endif
 73 
 74 #include "ucode/module.h"
 75 #include "ucode/platform.h"
 76 
 77 #define err_return(err) do { \
 78         uc_vm_registry_set(vm, "io.last_error", ucv_int64_new(err)); \
 79         return NULL; \
 80 } while(0)
 81 
 82 typedef struct {
 83         int fd;
 84         bool close_on_free;
 85 } uc_io_handle_t;
 86 
 87 static bool
 88 get_fd_from_value(uc_vm_t *vm, uc_value_t *val, int *fd)
 89 {
 90         uc_io_handle_t *handle;
 91         uc_value_t *fn;
 92         int64_t n;
 93 
 94         /* Check if it's an io.handle resource */
 95         handle = ucv_resource_data(val, "io.handle");
 96 
 97         if (handle) {
 98                 if (handle->fd < 0)
 99                         err_return(EBADF);
100 
101                 *fd = handle->fd;
102 
103                 return true;
104         }
105 
106         /* Try calling fileno() method */
107         fn = ucv_property_get(val, "fileno");
108         errno = 0;
109 
110         if (ucv_is_callable(fn)) {
111                 uc_vm_stack_push(vm, ucv_get(val));
112                 uc_vm_stack_push(vm, ucv_get(fn));
113 
114                 if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
115                         err_return(EBADF);
116 
117                 val = uc_vm_stack_pop(vm);
118                 n = ucv_int64_get(val);
119                 ucv_put(val);
120         }
121         else {
122                 n = ucv_int64_get(val);
123         }
124 
125         if (errno || n < 0 || n > (int64_t)INT_MAX)
126                 err_return(errno ? errno : EBADF);
127 
128         *fd = n;
129 
130         return true;
131 }
132 
133 /**
134  * Query error information.
135  *
136  * Returns a string containing a description of the last occurred error or
137  * `null` if there is no error information.
138  *
139  * @function module:io#error
140  *
141  * @returns {?string}
142  *
143  * @example
144  * // Trigger an error
145  * io.open('/path/does/not/exist');
146  *
147  * // Print error (should yield "No such file or directory")
148  * print(io.error(), "\n");
149  */
150 static uc_value_t *
151 uc_io_error(uc_vm_t *vm, size_t nargs)
152 {
153         int last_error = ucv_int64_get(uc_vm_registry_get(vm, "io.last_error"));
154 
155         if (last_error == 0)
156                 return NULL;
157 
158         uc_vm_registry_set(vm, "io.last_error", ucv_int64_new(0));
159 
160         return ucv_string_new(strerror(last_error));
161 }
162 
163 /**
164  * Represents a handle for interacting with a file descriptor.
165  *
166  * @class module:io.handle
167  * @hideconstructor
168  *
169  * @borrows module:io#error as module:io.handle#error
170  *
171  * @see {@link module:io#new|new()}
172  * @see {@link module:io#open|open()}
173  * @see {@link module:io#from|from()}
174  *
175  * @example
176  *
177  * const handle = io.open(…);
178  *
179  * handle.read(…);
180  * handle.write(…);
181  *
182  * handle.seek(…);
183  * handle.tell();
184  *
185  * handle.fileno();
186  *
187  * handle.close();
188  *
189  * handle.error();
190  */
191 
192 /**
193  * Reads data from the file descriptor.
194  *
195  * Reads up to the specified number of bytes from the file descriptor.
196  *
197  * Returns a string containing the read data.
198  *
199  * Returns an empty string on EOF.
200  *
201  * Returns `null` if a read error occurred.
202  *
203  * @function module:io.handle#read
204  *
205  * @param {number} length
206  * The maximum number of bytes to read.
207  *
208  * @returns {?string}
209  *
210  * @example
211  * const handle = io.open('/tmp/test.txt', O_RDONLY);
212  * const data = handle.read(1024);
213  */
214 static uc_value_t *
215 uc_io_read(uc_vm_t *vm, size_t nargs)
216 {
217         uc_value_t *limit = uc_fn_arg(0);
218         uc_value_t *rv = NULL;
219         uc_io_handle_t *handle;
220         int fd;
221         int64_t len;
222         ssize_t rlen;
223         char *buf;
224 
225         handle = uc_fn_thisval("io.handle");
226 
227         if (!handle || handle->fd < 0)
228                 err_return(EBADF);
229 
230         fd = handle->fd;
231 
232         if (ucv_type(limit) != UC_INTEGER)
233                 err_return(EINVAL);
234 
235         len = ucv_int64_get(limit);
236 
237         if (len <= 0)
238                 return ucv_string_new_length("", 0);
239 
240         if (len > SSIZE_MAX)
241                 len = SSIZE_MAX;
242 
243         buf = xalloc(len);
244 
245         rlen = read(fd, buf, len);
246 
247         if (rlen < 0) {
248                 free(buf);
249                 err_return(errno);
250         }
251 
252         rv = ucv_string_new_length(buf, rlen);
253         free(buf);
254 
255         return rv;
256 }
257 
258 /**
259  * Writes data to the file descriptor.
260  *
261  * Writes the given data to the file descriptor. Non-string values are
262  * converted to strings before being written.
263  *
264  * Returns the number of bytes written.
265  *
266  * Returns `null` if a write error occurred.
267  *
268  * @function module:io.handle#write
269  *
270  * @param {*} data
271  * The data to write.
272  *
273  * @returns {?number}
274  *
275  * @example
276  * const handle = io.open('/tmp/test.txt', O_WRONLY | O_CREAT);
277  * handle.write('Hello World\n');
278  */
279 static uc_value_t *
280 uc_io_write(uc_vm_t *vm, size_t nargs)
281 {
282         uc_value_t *data = uc_fn_arg(0);
283         uc_io_handle_t *handle;
284         ssize_t wlen;
285         size_t len;
286         char *str;
287         int fd;
288 
289         handle = uc_fn_thisval("io.handle");
290 
291         if (!handle || handle->fd < 0)
292                 err_return(EBADF);
293 
294         fd = handle->fd;
295 
296         if (ucv_type(data) == UC_STRING) {
297                 len = ucv_string_length(data);
298                 wlen = write(fd, ucv_string_get(data), len);
299         }
300         else {
301                 str = ucv_to_jsonstring(vm, data);
302                 len = str ? strlen(str) : 0;
303                 wlen = write(fd, str, len);
304                 free(str);
305         }
306 
307         if (wlen < 0)
308                 err_return(errno);
309 
310         return ucv_int64_new(wlen);
311 }
312 
313 /**
314  * Sets the file descriptor position.
315  *
316  * Sets the file position of the descriptor to the given offset and whence.
317  *
318  * Returns `true` if the position was successfully set.
319  *
320  * Returns `null` if an error occurred.
321  *
322  * @function module:io.handle#seek
323  *
324  * @param {number} [offset=0]
325  * The offset in bytes.
326  *
327  * @param {number} [whence=0]
328  * The position reference.
329  *
330  * | Whence | Description                                                        |
331  * |--------|--------------------------------------------------------------------|
332  * | `0`    | The offset is relative to the start of the file (SEEK_SET).       |
333  * | `1`    | The offset is relative to the current position (SEEK_CUR).        |
334  * | `2`    | The offset is relative to the end of the file (SEEK_END).         |
335  *
336  * @returns {?boolean}
337  *
338  * @example
339  * const handle = io.open('/tmp/test.txt', O_RDONLY);
340  * handle.seek(100, 0);  // Seek to byte 100 from start
341  */
342 static uc_value_t *
343 uc_io_seek(uc_vm_t *vm, size_t nargs)
344 {
345         uc_value_t *ofs = uc_fn_arg(0);
346         uc_value_t *how = uc_fn_arg(1);
347         uc_io_handle_t *handle;
348         int whence;
349         off_t offset;
350         int fd;
351 
352         handle = uc_fn_thisval("io.handle");
353 
354         if (!handle || handle->fd < 0)
355                 err_return(EBADF);
356 
357         fd = handle->fd;
358 
359         if (!ofs)
360                 offset = 0;
361         else if (ucv_type(ofs) != UC_INTEGER)
362                 err_return(EINVAL);
363         else
364                 offset = (off_t)ucv_int64_get(ofs);
365 
366         if (!how)
367                 whence = SEEK_SET;
368         else if (ucv_type(how) != UC_INTEGER)
369                 err_return(EINVAL);
370         else
371                 whence = (int)ucv_int64_get(how);
372 
373         if (lseek(fd, offset, whence) < 0)
374                 err_return(errno);
375 
376         return ucv_boolean_new(true);
377 }
378 
379 /**
380  * Gets the current file descriptor position.
381  *
382  * Returns the current file position as an integer.
383  *
384  * Returns `null` if an error occurred.
385  *
386  * @function module:io.handle#tell
387  *
388  * @returns {?number}
389  *
390  * @example
391  * const handle = io.open('/tmp/test.txt', O_RDONLY);
392  * const pos = handle.tell();
393  */
394 static uc_value_t *
395 uc_io_tell(uc_vm_t *vm, size_t nargs)
396 {
397         uc_io_handle_t *handle;
398         off_t offset;
399         int fd;
400 
401         handle = uc_fn_thisval("io.handle");
402 
403         if (!handle || handle->fd < 0)
404                 err_return(EBADF);
405 
406         fd = handle->fd;
407 
408         offset = lseek(fd, 0, SEEK_CUR);
409 
410         if (offset < 0)
411                 err_return(errno);
412 
413         return ucv_int64_new(offset);
414 }
415 
416 /**
417  * Duplicates the file descriptor.
418  *
419  * Creates a duplicate of the file descriptor using dup(2).
420  *
421  * Returns a new io.handle for the duplicated descriptor.
422  *
423  * Returns `null` if an error occurred.
424  *
425  * @function module:io.handle#dup
426  *
427  * @returns {?module:io.handle}
428  *
429  * @example
430  * const handle = io.open('/tmp/test.txt', O_RDONLY);
431  * const dup_handle = handle.dup();
432  */
433 static uc_value_t *
434 uc_io_dup(uc_vm_t *vm, size_t nargs)
435 {
436         uc_io_handle_t *handle, *new_handle = NULL;
437         uc_value_t *res;
438         int fd, newfd;
439 
440         handle = uc_fn_thisval("io.handle");
441 
442         if (!handle || handle->fd < 0)
443                 err_return(EBADF);
444 
445         fd = handle->fd;
446 
447         newfd = dup(fd);
448 
449         if (newfd < 0)
450                 err_return(errno);
451 
452         res = ucv_resource_create_ex(vm, "io.handle",
453                                      (void **)&new_handle, 0, sizeof(*new_handle));
454 
455         if (!new_handle)
456                 err_return(ENOMEM);
457 
458         new_handle->fd = newfd;
459         new_handle->close_on_free = true;
460 
461         return res;
462 }
463 
464 /**
465  * Duplicates the file descriptor to a specific descriptor number.
466  *
467  * Creates a duplicate of the file descriptor to the specified descriptor
468  * number using dup2(2). If newfd was previously open, it is silently closed.
469  *
470  * Returns `true` on success.
471  *
472  * Returns `null` if an error occurred.
473  *
474  * @function module:io.handle#dup2
475  *
476  * @param {number} newfd
477  * The target file descriptor number.
478  *
479  * @returns {?boolean}
480  *
481  * @example
482  * const handle = io.open('/tmp/test.txt', O_WRONLY);
483  * handle.dup2(2);  // Redirect stderr to the file
484  */
485 static uc_value_t *
486 uc_io_dup2(uc_vm_t *vm, size_t nargs)
487 {
488         uc_value_t *newfd_arg = uc_fn_arg(0);
489         uc_io_handle_t *handle;
490         int fd, newfd;
491 
492         handle = uc_fn_thisval("io.handle");
493 
494         if (!handle || handle->fd < 0)
495                 err_return(EBADF);
496 
497         fd = handle->fd;
498 
499         if (!get_fd_from_value(vm, newfd_arg, &newfd))
500                 return NULL;
501 
502         if (dup2(fd, newfd) < 0)
503                 err_return(errno);
504 
505         return ucv_boolean_new(true);
506 }
507 
508 /**
509  * Gets the file descriptor number.
510  *
511  * Returns the underlying file descriptor number.
512  *
513  * Returns `null` if the handle is closed.
514  *
515  * @function module:io.handle#fileno
516  *
517  * @returns {?number}
518  *
519  * @example
520  * const handle = io.open('/tmp/test.txt', O_RDONLY);
521  * print(handle.fileno(), "\n");
522  */
523 static uc_value_t *
524 uc_io_fileno(uc_vm_t *vm, size_t nargs)
525 {
526         uc_io_handle_t *handle;
527 
528         handle = uc_fn_thisval("io.handle");
529 
530         if (!handle || handle->fd < 0)
531                 err_return(EBADF);
532 
533         return ucv_int64_new(handle->fd);
534 }
535 
536 /**
537  * Performs fcntl() operations on the file descriptor.
538  *
539  * Performs the specified fcntl() command on the file descriptor with an
540  * optional argument.
541  *
542  * Returns the result of the fcntl() call. For F_DUPFD and F_DUPFD_CLOEXEC,
543  * returns a new io.handle wrapping the duplicated descriptor. For other
544  * commands, returns a number (interpretation depends on cmd).
545  *
546  * Returns `null` if an error occurred.
547  *
548  * @function module:io.handle#fcntl
549  *
550  * @param {number} cmd
551  * The fcntl command (e.g., F_GETFL, F_SETFL, F_GETFD, F_SETFD, F_DUPFD).
552  *
553  * @param {number} [arg]
554  * Optional argument for the command.
555  *
556  * @returns {?(number|module:io.handle)}
557  *
558  * @example
559  * const handle = io.open('/tmp/test.txt', O_RDONLY);
560  * const flags = handle.fcntl(F_GETFL);
561  * handle.fcntl(F_SETFL, flags | O_NONBLOCK);
562  * const dup_handle = handle.fcntl(F_DUPFD, 10);  // Returns io.handle
563  */
564 static uc_value_t *
565 uc_io_fcntl(uc_vm_t *vm, size_t nargs)
566 {
567         uc_io_handle_t *handle, *new_handle = NULL;
568         uc_value_t *cmd_arg = uc_fn_arg(0);
569         uc_value_t *val_arg = uc_fn_arg(1);
570         uc_value_t *res;
571         int fd, cmd, ret;
572         long arg = 0;
573 
574         handle = uc_fn_thisval("io.handle");
575 
576         if (!handle || handle->fd < 0)
577                 err_return(EBADF);
578 
579         fd = handle->fd;
580 
581         if (ucv_type(cmd_arg) != UC_INTEGER)
582                 err_return(EINVAL);
583 
584         cmd = (int)ucv_int64_get(cmd_arg);
585 
586         if (val_arg) {
587                 if (ucv_type(val_arg) != UC_INTEGER)
588                         err_return(EINVAL);
589 
590                 arg = (long)ucv_int64_get(val_arg);
591         }
592 
593         ret = fcntl(fd, cmd, arg);
594 
595         if (ret < 0)
596                 err_return(errno);
597 
598         /* F_DUPFD and F_DUPFD_CLOEXEC return a new fd that we own */
599         if (cmd == F_DUPFD
600 #ifdef F_DUPFD_CLOEXEC
601             || cmd == F_DUPFD_CLOEXEC
602 #endif
603         ) {
604                 res = ucv_resource_create_ex(vm, "io.handle", (void **)&new_handle,
605                                              0, sizeof(*new_handle));
606 
607                 if (!new_handle)
608                         err_return(ENOMEM);
609 
610                 new_handle->fd = ret;
611                 new_handle->close_on_free = true;
612 
613                 return res;
614         }
615 
616         return ucv_int64_new(ret);
617 }
618 
619 #ifdef HAS_IOCTL
620 
621 /**
622  * Performs an ioctl operation on the file descriptor.
623  *
624  * The direction parameter specifies who is reading and writing,
625  * from the user's point of view. It can be one of the following values:
626  *
627  * | Direction      | Description                                                                       |
628  * |----------------|-----------------------------------------------------------------------------------|
629  * | IOC_DIR_NONE   | neither userspace nor kernel is writing, ioctl is executed without passing data.  |
630  * | IOC_DIR_WRITE  | userspace is writing and kernel is reading.                                       |
631  * | IOC_DIR_READ   | kernel is writing and userspace is reading.                                       |
632  * | IOC_DIR_RW     | userspace is writing and kernel is writing back into the data structure.          |
633  *
634  * Returns the result of the ioctl operation; for `IOC_DIR_READ` and
635  * `IOC_DIR_RW` this is a string containing the data, otherwise a number as
636  * return code.
637  *
638  * Returns `null` if an error occurred.
639  *
640  * @function module:io.handle#ioctl
641  *
642  * @param {number} direction
643  * The direction of the ioctl operation. Use constants IOC_DIR_*.
644  *
645  * @param {number} type
646  * The ioctl type (see https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html)
647  *
648  * @param {number} num
649  * The ioctl sequence number.
650  *
651  * @param {number|string} [value]
652  * The value to pass to the ioctl system call. For `IOC_DIR_NONE`, this argument
653  * is ignored. With `IOC_DIR_READ`, the value should be a positive integer
654  * specifying the number of bytes to expect from the kernel. For the other
655  * directions, `IOC_DIR_WRITE` and `IOC_DIR_RW`, that value parameter must be a
656  * string, serving as buffer for the data to send.
657  *
658  * @returns {?number|?string}
659  *
660  * @example
661  * const handle = io.open('/dev/tty', O_RDWR);
662  * const size = handle.ioctl(IOC_DIR_READ, 0x54, 0x13, 8);  // TIOCGWINSZ
663  */
664 static uc_value_t *
665 uc_io_ioctl(uc_vm_t *vm, size_t nargs)
666 {
667         uc_io_handle_t *handle = uc_fn_thisval("io.handle");
668         uc_value_t *direction = uc_fn_arg(0);
669         uc_value_t *type = uc_fn_arg(1);
670         uc_value_t *num = uc_fn_arg(2);
671         uc_value_t *value = uc_fn_arg(3);
672         uc_value_t *mem = NULL;
673         char *buf = NULL;
674         unsigned long req = 0;
675         unsigned int dir, ty, nr;
676         size_t sz = 0;
677         int fd, ret;
678 
679         if (!handle || handle->fd < 0)
680                 err_return(EBADF);
681 
682         fd = handle->fd;
683 
684         if (ucv_type(direction) != UC_INTEGER || ucv_type(type) != UC_INTEGER ||
685             ucv_type(num) != UC_INTEGER)
686                 err_return(EINVAL);
687 
688         dir = ucv_uint64_get(direction);
689         ty = ucv_uint64_get(type);
690         nr = ucv_uint64_get(num);
691 
692         switch (dir) {
693         case IOC_DIR_NONE:
694                 break;
695 
696         case IOC_DIR_WRITE:
697                 if (ucv_type(value) != UC_STRING)
698                         err_return(EINVAL);
699 
700                 sz = ucv_string_length(value);
701                 buf = ucv_string_get(value);
702                 break;
703 
704         case IOC_DIR_READ:
705                 if (ucv_type(value) != UC_INTEGER)
706                         err_return(EINVAL);
707 
708                 sz = ucv_to_unsigned(value);
709 
710                 if (errno != 0)
711                         err_return(errno);
712 
713                 mem = xalloc(sizeof(uc_string_t) + sz + 1);
714                 mem->type = UC_STRING;
715                 mem->refcount = 1;
716                 buf = ucv_string_get(mem);
717                 ((uc_string_t *)mem)->length = sz;
718                 break;
719 
720         case IOC_DIR_RW:
721                 if (ucv_type(value) != UC_STRING)
722                         err_return(EINVAL);
723 
724                 sz = ucv_string_length(value);
725                 mem = ucv_string_new_length(ucv_string_get(value), sz);
726                 buf = ucv_string_get(mem);
727                 break;
728 
729         default:
730                 err_return(EINVAL);
731         }
732 
733         req = _IOC(dir, ty, nr, sz);
734         ret = ioctl(fd, req, buf);
735 
736         if (ret < 0) {
737                 ucv_put(mem);
738                 err_return(errno);
739         }
740 
741         return mem ? mem : ucv_uint64_new(ret);
742 }
743 
744 #endif
745 
746 /**
747  * Checks if the file descriptor refers to a terminal.
748  *
749  * Returns `true` if the descriptor refers to a terminal device.
750  *
751  * Returns `false` otherwise.
752  *
753  * Returns `null` if an error occurred.
754  *
755  * @function module:io.handle#isatty
756  *
757  * @returns {?boolean}
758  *
759  * @example
760  * const handle = io.new(0);  // stdin
761  * if (handle.isatty())
762  *     print("Running in a terminal\n");
763  */
764 static uc_value_t *
765 uc_io_isatty(uc_vm_t *vm, size_t nargs)
766 {
767         uc_io_handle_t *handle;
768         int fd;
769 
770         handle = uc_fn_thisval("io.handle");
771 
772         if (!handle || handle->fd < 0)
773                 err_return(EBADF);
774 
775         fd = handle->fd;
776 
777         return ucv_boolean_new(isatty(fd) == 1);
778 }
779 
780 /**
781  * Closes the file descriptor.
782  *
783  * Closes the underlying file descriptor. Further operations on this handle
784  * will fail.
785  *
786  * Returns `true` if the descriptor was successfully closed.
787  *
788  * Returns `null` if an error occurred.
789  *
790  * @function module:io.handle#close
791  *
792  * @returns {?boolean}
793  *
794  * @example
795  * const handle = io.open('/tmp/test.txt', O_RDONLY);
796  * handle.close();
797  */
798 static uc_value_t *
799 uc_io_close(uc_vm_t *vm, size_t nargs)
800 {
801         uc_io_handle_t *handle;
802 
803         handle = uc_fn_thisval("io.handle");
804 
805         if (!handle || handle->fd < 0)
806                 err_return(EBADF);
807 
808         if (close(handle->fd) < 0)
809                 err_return(errno);
810 
811         handle->fd = -1;
812 
813         return ucv_boolean_new(true);
814 }
815 
816 /**
817  * Creates an io.handle from a file descriptor number.
818  *
819  * Wraps the given file descriptor number in an io.handle object.
820  *
821  * Returns an io.handle object.
822  *
823  * Returns `null` if an error occurred.
824  *
825  * @function module:io#new
826  *
827  * @param {number} fd
828  * The file descriptor number.
829  *
830  * @returns {?module:io.handle}
831  *
832  * @example
833  * // Wrap stdin
834  * const stdin = io.new(0);
835  * const data = stdin.read(100);
836  */
837 static uc_value_t *
838 uc_io_new(uc_vm_t *vm, size_t nargs)
839 {
840         uc_value_t *fdno = uc_fn_arg(0);
841         uc_io_handle_t *handle = NULL;
842         uc_value_t *res;
843         int64_t n;
844 
845         if (ucv_type(fdno) != UC_INTEGER)
846                 err_return(EINVAL);
847 
848         n = ucv_int64_get(fdno);
849 
850         if (n < 0 || n > INT_MAX)
851                 err_return(EBADF);
852 
853         res = ucv_resource_create_ex(vm, "io.handle",
854                                      (void **)&handle, 0, sizeof(*handle));
855 
856         if (!handle)
857                 err_return(ENOMEM);
858 
859         handle->fd = (int)n;
860         handle->close_on_free = false;  /* Don't own this fd */
861 
862         return res;
863 }
864 
865 /**
866  * Opens a file and returns an io.handle.
867  *
868  * Opens the specified file with the given flags and mode, returning an
869  * io.handle wrapping the resulting file descriptor.
870  *
871  * Returns an io.handle object.
872  *
873  * Returns `null` if an error occurred.
874  *
875  * @function module:io#open
876  *
877  * @param {string} path
878  * The path to the file.
879  *
880  * @param {number} [flags=O_RDONLY]
881  * The open flags (O_RDONLY, O_WRONLY, O_RDWR, etc.).
882  *
883  * @param {number} [mode=0o666]
884  * The file creation mode (used with O_CREAT).
885  *
886  * @returns {?module:io.handle}
887  *
888  * @example
889  * const handle = io.open('/tmp/test.txt', O_RDWR | O_CREAT, 0o644);
890  * handle.write('Hello World\n');
891  * handle.close();
892  */
893 static uc_value_t *
894 uc_io_open(uc_vm_t *vm, size_t nargs)
895 {
896         uc_value_t *path = uc_fn_arg(0);
897         uc_value_t *flags = uc_fn_arg(1);
898         uc_value_t *mode = uc_fn_arg(2);
899         uc_io_handle_t *handle = NULL;
900         uc_value_t *res;
901         int open_flags = O_RDONLY;
902         mode_t open_mode = 0666;
903         int fd;
904 
905         if (ucv_type(path) != UC_STRING)
906                 err_return(EINVAL);
907 
908         if (flags) {
909                 if (ucv_type(flags) != UC_INTEGER)
910                         err_return(EINVAL);
911 
912                 open_flags = (int)ucv_int64_get(flags);
913         }
914 
915         if (mode) {
916                 if (ucv_type(mode) != UC_INTEGER)
917                         err_return(EINVAL);
918 
919                 open_mode = (mode_t)ucv_int64_get(mode);
920         }
921 
922         fd = open(ucv_string_get(path), open_flags, open_mode);
923 
924         if (fd < 0)
925                 err_return(errno);
926 
927         res = ucv_resource_create_ex(vm, "io.handle",
928                                      (void **)&handle, 0, sizeof(*handle));
929 
930         if (!handle)
931                 err_return(ENOMEM);
932 
933         handle->fd = fd;
934         handle->close_on_free = true;  /* We own this fd */
935 
936         return res;
937 }
938 
939 /**
940  * Creates a pipe.
941  *
942  * Creates a unidirectional data channel (pipe) that can be used for
943  * inter-process communication. Returns an array containing two io.handle
944  * objects: the first is the read end of the pipe, the second is the write end.
945  *
946  * Data written to the write end can be read from the read end.
947  *
948  * Returns an array `[read_handle, write_handle]` on success.
949  *
950  * Returns `null` if an error occurred.
951  *
952  * @function module:io#pipe
953  *
954  * @returns {?Array<module:io.handle>}
955  *
956  * @example
957  * const [reader, writer] = io.pipe();
958  * writer.write('Hello from pipe!');
959  * const data = reader.read(100);
960  * print(data, "\n");  // Prints: Hello from pipe!
961  */
962 static uc_value_t *
963 uc_io_pipe(uc_vm_t *vm, size_t nargs)
964 {
965         uc_io_handle_t *read_handle = NULL, *write_handle = NULL;
966         uc_value_t *result, *res;
967         int fds[2];
968 
969         if (pipe(fds) < 0)
970                 err_return(errno);
971 
972         res = ucv_resource_create_ex(vm, "io.handle", (void **)&read_handle, 0,
973                                      sizeof(*read_handle));
974 
975         if (!read_handle)
976                 err_return(ENOMEM);
977 
978         read_handle->fd = fds[0];
979         read_handle->close_on_free = true;
980 
981         result = ucv_array_new(vm);
982         ucv_array_push(result, res);
983 
984         res = ucv_resource_create_ex(vm, "io.handle", (void **)&write_handle, 0,
985                                      sizeof(*write_handle));
986 
987         if (!write_handle) {
988                 ucv_put(result);
989                 err_return(ENOMEM);
990         }
991 
992         write_handle->fd = fds[1];
993         write_handle->close_on_free = true;
994 
995         ucv_array_push(result, res);
996 
997         return result;
998 }
999 
1000 /**
1001  * Creates an io.handle from various value types.
1002  *
1003  * Creates an io.handle by extracting the file descriptor from the given value.
1004  * The value can be:
1005  * - An integer file descriptor number
1006  * - An fs.file, fs.proc, or socket resource
1007  * - Any object/array/resource with a fileno() method
1008  *
1009  * Returns an io.handle object.
1010  *
1011  * Returns `null` if an error occurred or the value cannot be converted.
1012  *
1013  * @function module:io#from
1014  *
1015  * @param {*} value
1016  * The value to convert.
1017  *
1018  * @returns {?module:io.handle}
1019  *
1020  * @example
1021  * import { open as fsopen } from 'fs';
1022  * const fp = fsopen('/tmp/test.txt', 'r');
1023  * const handle = io.from(fp);
1024  * const data = handle.read(100);
1025  */
1026 static uc_value_t *
1027 uc_io_from(uc_vm_t *vm, size_t nargs)
1028 {
1029         uc_io_handle_t *handle = NULL;
1030         uc_value_t *val = uc_fn_arg(0);
1031         uc_value_t *res;
1032         int fd;
1033 
1034         if (!val)
1035                 err_return(EINVAL);
1036 
1037         if (!get_fd_from_value(vm, val, &fd))
1038                 return NULL;
1039 
1040         res = ucv_resource_create_ex(vm, "io.handle",
1041                                      (void **)&handle, 0, sizeof(*handle));
1042 
1043         if (!handle)
1044                 err_return(ENOMEM);
1045 
1046         handle->fd = fd;
1047         handle->close_on_free = false;  /* Don't own this fd, it's from external source */
1048 
1049         return res;
1050 }
1051 
1052 static void
1053 uc_io_handle_free(void *ptr)
1054 {
1055         uc_io_handle_t *handle = ptr;
1056 
1057         if (!handle)
1058                 return;
1059 
1060         if (handle->close_on_free && handle->fd >= 0)
1061                 close(handle->fd);
1062 }
1063 
1064 static const uc_function_list_t io_handle_fns[] = {
1065         { "read",               uc_io_read },
1066         { "write",              uc_io_write },
1067         { "seek",               uc_io_seek },
1068         { "tell",               uc_io_tell },
1069         { "dup",                uc_io_dup },
1070         { "dup2",               uc_io_dup2 },
1071         { "fileno",             uc_io_fileno },
1072         { "fcntl",              uc_io_fcntl },
1073 #ifdef HAS_IOCTL
1074         { "ioctl",              uc_io_ioctl },
1075 #endif
1076         { "isatty",             uc_io_isatty },
1077         { "close",              uc_io_close },
1078         { "error",              uc_io_error },
1079 };
1080 
1081 static const uc_function_list_t io_fns[] = {
1082         { "error",              uc_io_error },
1083         { "new",                uc_io_new },
1084         { "open",               uc_io_open },
1085         { "from",               uc_io_from },
1086         { "pipe",               uc_io_pipe },
1087 };
1088 
1089 #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
1090 
1091 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
1092 {
1093         uc_function_list_register(scope, io_fns);
1094 
1095         ADD_CONST(O_RDONLY);
1096         ADD_CONST(O_WRONLY);
1097         ADD_CONST(O_RDWR);
1098         ADD_CONST(O_CREAT);
1099         ADD_CONST(O_EXCL);
1100         ADD_CONST(O_TRUNC);
1101         ADD_CONST(O_APPEND);
1102         ADD_CONST(O_NONBLOCK);
1103         ADD_CONST(O_NOCTTY);
1104         ADD_CONST(O_SYNC);
1105         ADD_CONST(O_CLOEXEC);
1106 #ifdef O_DIRECTORY
1107         ADD_CONST(O_DIRECTORY);
1108 #endif
1109 #ifdef O_NOFOLLOW
1110         ADD_CONST(O_NOFOLLOW);
1111 #endif
1112 
1113         ADD_CONST(SEEK_SET);
1114         ADD_CONST(SEEK_CUR);
1115         ADD_CONST(SEEK_END);
1116 
1117         ADD_CONST(F_DUPFD);
1118 #ifdef F_DUPFD_CLOEXEC
1119         ADD_CONST(F_DUPFD_CLOEXEC);
1120 #endif
1121         ADD_CONST(F_GETFD);
1122         ADD_CONST(F_SETFD);
1123         ADD_CONST(F_GETFL);
1124         ADD_CONST(F_SETFL);
1125         ADD_CONST(F_GETLK);
1126         ADD_CONST(F_SETLK);
1127         ADD_CONST(F_SETLKW);
1128         ADD_CONST(F_GETOWN);
1129         ADD_CONST(F_SETOWN);
1130 
1131         ADD_CONST(FD_CLOEXEC);
1132 
1133 #ifdef HAS_IOCTL
1134         ADD_CONST(IOC_DIR_NONE);
1135         ADD_CONST(IOC_DIR_READ);
1136         ADD_CONST(IOC_DIR_WRITE);
1137         ADD_CONST(IOC_DIR_RW);
1138 #endif
1139 
1140         uc_type_declare(vm, "io.handle", io_handle_fns, uc_io_handle_free);
1141 }
1142 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt