• 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 <stdlib.h>
 54 #include <string.h>
 55 #include <unistd.h>
 56 #include <fcntl.h>
 57 #include <termios.h>
 58 #include <sys/types.h>
 59 #include <sys/stat.h>
 60 #include <limits.h>
 61 
 62 #if defined(__linux__)
 63 #define HAS_IOCTL
 64 #endif
 65 
 66 #ifdef HAS_IOCTL
 67 #include <sys/ioctl.h>
 68 
 69 #define IOC_DIR_NONE    (_IOC_NONE)
 70 #define IOC_DIR_READ    (_IOC_READ)
 71 #define IOC_DIR_WRITE   (_IOC_WRITE)
 72 #define IOC_DIR_RW              (_IOC_READ | _IOC_WRITE)
 73 
 74 #endif
 75 
 76 #include "ucode/module.h"
 77 #include "ucode/platform.h"
 78 
 79 #define err_return(err) do { \
 80         uc_vm_registry_set(vm, "io.last_error", ucv_int64_new(err)); \
 81         return NULL; \
 82 } while(0)
 83 
 84 typedef struct {
 85         int fd;
 86         bool close_on_free;
 87 } uc_io_handle_t;
 88 
 89 static bool
 90 get_fd_from_value(uc_vm_t *vm, uc_value_t *val, int *fd)
 91 {
 92         uc_io_handle_t *handle;
 93         uc_value_t *fn;
 94         int64_t n;
 95 
 96         /* Check if it's an io.handle resource */
 97         handle = ucv_resource_data(val, "io.handle");
 98 
 99         if (handle) {
100                 if (handle->fd < 0)
101                         err_return(EBADF);
102 
103                 *fd = handle->fd;
104 
105                 return true;
106         }
107 
108         /* Try calling fileno() method */
109         fn = ucv_property_get(val, "fileno");
110         errno = 0;
111 
112         if (ucv_is_callable(fn)) {
113                 uc_vm_stack_push(vm, ucv_get(val));
114                 uc_vm_stack_push(vm, ucv_get(fn));
115 
116                 if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
117                         err_return(EBADF);
118 
119                 val = uc_vm_stack_pop(vm);
120                 n = ucv_int64_get(val);
121                 ucv_put(val);
122         }
123         else {
124                 n = ucv_int64_get(val);
125         }
126 
127         if (errno || n < 0 || n > (int64_t)INT_MAX)
128                 err_return(errno ? errno : EBADF);
129 
130         *fd = n;
131 
132         return true;
133 }
134 
135 /**
136  * Query error information.
137  *
138  * Returns a string containing a description of the last occurred error or
139  * `null` if there is no error information.
140  *
141  * @function module:io#error
142  *
143  * @returns {?string}
144  *
145  * @example
146  * // Trigger an error
147  * io.open('/path/does/not/exist');
148  *
149  * // Print error (should yield "No such file or directory")
150  * print(io.error(), "\n");
151  */
152 static uc_value_t *
153 uc_io_error(uc_vm_t *vm, size_t nargs)
154 {
155         int last_error = ucv_int64_get(uc_vm_registry_get(vm, "io.last_error"));
156 
157         if (last_error == 0)
158                 return NULL;
159 
160         uc_vm_registry_set(vm, "io.last_error", ucv_int64_new(0));
161 
162         return ucv_string_new(strerror(last_error));
163 }
164 
165 /**
166  * Gets the name of the pseudo-terminal slave.
167  *
168  * Returns the name of the pseudo-terminal slave device associated with
169  * the master file descriptor.
170  *
171  * Returns a string containing the slave device name.
172  *
173  * Returns `null` if an error occurred or if the descriptor is not a
174  * pseudo-terminal master.
175  *
176  * @function module:io.handle#ptsname
177  *
178  * @returns {?string}
179  *
180  * @example
181  * const master = io.open('/dev/ptmx', O_RDWR);
182  * const slave_name = master.ptsname();
183  * print(slave_name, "\n");
184  */
185 static uc_value_t *
186 uc_io_ptsname(uc_vm_t *vm, size_t nargs)
187 {
188         uc_io_handle_t *handle;
189         char *name;
190         int fd;
191 
192         handle = uc_fn_thisval("io.handle");
193 
194         if (!handle || handle->fd < 0)
195                 err_return(EBADF);
196 
197         fd = handle->fd;
198 
199         name = ptsname(fd);
200 
201         if (!name)
202                 err_return(errno);
203 
204         return ucv_string_new(name);
205 }
206 
207 /**
208  * Gets terminal attributes.
209  *
210  * Retrieves the terminal attributes for the file descriptor.
211  *
212  * Returns an object containing terminal attributes (iflag, oflag, cflag, lflag,
213  * ispeed, ospeed, and cc array).
214  *
215  * Returns `null` if an error occurred or if the descriptor is not a terminal.
216  *
217  * @function module:io.handle#tcgetattr
218  *
219  * @returns {?object}
220  *
221  * @example
222  * const handle = io.open('/dev/tty', O_RDWR);
223  * const attrs = handle.tcgetattr();
224  * if (attrs)
225  *     print("Input flags: ", attrs.iflag, "\n");
226  */
227 static uc_value_t *
228 uc_io_tcgetattr(uc_vm_t *vm, size_t nargs)
229 {
230         uc_io_handle_t *handle;
231         struct termios tios;
232         uc_value_t *attrs;
233         int fd;
234         int i;
235 
236         handle = uc_fn_thisval("io.handle");
237 
238         if (!handle || handle->fd < 0)
239                 err_return(EBADF);
240 
241         fd = handle->fd;
242 
243         if (tcgetattr(fd, &tios) < 0)
244                 err_return(errno);
245 
246         attrs = ucv_object_new(vm);
247 
248         ucv_object_add(attrs, "iflag", ucv_uint64_new(tios.c_iflag));
249         ucv_object_add(attrs, "oflag", ucv_uint64_new(tios.c_oflag));
250         ucv_object_add(attrs, "cflag", ucv_uint64_new(tios.c_cflag));
251         ucv_object_add(attrs, "lflag", ucv_uint64_new(tios.c_lflag));
252         ucv_object_add(attrs, "ispeed", ucv_uint64_new(cfgetispeed(&tios)));
253         ucv_object_add(attrs, "ospeed", ucv_uint64_new(cfgetospeed(&tios)));
254 
255         uc_value_t *cc_array = ucv_array_new(vm);
256         for (i = 0; i < NCCS; i++) {
257                 ucv_array_push(cc_array, ucv_int64_new((unsigned char)tios.c_cc[i]));
258         }
259         ucv_object_add(attrs, "cc", cc_array);
260 
261         return attrs;
262 }
263 
264 /**
265  * Sets terminal attributes.
266  *
267  * Sets the terminal attributes for the file descriptor.
268  *
269  * The attrs parameter should be an object with properties:
270  * - iflag: input flags
271  * - oflag: output flags
272  * - cflag: control flags
273  * - lflag: local flags
274  * - ispeed: input speed
275  * - ospeed: output speed
276  * - cc: array of control characters (optional)
277  *
278  * Returns `true` on success.
279  *
280  * Returns `null` if an error occurred.
281  *
282  * @function module:io.handle#tcsetattr
283  *
284  * @param {object} attrs
285  * The terminal attributes to set.
286  *
287  * @param {number} [when=0]
288  * When to apply the changes (TCSANOW, TCSADRAIN, TCSAFLUSH).
289  *
290  * @returns {?boolean}
291  *
292  * @example
293  * const handle = io.open('/dev/tty', O_RDWR);
294  * const attrs = handle.tcgetattr();
295  * attrs.lflag &= ~0x0000008; // Disable ECHO
296  * handle.tcsetattr(attrs, TCSANOW);
297  */
298 static uc_value_t *
299 uc_io_tcsetattr(uc_vm_t *vm, size_t nargs)
300 {
301         uc_value_t *attrs_arg = uc_fn_arg(0);
302         uc_value_t *when_arg = uc_fn_arg(1);
303         uc_io_handle_t *handle;
304         struct termios tios;
305         uc_value_t *cc_array, *cc_val;
306         int when = TCSANOW;
307         int fd;
308         size_t i, cc_len;
309 
310         handle = uc_fn_thisval("io.handle");
311 
312         if (!handle || handle->fd < 0)
313                 err_return(EBADF);
314 
315         fd = handle->fd;
316 
317         if (!attrs_arg || ucv_type(attrs_arg) != UC_OBJECT)
318                 err_return(EINVAL);
319 
320         if (when_arg && ucv_type(when_arg) == UC_INTEGER)
321                 when = (int)ucv_int64_get(when_arg);
322 
323         /* Update flags from the attrs object */
324         uc_value_t *iflag = ucv_property_get(attrs_arg, "iflag");
325         if (iflag && ucv_type(iflag) == UC_INTEGER)
326                 tios.c_iflag = (tcflag_t)ucv_uint64_get(iflag);
327 
328         uc_value_t *oflag = ucv_property_get(attrs_arg, "oflag");
329         if (oflag && ucv_type(oflag) == UC_INTEGER)
330                 tios.c_oflag = (tcflag_t)ucv_uint64_get(oflag);
331 
332         uc_value_t *cflag = ucv_property_get(attrs_arg, "cflag");
333         if (cflag && ucv_type(cflag) == UC_INTEGER)
334                 tios.c_cflag = (tcflag_t)ucv_uint64_get(cflag);
335 
336         uc_value_t *lflag = ucv_property_get(attrs_arg, "lflag");
337         if (lflag && ucv_type(lflag) == UC_INTEGER)
338                 tios.c_lflag = (tcflag_t)ucv_uint64_get(lflag);
339 
340         uc_value_t *ispeed = ucv_property_get(attrs_arg, "ispeed");
341         if (ispeed && ucv_type(ispeed) == UC_INTEGER)
342                 cfsetispeed(&tios, (speed_t)ucv_uint64_get(ispeed));
343 
344         uc_value_t *ospeed = ucv_property_get(attrs_arg, "ospeed");
345         if (ospeed && ucv_type(ospeed) == UC_INTEGER)
346                 cfsetospeed(&tios, (speed_t)ucv_uint64_get(ospeed));
347 
348         /* Update control characters */
349         cc_array = ucv_property_get(attrs_arg, "cc");
350         if (cc_array && ucv_type(cc_array) == UC_ARRAY) {
351                 cc_len = ucv_array_length(cc_array);
352                 for (i = 0; i < cc_len && i < NCCS; i++) {
353                         cc_val = ucv_array_get(cc_array, i);
354                         if (cc_val && ucv_type(cc_val) == UC_INTEGER) {
355                                 tios.c_cc[i] = (cc_t)ucv_uint64_get(cc_val);
356                         }
357                 }
358         }
359 
360         if (tcsetattr(fd, when, &tios) < 0)
361                 err_return(errno);
362 
363         return ucv_boolean_new(true);
364 }
365 
366 /**
367  * Grants access to a pseudo-terminal slave device.
368  *
369  * Allows the owner of the pseudo-terminal master device to grant the
370  * appropriate permissions on the corresponding slave device so that it
371  * may be opened.
372  *
373  * This function is typically called before opening the slave device.
374  *
375  * Returns `true` on success.
376  *
377  * Returns `null` if an error occurred.
378  *
379  * @function module:io.handle#grantpt
380  *
381  * @returns {?boolean}
382  *
383  * @example
384  * const master = io.open('/dev/ptmx', O_RDWR);
385  * if (master.grantpt()) {
386  *     print("Granted access to slave device\n");
387  * }
388  */
389 static uc_value_t *
390 uc_io_grantpt(uc_vm_t *vm, size_t nargs)
391 {
392         uc_io_handle_t *handle;
393         int fd;
394 
395         handle = uc_fn_thisval("io.handle");
396 
397         if (!handle || handle->fd < 0)
398                 err_return(EBADF);
399 
400         fd = handle->fd;
401 
402         if (grantpt(fd) < 0)
403                 err_return(errno);
404 
405         return ucv_boolean_new(true);
406 }
407 
408 /**
409  * Unlocks a pseudo-terminal slave device.
410  *
411  * Unlocks the pseudo-terminal slave device associated with the master device
412  * referred to by the file descriptor. This function is typically called after
413  * grantpt() and before opening the slave device.
414  *
415  * Returns `true` on success.
416  *
417  * Returns `null` if an error occurred.
418  *
419  * @function module:io.handle#unlockpt
420  *
421  * @returns {?boolean}
422  *
423  * @example
424  * const master = io.open('/dev/ptmx', O_RDWR);
425  * master.grantpt();
426  * if (master.unlockpt()) {
427  *     print("Unlocked slave device\n");
428  * }
429  */
430 static uc_value_t *
431 uc_io_unlockpt(uc_vm_t *vm, size_t nargs)
432 {
433         uc_io_handle_t *handle;
434         int fd;
435 
436         handle = uc_fn_thisval("io.handle");
437 
438         if (!handle || handle->fd < 0)
439                 err_return(EBADF);
440 
441         fd = handle->fd;
442 
443         if (unlockpt(fd) < 0)
444                 err_return(errno);
445 
446         return ucv_boolean_new(true);
447 }
448 
449 /**
450  * Represents a handle for interacting with a file descriptor.
451  *
452  * @class module:io.handle
453  * @hideconstructor
454  *
455  * @borrows module:io#error as module:io.handle#error
456  *
457  * @see {@link module:io#new|new()}
458  * @see {@link module:io#open|open()}
459  * @see {@link module:io#from|from()}
460  *
461  * @example
462  *
463  * const handle = io.open(…);
464  *
465  * handle.read(…);
466  * handle.write(…);
467  *
468  * handle.seek(…);
469  * handle.tell();
470  *
471  * handle.fileno();
472  *
473  * handle.close();
474  *
475  * handle.error();
476  */
477 
478 /**
479  * Reads data from the file descriptor.
480  *
481  * Reads up to the specified number of bytes from the file descriptor.
482  *
483  * Returns a string containing the read data.
484  *
485  * Returns an empty string on EOF.
486  *
487  * Returns `null` if a read error occurred.
488  *
489  * @function module:io.handle#read
490  *
491  * @param {number} length
492  * The maximum number of bytes to read.
493  *
494  * @returns {?string}
495  *
496  * @example
497  * const handle = io.open('/tmp/test.txt', O_RDONLY);
498  * const data = handle.read(1024);
499  */
500 static uc_value_t *
501 uc_io_read(uc_vm_t *vm, size_t nargs)
502 {
503         uc_value_t *limit = uc_fn_arg(0);
504         uc_value_t *rv = NULL;
505         uc_io_handle_t *handle;
506         int fd;
507         int64_t len;
508         ssize_t rlen;
509         char *buf;
510 
511         handle = uc_fn_thisval("io.handle");
512 
513         if (!handle || handle->fd < 0)
514                 err_return(EBADF);
515 
516         fd = handle->fd;
517 
518         if (ucv_type(limit) != UC_INTEGER)
519                 err_return(EINVAL);
520 
521         len = ucv_int64_get(limit);
522 
523         if (len <= 0)
524                 return ucv_string_new_length("", 0);
525 
526         if (len > SSIZE_MAX)
527                 len = SSIZE_MAX;
528 
529         buf = xalloc(len);
530 
531         rlen = read(fd, buf, len);
532 
533         if (rlen < 0) {
534                 free(buf);
535                 err_return(errno);
536         }
537 
538         rv = ucv_string_new_length(buf, rlen);
539         free(buf);
540 
541         return rv;
542 }
543 
544 /**
545  * Writes data to the file descriptor.
546  *
547  * Writes the given data to the file descriptor. Non-string values are
548  * converted to strings before being written.
549  *
550  * Returns the number of bytes written.
551  *
552  * Returns `null` if a write error occurred.
553  *
554  * @function module:io.handle#write
555  *
556  * @param {*} data
557  * The data to write.
558  *
559  * @returns {?number}
560  *
561  * @example
562  * const handle = io.open('/tmp/test.txt', O_WRONLY | O_CREAT);
563  * handle.write('Hello World\n');
564  */
565 static uc_value_t *
566 uc_io_write(uc_vm_t *vm, size_t nargs)
567 {
568         uc_value_t *data = uc_fn_arg(0);
569         uc_io_handle_t *handle;
570         ssize_t wlen;
571         size_t len;
572         char *str;
573         int fd;
574 
575         handle = uc_fn_thisval("io.handle");
576 
577         if (!handle || handle->fd < 0)
578                 err_return(EBADF);
579 
580         fd = handle->fd;
581 
582         if (ucv_type(data) == UC_STRING) {
583                 len = ucv_string_length(data);
584                 wlen = write(fd, ucv_string_get(data), len);
585         }
586         else {
587                 str = ucv_to_jsonstring(vm, data);
588                 len = str ? strlen(str) : 0;
589                 wlen = write(fd, str, len);
590                 free(str);
591         }
592 
593         if (wlen < 0)
594                 err_return(errno);
595 
596         return ucv_int64_new(wlen);
597 }
598 
599 /**
600  * Sets the file descriptor position.
601  *
602  * Sets the file position of the descriptor to the given offset and whence.
603  *
604  * Returns `true` if the position was successfully set.
605  *
606  * Returns `null` if an error occurred.
607  *
608  * @function module:io.handle#seek
609  *
610  * @param {number} [offset=0]
611  * The offset in bytes.
612  *
613  * @param {number} [whence=0]
614  * The position reference.
615  *
616  * | Whence | Description                                                        |
617  * |--------|--------------------------------------------------------------------|
618  * | `0`    | The offset is relative to the start of the file (SEEK_SET).       |
619  * | `1`    | The offset is relative to the current position (SEEK_CUR).        |
620  * | `2`    | The offset is relative to the end of the file (SEEK_END).         |
621  *
622  * @returns {?boolean}
623  *
624  * @example
625  * const handle = io.open('/tmp/test.txt', O_RDONLY);
626  * handle.seek(100, 0);  // Seek to byte 100 from start
627  */
628 static uc_value_t *
629 uc_io_seek(uc_vm_t *vm, size_t nargs)
630 {
631         uc_value_t *ofs = uc_fn_arg(0);
632         uc_value_t *how = uc_fn_arg(1);
633         uc_io_handle_t *handle;
634         int whence;
635         off_t offset;
636         int fd;
637 
638         handle = uc_fn_thisval("io.handle");
639 
640         if (!handle || handle->fd < 0)
641                 err_return(EBADF);
642 
643         fd = handle->fd;
644 
645         if (!ofs)
646                 offset = 0;
647         else if (ucv_type(ofs) != UC_INTEGER)
648                 err_return(EINVAL);
649         else
650                 offset = (off_t)ucv_int64_get(ofs);
651 
652         if (!how)
653                 whence = SEEK_SET;
654         else if (ucv_type(how) != UC_INTEGER)
655                 err_return(EINVAL);
656         else
657                 whence = (int)ucv_int64_get(how);
658 
659         if (lseek(fd, offset, whence) < 0)
660                 err_return(errno);
661 
662         return ucv_boolean_new(true);
663 }
664 
665 /**
666  * Gets the current file descriptor position.
667  *
668  * Returns the current file position as an integer.
669  *
670  * Returns `null` if an error occurred.
671  *
672  * @function module:io.handle#tell
673  *
674  * @returns {?number}
675  *
676  * @example
677  * const handle = io.open('/tmp/test.txt', O_RDONLY);
678  * const pos = handle.tell();
679  */
680 static uc_value_t *
681 uc_io_tell(uc_vm_t *vm, size_t nargs)
682 {
683         uc_io_handle_t *handle;
684         off_t offset;
685         int fd;
686 
687         handle = uc_fn_thisval("io.handle");
688 
689         if (!handle || handle->fd < 0)
690                 err_return(EBADF);
691 
692         fd = handle->fd;
693 
694         offset = lseek(fd, 0, SEEK_CUR);
695 
696         if (offset < 0)
697                 err_return(errno);
698 
699         return ucv_int64_new(offset);
700 }
701 
702 /**
703  * Duplicates the file descriptor.
704  *
705  * Creates a duplicate of the file descriptor using dup(2).
706  *
707  * Returns a new io.handle for the duplicated descriptor.
708  *
709  * Returns `null` if an error occurred.
710  *
711  * @function module:io.handle#dup
712  *
713  * @returns {?module:io.handle}
714  *
715  * @example
716  * const handle = io.open('/tmp/test.txt', O_RDONLY);
717  * const dup_handle = handle.dup();
718  */
719 static uc_value_t *
720 uc_io_dup(uc_vm_t *vm, size_t nargs)
721 {
722         uc_io_handle_t *handle, *new_handle = NULL;
723         uc_value_t *res;
724         int fd, newfd;
725 
726         handle = uc_fn_thisval("io.handle");
727 
728         if (!handle || handle->fd < 0)
729                 err_return(EBADF);
730 
731         fd = handle->fd;
732 
733         newfd = dup(fd);
734 
735         if (newfd < 0)
736                 err_return(errno);
737 
738         res = ucv_resource_create_ex(vm, "io.handle",
739                                      (void **)&new_handle, 0, sizeof(*new_handle));
740 
741         if (!new_handle)
742                 err_return(ENOMEM);
743 
744         new_handle->fd = newfd;
745         new_handle->close_on_free = true;
746 
747         return res;
748 }
749 
750 /**
751  * Duplicates the file descriptor to a specific descriptor number.
752  *
753  * Creates a duplicate of the file descriptor to the specified descriptor
754  * number using dup2(2). If newfd was previously open, it is silently closed.
755  *
756  * Returns `true` on success.
757  *
758  * Returns `null` if an error occurred.
759  *
760  * @function module:io.handle#dup2
761  *
762  * @param {number} newfd
763  * The target file descriptor number.
764  *
765  * @returns {?boolean}
766  *
767  * @example
768  * const handle = io.open('/tmp/test.txt', O_WRONLY);
769  * handle.dup2(2);  // Redirect stderr to the file
770  */
771 static uc_value_t *
772 uc_io_dup2(uc_vm_t *vm, size_t nargs)
773 {
774         uc_value_t *newfd_arg = uc_fn_arg(0);
775         uc_io_handle_t *handle;
776         int fd, newfd;
777 
778         handle = uc_fn_thisval("io.handle");
779 
780         if (!handle || handle->fd < 0)
781                 err_return(EBADF);
782 
783         fd = handle->fd;
784 
785         if (!get_fd_from_value(vm, newfd_arg, &newfd))
786                 return NULL;
787 
788         if (dup2(fd, newfd) < 0)
789                 err_return(errno);
790 
791         return ucv_boolean_new(true);
792 }
793 
794 /**
795  * Gets the file descriptor number.
796  *
797  * Returns the underlying file descriptor number.
798  *
799  * Returns `null` if the handle is closed.
800  *
801  * @function module:io.handle#fileno
802  *
803  * @returns {?number}
804  *
805  * @example
806  * const handle = io.open('/tmp/test.txt', O_RDONLY);
807  * print(handle.fileno(), "\n");
808  */
809 static uc_value_t *
810 uc_io_fileno(uc_vm_t *vm, size_t nargs)
811 {
812         uc_io_handle_t *handle;
813 
814         handle = uc_fn_thisval("io.handle");
815 
816         if (!handle || handle->fd < 0)
817                 err_return(EBADF);
818 
819         return ucv_int64_new(handle->fd);
820 }
821 
822 /**
823  * Performs fcntl() operations on the file descriptor.
824  *
825  * Performs the specified fcntl() command on the file descriptor with an
826  * optional argument.
827  *
828  * Returns the result of the fcntl() call. For F_DUPFD and F_DUPFD_CLOEXEC,
829  * returns a new io.handle wrapping the duplicated descriptor. For other
830  * commands, returns a number (interpretation depends on cmd).
831  *
832  * Returns `null` if an error occurred.
833  *
834  * @function module:io.handle#fcntl
835  *
836  * @param {number} cmd
837  * The fcntl command (e.g., F_GETFL, F_SETFL, F_GETFD, F_SETFD, F_DUPFD).
838  *
839  * @param {number} [arg]
840  * Optional argument for the command.
841  *
842  * @returns {?(number|module:io.handle)}
843  *
844  * @example
845  * const handle = io.open('/tmp/test.txt', O_RDONLY);
846  * const flags = handle.fcntl(F_GETFL);
847  * handle.fcntl(F_SETFL, flags | O_NONBLOCK);
848  * const dup_handle = handle.fcntl(F_DUPFD, 10);  // Returns io.handle
849  */
850 static uc_value_t *
851 uc_io_fcntl(uc_vm_t *vm, size_t nargs)
852 {
853         uc_io_handle_t *handle, *new_handle = NULL;
854         uc_value_t *cmd_arg = uc_fn_arg(0);
855         uc_value_t *val_arg = uc_fn_arg(1);
856         uc_value_t *res;
857         int fd, cmd, ret;
858         long arg = 0;
859 
860         handle = uc_fn_thisval("io.handle");
861 
862         if (!handle || handle->fd < 0)
863                 err_return(EBADF);
864 
865         fd = handle->fd;
866 
867         if (ucv_type(cmd_arg) != UC_INTEGER)
868                 err_return(EINVAL);
869 
870         cmd = (int)ucv_int64_get(cmd_arg);
871 
872         if (val_arg) {
873                 if (ucv_type(val_arg) != UC_INTEGER)
874                         err_return(EINVAL);
875 
876                 arg = (long)ucv_int64_get(val_arg);
877         }
878 
879         ret = fcntl(fd, cmd, arg);
880 
881         if (ret < 0)
882                 err_return(errno);
883 
884         /* F_DUPFD and F_DUPFD_CLOEXEC return a new fd that we own */
885         if (cmd == F_DUPFD
886 #ifdef F_DUPFD_CLOEXEC
887             || cmd == F_DUPFD_CLOEXEC
888 #endif
889         ) {
890                 res = ucv_resource_create_ex(vm, "io.handle", (void **)&new_handle,
891                                              0, sizeof(*new_handle));
892 
893                 if (!new_handle)
894                         err_return(ENOMEM);
895 
896                 new_handle->fd = ret;
897                 new_handle->close_on_free = true;
898 
899                 return res;
900         }
901 
902         return ucv_int64_new(ret);
903 }
904 
905 #ifdef HAS_IOCTL
906 
907 /**
908  * Performs an ioctl operation on the file descriptor.
909  *
910  * The direction parameter specifies who is reading and writing,
911  * from the user's point of view. It can be one of the following values:
912  *
913  * | Direction      | Description                                                                       |
914  * |----------------|-----------------------------------------------------------------------------------|
915  * | IOC_DIR_NONE   | neither userspace nor kernel is writing, ioctl is executed without passing data.  |
916  * | IOC_DIR_WRITE  | userspace is writing and kernel is reading.                                       |
917  * | IOC_DIR_READ   | kernel is writing and userspace is reading.                                       |
918  * | IOC_DIR_RW     | userspace is writing and kernel is writing back into the data structure.          |
919  *
920  * Returns the result of the ioctl operation; for `IOC_DIR_READ` and
921  * `IOC_DIR_RW` this is a string containing the data, otherwise a number as
922  * return code.
923  *
924  * Returns `null` if an error occurred.
925  *
926  * @function module:io.handle#ioctl
927  *
928  * @param {number} direction
929  * The direction of the ioctl operation. Use constants IOC_DIR_*.
930  *
931  * @param {number} type
932  * The ioctl type (see https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html)
933  *
934  * @param {number} num
935  * The ioctl sequence number.
936  *
937  * @param {number|string} [value]
938  * The value to pass to the ioctl system call. For `IOC_DIR_NONE`, this argument
939  * is ignored. With `IOC_DIR_READ`, the value should be a positive integer
940  * specifying the number of bytes to expect from the kernel. For the other
941  * directions, `IOC_DIR_WRITE` and `IOC_DIR_RW`, that value parameter must be a
942  * string, serving as buffer for the data to send.
943  *
944  * @returns {?number|?string}
945  *
946  * @example
947  * const handle = io.open('/dev/tty', O_RDWR);
948  * const size = handle.ioctl(IOC_DIR_READ, 0x54, 0x13, 8);  // TIOCGWINSZ
949  */
950 static uc_value_t *
951 uc_io_ioctl(uc_vm_t *vm, size_t nargs)
952 {
953         uc_io_handle_t *handle = uc_fn_thisval("io.handle");
954         uc_value_t *direction = uc_fn_arg(0);
955         uc_value_t *type = uc_fn_arg(1);
956         uc_value_t *num = uc_fn_arg(2);
957         uc_value_t *value = uc_fn_arg(3);
958         uc_value_t *mem = NULL;
959         char *buf = NULL;
960         unsigned long req = 0;
961         unsigned int dir, ty, nr;
962         size_t sz = 0;
963         int fd, ret;
964 
965         if (!handle || handle->fd < 0)
966                 err_return(EBADF);
967 
968         fd = handle->fd;
969 
970         if (ucv_type(direction) != UC_INTEGER || ucv_type(type) != UC_INTEGER ||
971             ucv_type(num) != UC_INTEGER)
972                 err_return(EINVAL);
973 
974         dir = ucv_uint64_get(direction);
975         ty = ucv_uint64_get(type);
976         nr = ucv_uint64_get(num);
977 
978         switch (dir) {
979         case IOC_DIR_NONE:
980                 break;
981 
982         case IOC_DIR_WRITE:
983                 if (ucv_type(value) != UC_STRING)
984                         err_return(EINVAL);
985 
986                 sz = ucv_string_length(value);
987                 buf = ucv_string_get(value);
988                 break;
989 
990         case IOC_DIR_READ:
991                 if (ucv_type(value) != UC_INTEGER)
992                         err_return(EINVAL);
993 
994                 sz = ucv_to_unsigned(value);
995 
996                 if (errno != 0)
997                         err_return(errno);
998 
999                 mem = xalloc(sizeof(uc_string_t) + sz + 1);
1000                 mem->type = UC_STRING;
1001                 mem->refcount = 1;
1002                 buf = ucv_string_get(mem);
1003                 ((uc_string_t *)mem)->length = sz;
1004                 break;
1005 
1006         case IOC_DIR_RW:
1007                 if (ucv_type(value) != UC_STRING)
1008                         err_return(EINVAL);
1009 
1010                 sz = ucv_string_length(value);
1011                 mem = ucv_string_new_length(ucv_string_get(value), sz);
1012                 buf = ucv_string_get(mem);
1013                 break;
1014 
1015         default:
1016                 err_return(EINVAL);
1017         }
1018 
1019         req = _IOC(dir, ty, nr, sz);
1020         ret = ioctl(fd, req, buf);
1021 
1022         if (ret < 0) {
1023                 ucv_put(mem);
1024                 err_return(errno);
1025         }
1026 
1027         return mem ? mem : ucv_uint64_new(ret);
1028 }
1029 
1030 #endif
1031 
1032 /**
1033  * Checks if the file descriptor refers to a terminal.
1034  *
1035  * Returns `true` if the descriptor refers to a terminal device.
1036  *
1037  * Returns `false` otherwise.
1038  *
1039  * Returns `null` if an error occurred.
1040  *
1041  * @function module:io.handle#isatty
1042  *
1043  * @returns {?boolean}
1044  *
1045  * @example
1046  * const handle = io.new(0);  // stdin
1047  * if (handle.isatty())
1048  *     print("Running in a terminal\n");
1049  */
1050 static uc_value_t *
1051 uc_io_isatty(uc_vm_t *vm, size_t nargs)
1052 {
1053         uc_io_handle_t *handle;
1054         int fd;
1055 
1056         handle = uc_fn_thisval("io.handle");
1057 
1058         if (!handle || handle->fd < 0)
1059                 err_return(EBADF);
1060 
1061         fd = handle->fd;
1062 
1063         return ucv_boolean_new(isatty(fd) == 1);
1064 }
1065 
1066 /**
1067  * Closes the file descriptor.
1068  *
1069  * Closes the underlying file descriptor. Further operations on this handle
1070  * will fail.
1071  *
1072  * Returns `true` if the descriptor was successfully closed.
1073  *
1074  * Returns `null` if an error occurred.
1075  *
1076  * @function module:io.handle#close
1077  *
1078  * @returns {?boolean}
1079  *
1080  * @example
1081  * const handle = io.open('/tmp/test.txt', O_RDONLY);
1082  * handle.close();
1083  */
1084 static uc_value_t *
1085 uc_io_close(uc_vm_t *vm, size_t nargs)
1086 {
1087         uc_io_handle_t *handle;
1088 
1089         handle = uc_fn_thisval("io.handle");
1090 
1091         if (!handle || handle->fd < 0)
1092                 err_return(EBADF);
1093 
1094         if (close(handle->fd) < 0)
1095                 err_return(errno);
1096 
1097         handle->fd = -1;
1098 
1099         return ucv_boolean_new(true);
1100 }
1101 
1102 /**
1103  * Creates an io.handle from a file descriptor number.
1104  *
1105  * Wraps the given file descriptor number in an io.handle object.
1106  *
1107  * Returns an io.handle object.
1108  *
1109  * Returns `null` if an error occurred.
1110  *
1111  * @function module:io#new
1112  *
1113  * @param {number} fd
1114  * The file descriptor number.
1115  *
1116  * @returns {?module:io.handle}
1117  *
1118  * @example
1119  * // Wrap stdin
1120  * const stdin = io.new(0);
1121  * const data = stdin.read(100);
1122  */
1123 static uc_value_t *
1124 uc_io_new(uc_vm_t *vm, size_t nargs)
1125 {
1126         uc_value_t *fdno = uc_fn_arg(0);
1127         uc_io_handle_t *handle = NULL;
1128         uc_value_t *res;
1129         int64_t n;
1130 
1131         if (ucv_type(fdno) != UC_INTEGER)
1132                 err_return(EINVAL);
1133 
1134         n = ucv_int64_get(fdno);
1135 
1136         if (n < 0 || n > INT_MAX)
1137                 err_return(EBADF);
1138 
1139         res = ucv_resource_create_ex(vm, "io.handle",
1140                                      (void **)&handle, 0, sizeof(*handle));
1141 
1142         if (!handle)
1143                 err_return(ENOMEM);
1144 
1145         handle->fd = (int)n;
1146         handle->close_on_free = false;  /* Don't own this fd */
1147 
1148         return res;
1149 }
1150 
1151 /**
1152  * Opens a file and returns an io.handle.
1153  *
1154  * Opens the specified file with the given flags and mode, returning an
1155  * io.handle wrapping the resulting file descriptor.
1156  *
1157  * Returns an io.handle object.
1158  *
1159  * Returns `null` if an error occurred.
1160  *
1161  * @function module:io#open
1162  *
1163  * @param {string} path
1164  * The path to the file.
1165  *
1166  * @param {number} [flags=O_RDONLY]
1167  * The open flags (O_RDONLY, O_WRONLY, O_RDWR, etc.).
1168  *
1169  * @param {number} [mode=0o666]
1170  * The file creation mode (used with O_CREAT).
1171  *
1172  * @returns {?module:io.handle}
1173  *
1174  * @example
1175  * const handle = io.open('/tmp/test.txt', O_RDWR | O_CREAT, 0o644);
1176  * handle.write('Hello World\n');
1177  * handle.close();
1178  */
1179 static uc_value_t *
1180 uc_io_open(uc_vm_t *vm, size_t nargs)
1181 {
1182         uc_value_t *path = uc_fn_arg(0);
1183         uc_value_t *flags = uc_fn_arg(1);
1184         uc_value_t *mode = uc_fn_arg(2);
1185         uc_io_handle_t *handle = NULL;
1186         uc_value_t *res;
1187         int open_flags = O_RDONLY;
1188         mode_t open_mode = 0666;
1189         int fd;
1190 
1191         if (ucv_type(path) != UC_STRING)
1192                 err_return(EINVAL);
1193 
1194         if (flags) {
1195                 if (ucv_type(flags) != UC_INTEGER)
1196                         err_return(EINVAL);
1197 
1198                 open_flags = (int)ucv_int64_get(flags);
1199         }
1200 
1201         if (mode) {
1202                 if (ucv_type(mode) != UC_INTEGER)
1203                         err_return(EINVAL);
1204 
1205                 open_mode = (mode_t)ucv_int64_get(mode);
1206         }
1207 
1208         fd = open(ucv_string_get(path), open_flags, open_mode);
1209 
1210         if (fd < 0)
1211                 err_return(errno);
1212 
1213         res = ucv_resource_create_ex(vm, "io.handle",
1214                                      (void **)&handle, 0, sizeof(*handle));
1215 
1216         if (!handle)
1217                 err_return(ENOMEM);
1218 
1219         handle->fd = fd;
1220         handle->close_on_free = true;  /* We own this fd */
1221 
1222         return res;
1223 }
1224 
1225 /**
1226  * Creates a pipe.
1227  *
1228  * Creates a unidirectional data channel (pipe) that can be used for
1229  * inter-process communication. Returns an array containing two io.handle
1230  * objects: the first is the read end of the pipe, the second is the write end.
1231  *
1232  * Data written to the write end can be read from the read end.
1233  *
1234  * Returns an array `[read_handle, write_handle]` on success.
1235  *
1236  * Returns `null` if an error occurred.
1237  *
1238  * @function module:io#pipe
1239  *
1240  * @returns {?Array<module:io.handle>}
1241  *
1242  * @example
1243  * const [reader, writer] = io.pipe();
1244  * writer.write('Hello from pipe!');
1245  * const data = reader.read(100);
1246  * print(data, "\n");  // Prints: Hello from pipe!
1247  */
1248 static uc_value_t *
1249 uc_io_pipe(uc_vm_t *vm, size_t nargs)
1250 {
1251         uc_io_handle_t *read_handle = NULL, *write_handle = NULL;
1252         uc_value_t *result, *res;
1253         int fds[2];
1254 
1255         if (pipe(fds) < 0)
1256                 err_return(errno);
1257 
1258         res = ucv_resource_create_ex(vm, "io.handle", (void **)&read_handle, 0,
1259                                      sizeof(*read_handle));
1260 
1261         if (!read_handle)
1262                 err_return(ENOMEM);
1263 
1264         read_handle->fd = fds[0];
1265         read_handle->close_on_free = true;
1266 
1267         result = ucv_array_new(vm);
1268         ucv_array_push(result, res);
1269 
1270         res = ucv_resource_create_ex(vm, "io.handle", (void **)&write_handle, 0,
1271                                      sizeof(*write_handle));
1272 
1273         if (!write_handle) {
1274                 ucv_put(result);
1275                 err_return(ENOMEM);
1276         }
1277 
1278         write_handle->fd = fds[1];
1279         write_handle->close_on_free = true;
1280 
1281         ucv_array_push(result, res);
1282 
1283         return result;
1284 }
1285 
1286 /**
1287  * Creates an io.handle from various value types.
1288  *
1289  * Creates an io.handle by extracting the file descriptor from the given value.
1290  * The value can be:
1291  * - An integer file descriptor number
1292  * - An fs.file, fs.proc, or socket resource
1293  * - Any object/array/resource with a fileno() method
1294  *
1295  * Returns an io.handle object.
1296  *
1297  * Returns `null` if an error occurred or the value cannot be converted.
1298  *
1299  * @function module:io#from
1300  *
1301  * @param {*} value
1302  * The value to convert.
1303  *
1304  * @returns {?module:io.handle}
1305  *
1306  * @example
1307  * import { open as fsopen } from 'fs';
1308  * const fp = fsopen('/tmp/test.txt', 'r');
1309  * const handle = io.from(fp);
1310  * const data = handle.read(100);
1311  */
1312 static uc_value_t *
1313 uc_io_from(uc_vm_t *vm, size_t nargs)
1314 {
1315         uc_io_handle_t *handle = NULL;
1316         uc_value_t *val = uc_fn_arg(0);
1317         uc_value_t *res;
1318         int fd;
1319 
1320         if (!val)
1321                 err_return(EINVAL);
1322 
1323         if (!get_fd_from_value(vm, val, &fd))
1324                 return NULL;
1325 
1326         res = ucv_resource_create_ex(vm, "io.handle",
1327                                      (void **)&handle, 0, sizeof(*handle));
1328 
1329         if (!handle)
1330                 err_return(ENOMEM);
1331 
1332         handle->fd = fd;
1333         handle->close_on_free = false;  /* Don't own this fd, it's from external source */
1334 
1335         return res;
1336 }
1337 
1338 static void
1339 uc_io_handle_free(void *ptr)
1340 {
1341         uc_io_handle_t *handle = ptr;
1342 
1343         if (!handle)
1344                 return;
1345 
1346         if (handle->close_on_free && handle->fd >= 0)
1347                 close(handle->fd);
1348 }
1349 
1350 static const uc_function_list_t io_handle_fns[] = {
1351         { "read",               uc_io_read },
1352         { "write",              uc_io_write },
1353         { "seek",               uc_io_seek },
1354         { "tell",               uc_io_tell },
1355         { "dup",                uc_io_dup },
1356         { "dup2",               uc_io_dup2 },
1357         { "fileno",             uc_io_fileno },
1358         { "fcntl",              uc_io_fcntl },
1359 #ifdef HAS_IOCTL
1360         { "ioctl",              uc_io_ioctl },
1361 #endif
1362         { "isatty",             uc_io_isatty },
1363         { "close",              uc_io_close },
1364         { "error",              uc_io_error },
1365         { "ptsname",    uc_io_ptsname },
1366         { "tcgetattr",  uc_io_tcgetattr },
1367         { "tcsetattr",  uc_io_tcsetattr },
1368         { "grantpt",    uc_io_grantpt },
1369         { "unlockpt",   uc_io_unlockpt },
1370 };
1371 
1372 static const uc_function_list_t io_fns[] = {
1373         { "error",              uc_io_error },
1374         { "new",                uc_io_new },
1375         { "open",               uc_io_open },
1376         { "from",               uc_io_from },
1377         { "pipe",               uc_io_pipe },
1378 };
1379 
1380 #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
1381 
1382 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
1383 {
1384         uc_function_list_register(scope, io_fns);
1385 
1386         ADD_CONST(O_RDONLY);
1387         ADD_CONST(O_WRONLY);
1388         ADD_CONST(O_RDWR);
1389         ADD_CONST(O_CREAT);
1390         ADD_CONST(O_EXCL);
1391         ADD_CONST(O_TRUNC);
1392         ADD_CONST(O_APPEND);
1393         ADD_CONST(O_NONBLOCK);
1394         ADD_CONST(O_NOCTTY);
1395         ADD_CONST(O_SYNC);
1396         ADD_CONST(O_CLOEXEC);
1397 #ifdef O_DIRECTORY
1398         ADD_CONST(O_DIRECTORY);
1399 #endif
1400 #ifdef O_NOFOLLOW
1401         ADD_CONST(O_NOFOLLOW);
1402 #endif
1403 
1404         ADD_CONST(SEEK_SET);
1405         ADD_CONST(SEEK_CUR);
1406         ADD_CONST(SEEK_END);
1407 
1408         ADD_CONST(F_DUPFD);
1409 #ifdef F_DUPFD_CLOEXEC
1410         ADD_CONST(F_DUPFD_CLOEXEC);
1411 #endif
1412         ADD_CONST(F_GETFD);
1413         ADD_CONST(F_SETFD);
1414         ADD_CONST(F_GETFL);
1415         ADD_CONST(F_SETFL);
1416         ADD_CONST(F_GETLK);
1417         ADD_CONST(F_SETLK);
1418         ADD_CONST(F_SETLKW);
1419         ADD_CONST(F_GETOWN);
1420         ADD_CONST(F_SETOWN);
1421 
1422         ADD_CONST(FD_CLOEXEC);
1423 
1424         ADD_CONST(TCSANOW);
1425         ADD_CONST(TCSADRAIN);
1426         ADD_CONST(TCSAFLUSH);
1427 
1428 #ifdef HAS_IOCTL
1429         ADD_CONST(IOC_DIR_NONE);
1430         ADD_CONST(IOC_DIR_READ);
1431         ADD_CONST(IOC_DIR_WRITE);
1432         ADD_CONST(IOC_DIR_RW);
1433 #endif
1434 
1435         uc_type_declare(vm, "io.handle", io_handle_fns, uc_io_handle_free);
1436 }
1437 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt