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