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