1 /* 2 * Filesystem client functions. 3 * 4 * Copyright (C) 2018-2024 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/re/env.h> 23 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 28 #include <mem/memory_utils.h> 29 #include <systypes/env.h> 30 #include <systypes/fcntl.h> 31 32 #include "client.h" 33 34 35 36 /* Default size of pipe regions. */ 37 38 static const offset_t DEFAULT_PIPE_SIZE = 4096; 39 40 /* Size of the core member region of a directory entry structure. */ 41 42 static const offset_t DIRENT_CORE_SIZE = (sizeof(struct dirent) - sizeof(((struct dirent *) 0)->d_name)); 43 44 45 46 /* Merging of region flags from protection and access flags. */ 47 48 static rm_flags_t _combine_region_flags(rm_flags_t region_flags, flags_t flags) 49 { 50 return region_flags & (file_region_flags(flags) | L4RE_RM_F_X); 51 } 52 53 /* Conversion of protection and access flags to region flags. */ 54 55 rm_flags_t client_region_flags(prot_t prot, flags_t flags) 56 { 57 rm_flags_t rm_flags = 0; 58 59 if (prot & PROT_READ) 60 rm_flags |= L4RE_RM_F_R; 61 if (prot & PROT_WRITE) 62 rm_flags |= L4RE_RM_F_W; 63 if (prot & PROT_EXEC) 64 rm_flags |= L4RE_RM_F_X; 65 66 return _combine_region_flags(rm_flags, flags); 67 } 68 69 70 71 /* Access the given position and synchronise state with the file object. Pipe 72 objects may return busy conditions indicating that the desired access cannot 73 yet be fulfilled. */ 74 75 static long _access(file_t *file, offset_t position) 76 { 77 long err; 78 79 if (file->object_flags & OBJECT_SUPPORTS_MMAP) 80 { 81 offset_t adjusted_position = position; 82 83 /* Where the position is outside the current region, re-map. */ 84 85 if ((position < file->start_pos) || (position >= file->end_pos)) 86 { 87 offset_t length = file_span(file); 88 89 if (!length) 90 length = PAGE_SIZE; 91 92 /* Avoid growth of the mapped region when the end of the region is 93 calculated to be position + span, which is then rounded up, whereas the 94 start of the region is rounded down. */ 95 96 adjusted_position = trunc(position, PAGE_SIZE); 97 98 if (file_mmap(file, adjusted_position, length, 0, 0, 99 file_region_flags(file->flags))) 100 return -L4_EIO; 101 } 102 103 /* Otherwise, flush any written data in the current region and update the 104 file size details. */ 105 106 else 107 { 108 err = client_flush(file); 109 if (err) 110 return err; 111 } 112 113 /* Update the current data offset. */ 114 115 file->data_current = position - file->start_pos; 116 117 return L4_EOK; 118 } 119 else 120 { 121 bool initial = (file->memory == NULL); 122 123 /* Handle the initial condition with no current region. */ 124 125 if (initial) 126 { 127 err = client_current_region(file); 128 if (err) 129 return err; 130 } 131 132 /* Strict conditions for region navigation in pipes. */ 133 134 if ((position < file->start_pos) || (position > file->end_pos)) 135 { 136 return -L4_EIO; 137 } 138 139 /* The next region is only available at the end of the mapped memory. */ 140 141 else if (position == file->end_pos) 142 { 143 return client_next_region(file); 144 } 145 146 /* Within the current pipe region, synchronise with the pipe object. */ 147 148 else 149 { 150 if (initial) 151 return L4_EOK; 152 else 153 return client_current_region(file); 154 } 155 } 156 } 157 158 159 160 /* Return whether an operation on file should block for more content or more 161 space. A file must be configured for blocking, not be closed, and must either 162 be lacking content (if reading) or space (if writing). */ 163 164 static int _operation_blocking(file_t *file, int reading) 165 { 166 return (file->can_block && !(file->notifiable.notifications & NOTIFY_PEER_CLOSED) && ( 167 (reading && !file_data_available(file)) || 168 (!reading && !file_data_space(file)))); 169 } 170 171 172 173 /* Return whether an access could occur, blocking if necessary. */ 174 175 static int _access_blocking(file_t *file, offset_t position, int reading) 176 { 177 long err; 178 179 /* Attempt to access the position, handling an error condition or a blocking 180 condition. */ 181 182 while ((err = _access(file, position)) || _operation_blocking(file, reading)) 183 { 184 position = file->data_current; 185 186 /* Exit if blocking is not configured or suitable. */ 187 188 if ((err && (err != -L4_EBUSY)) || !file->can_block) 189 return 0; 190 191 /* Handle an inability to access by blocking, exiting if waiting failed. */ 192 193 if (client_wait_file(file, client_notifier_task())) 194 return 0; 195 } 196 197 return 1; 198 } 199 200 201 202 /* Ensure that memory is mapped for accessing the given file, using the 203 indicated count as a region size hint. */ 204 205 static void *_map_memory(file_t *file, offset_t count) 206 { 207 if (file->memory == NULL) 208 { 209 if (file->object_flags & OBJECT_SUPPORTS_MMAP) 210 return client_mmap(file, client_tell(file), count, 0, 0, 211 file_region_flags(file->flags)); 212 else if (pipe_current(file)) 213 return NULL; 214 } 215 216 return file->memory; 217 } 218 219 220 221 /* Open a file opening object. */ 222 223 l4_cap_idx_t client_open_for_user(user_t user) 224 { 225 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 226 227 return client_open_for_user_using(user, server); 228 } 229 230 /* Open a file opening object via a named capability. */ 231 232 l4_cap_idx_t client_open_for_user_using(user_t user, l4_cap_idx_t server) 233 { 234 if (l4_is_invalid_cap(server)) 235 return L4_INVALID_CAP; 236 237 l4_cap_idx_t opener; 238 long err = file_open_for_user(user, server, &opener); 239 240 if (err) 241 return L4_INVALID_CAP; 242 243 return opener; 244 } 245 246 247 248 /* Close a filesystem object. */ 249 250 void client_close(file_t *file) 251 { 252 if (file == NULL) 253 return; 254 255 if (file->metadata != NULL) 256 client_close(file->metadata); 257 258 file_flush(file); 259 file_close(file); 260 free(file); 261 } 262 263 264 265 /* Obtain a stream from the environment. */ 266 267 file_t *client_get_stream(const char *name, flags_t flags) 268 { 269 l4_cap_idx_t ref = l4re_env_get_cap(name); 270 file_t *stream; 271 272 if (l4_is_invalid_cap(ref)) 273 return NULL; 274 275 stream = (file_t *) malloc(sizeof(file_t)); 276 277 file_init(stream); 278 stream->flags = flags; 279 stream->ref = ref; 280 281 /* Restore the state of the stream. */ 282 283 client_sync_stream(stream); 284 285 /* Enforce blocking if necessary. 286 NOTE: Ignoring any event subscription error. */ 287 288 if (!(flags & O_NONBLOCK)) 289 client_set_blocking(stream, file_notify_flags(flags)); 290 291 return stream; 292 } 293 294 /* Initialise the stream data position to the end of any existing data for an 295 output stream. For an input stream, initialise the position to the end of 296 consumed data, preserving any unconsumed data. */ 297 298 long client_sync_stream(file_t *file) 299 { 300 if (!client_opened(file)) 301 return -L4_EINVAL; 302 303 return file_refresh(file); 304 } 305 306 307 308 /* Open a filesystem object. */ 309 310 file_t *client_open(const char *name, flags_t flags) 311 { 312 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 313 314 return client_open_using(name, flags, server); 315 } 316 317 /* Open a filesystem object via a named capability. */ 318 319 file_t *client_open_using(const char *name, flags_t flags, l4_cap_idx_t server) 320 { 321 if (l4_is_invalid_cap(server)) 322 return NULL; 323 324 file_t *file = (file_t *) malloc(sizeof(file_t)); 325 326 if (file == NULL) 327 return NULL; 328 329 /* Return any allocated structure even if an error occurs. */ 330 331 file->error = file_open(file, name, flags, server); 332 return file; 333 } 334 335 336 337 /* Open a directory listing stream via the given named directory. */ 338 339 file_t *client_opendir(const char *name) 340 { 341 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 342 343 return client_opendir_using(name, server); 344 } 345 346 /* Open a directory listing stream via the given named directory and a named 347 capability, returning the directory object with a metadata file object 348 reference to the directory listing stream. */ 349 350 file_t *client_opendir_using(const char *name, l4_cap_idx_t server) 351 { 352 file_t *file = client_open_using(name, O_DIRECTORY, server); 353 354 /* Return the directory structure itself for error handling. */ 355 356 if (!client_opened(file)) 357 return file; 358 359 file->metadata = client_opendir_reader(file); 360 361 /* Return the directory even if an error occurs. */ 362 363 return file; 364 } 365 366 367 368 /* Open a directory listing stream via the given directory. */ 369 370 file_t *client_opendir_reader(file_t *file) 371 { 372 file_t *reader = (file_t *) malloc(sizeof(file_t)); 373 374 if (reader == NULL) 375 return NULL; 376 377 /* Return any allocated structure even if an error occurs. */ 378 379 reader->error = directory_opendir(file, reader); 380 381 /* Set blocking read mode to be able to conveniently read directory entries 382 from the stream. If this fails, the error is set on the structure, but the 383 stream will be open. */ 384 385 if (!reader->error) 386 reader->error = client_set_blocking(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 387 388 return reader; 389 } 390 391 392 393 /* Open another instance of an opened filesystem object. */ 394 395 file_t *client_reopen(file_t *file, flags_t flags) 396 { 397 if (file == NULL) 398 return NULL; 399 400 file_t *new_file = (file_t *) malloc(sizeof(file_t)); 401 402 if (new_file == NULL) 403 return NULL; 404 405 /* Return any allocated structure even if an error occurs. */ 406 407 new_file->error = file_reopen(file, new_file, flags); 408 return new_file; 409 } 410 411 412 413 /* Open a pipe object, returning any error condition. */ 414 415 long client_pipe(file_t **reader, file_t **writer, flags_t flags) 416 { 417 l4_cap_idx_t server = l4re_env_get_cap(ENV_PIPE_SERVER_NAME); 418 419 return client_pipe_using(reader, writer, flags, server); 420 } 421 422 long client_pipe_using(file_t **reader, file_t **writer, flags_t flags, l4_cap_idx_t server) 423 { 424 *reader = NULL; 425 *writer = NULL; 426 427 if (l4_is_invalid_cap(server)) 428 return -L4_EINVAL; 429 430 *reader = (file_t *) malloc(sizeof(file_t)); 431 432 if (*reader == NULL) 433 return -L4_ENOMEM; 434 435 *writer = (file_t *) malloc(sizeof(file_t)); 436 437 if (*writer == NULL) 438 { 439 free(*reader); 440 return -L4_ENOMEM; 441 } 442 443 long err = pipe_open(DEFAULT_PIPE_SIZE, *reader, *writer, flags, server); 444 445 /* Set blocking if successful and non-blocking is not indicated. */ 446 447 if (!err && !(flags & O_NONBLOCK)) 448 { 449 err = client_set_blocking(*reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 450 if (!err) 451 err = client_set_blocking(*writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); 452 } 453 454 if (err) 455 { 456 free(*reader); 457 free(*writer); 458 } 459 460 return err; 461 } 462 463 464 465 /* Determine whether a file has been successfully opened. */ 466 467 int client_opened(file_t *file) 468 { 469 return (file != NULL) && !file->error; 470 } 471 472 473 474 /* Make a directory in the filesystem. */ 475 476 long client_mkdir(const char *path, mode_t mode) 477 { 478 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 479 480 return client_mkdir_using(path, mode, server); 481 } 482 483 /* Make a directory in the filesystem via a named capability. */ 484 485 long client_mkdir_using(const char *path, mode_t mode, l4_cap_idx_t server) 486 { 487 return file_mkdir(path, mode, server); 488 } 489 490 491 492 /* Remove a file from the filesystem. */ 493 494 long client_remove(const char *path) 495 { 496 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 497 498 return client_remove_using(path, server); 499 } 500 501 /* Remove a file from the filesystem via a named capability. */ 502 503 long client_remove_using(const char *path, l4_cap_idx_t server) 504 { 505 return file_remove(path, server); 506 } 507 508 509 510 /* Rename a file in the filesystem. */ 511 512 long client_rename(const char *source, const char *target) 513 { 514 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 515 516 return client_rename_using(source, target, server); 517 } 518 519 /* Rename a file in the filesystem via a named capability. */ 520 521 long client_rename_using(const char *source, const char *target, l4_cap_idx_t server) 522 { 523 return file_rename(source, target, server); 524 } 525 526 527 528 /* Obtain filesystem object statistics. */ 529 530 long client_stat(const char *path, struct stat *st) 531 { 532 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 533 534 return client_stat_using(path, st, server); 535 } 536 537 /* Obtain object statistics from the filesystem via a named capability. */ 538 539 long client_stat_using(const char *path, struct stat *st, l4_cap_idx_t server) 540 { 541 return file_stat(path, st, server); 542 } 543 544 545 546 /* Obtain the current region of a pipe, updating the extent of populated data 547 and only changing the current position if no populated data exists. */ 548 549 long client_current_region(file_t *file) 550 { 551 if (!client_opened(file)) 552 return -L4_EINVAL; 553 554 return pipe_current(file); 555 } 556 557 558 559 /* Flush data explicitly to the filesystem object. */ 560 561 long client_flush(file_t *file) 562 { 563 if (!client_opened(file)) 564 return -L4_EINVAL; 565 566 /* Flush and retain most buffer settings. */ 567 568 return file_flush(file); 569 } 570 571 572 573 /* Map a memory region to a file. */ 574 575 void *client_mmap(file_t *file, offset_t position, offset_t length, 576 offset_t start_visible, offset_t end_visible, 577 rm_flags_t region_flags) 578 { 579 if (!client_opened(file) || file_mmap(file, position, length, start_visible, 580 end_visible, region_flags)) 581 return NULL; 582 583 return file->memory; 584 } 585 586 587 588 /* Obtain the next region of a pipe. */ 589 590 long client_next_region(file_t *file) 591 { 592 if (!client_opened(file)) 593 return -L4_EINVAL; 594 595 return pipe_next(file); 596 } 597 598 599 600 /* Close a notifier object. */ 601 602 void client_notifier_close(notifier_t *notifier) 603 { 604 notify_close(notifier); 605 } 606 607 /* Obtain a local notifier object. */ 608 609 notifier_t *client_notifier_local() 610 { 611 return notify_get_local(); 612 } 613 614 /* Obtain a task-wide notifier object. */ 615 616 notifier_t *client_notifier_task() 617 { 618 return notify_get_task(); 619 } 620 621 622 623 /* Read a directory entry. This must be freed by the caller after use. */ 624 625 struct dirent *client_readdir(file_t *file) 626 { 627 if ((file == NULL) || (file->metadata == NULL)) 628 return NULL; 629 630 char buffer[DIRENT_CORE_SIZE]; 631 offset_t nread = client_read(file->metadata, buffer, DIRENT_CORE_SIZE); 632 633 /* Stop if no new structure can be successfully read. */ 634 635 if (nread != DIRENT_CORE_SIZE) 636 return NULL; 637 638 struct dirent *dirent = (struct dirent *) buffer; 639 offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE; 640 641 /* Allocate a buffer for the complete structure. */ 642 643 char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char)); 644 645 if (entry == NULL) 646 return NULL; 647 648 /* Copy the start of the entry into a new buffer. */ 649 650 memcpy(entry, buffer, DIRENT_CORE_SIZE); 651 652 /* Append to the entry buffer. */ 653 654 char *current = entry + DIRENT_CORE_SIZE; 655 656 nread = client_read(file->metadata, current, remaining); 657 658 /* Stop if no complete structure can be successfully read. */ 659 660 if (nread != remaining) 661 { 662 free(entry); 663 return NULL; 664 } 665 666 return (struct dirent *) entry; 667 } 668 669 /* Rewind directory reading. */ 670 671 void client_rewinddir(file_t *file) 672 { 673 if ((file == NULL) || (file->metadata == NULL)) 674 return; 675 676 client_close(file->metadata); 677 678 file->metadata = client_opendir_reader(file); 679 } 680 681 682 683 /* Read from the filesystem object into the buffer provided. */ 684 685 offset_t client_read(file_t *file, void *buf, offset_t count) 686 { 687 if (!client_opened(file)) 688 return 0; 689 690 /* Map memory if none has been mapped so far. */ 691 692 if (_map_memory(file, count) == NULL) 693 return 0; 694 695 /* Amount available in the descriptor buffer already. */ 696 697 offset_t available = file_data_available(file); 698 offset_t to_transfer, total = 0; 699 700 while (count > 0) 701 { 702 /* If there is no data, try and obtain more data. */ 703 704 if (!available) 705 { 706 /* Flush any unwritten data, preparing to read from the file position at 707 the end of the data, and returning if no new data is available. */ 708 709 if (!_access_blocking(file, file_data_end_position(file), 1)) 710 break; 711 712 available = file_data_available(file); 713 714 if (!available) 715 break; 716 } 717 718 /* Transfer data into the supplied buffer. */ 719 720 to_transfer = available <= count ? available : count; 721 722 file_data_read(file, (char *) buf, to_transfer); 723 724 /* Update counters. */ 725 726 available -= to_transfer; 727 728 count -= to_transfer; 729 total += to_transfer; 730 731 buf = ((char *) buf + to_transfer); 732 } 733 734 return total; 735 } 736 737 738 739 /* Ensure that the buffer can provide the needed data. */ 740 741 offset_t client_seek(file_t *file, offset_t offset, int whence) 742 { 743 if (!client_opened(file)) 744 return 0; 745 746 offset_t position, current = file_data_current_position(file), change; 747 748 switch (whence) 749 { 750 case SEEK_SET: 751 position = offset; 752 break; 753 754 case SEEK_CUR: 755 position = current + offset; 756 break; 757 758 case SEEK_END: 759 position = file->size + offset; 760 break; 761 762 default: 763 /* NOTE: Set errno to EINVAL. */ 764 return current; 765 } 766 767 /* Retain the current position if unchanged. */ 768 769 if (position == current) 770 return position; 771 772 /* Move forward in the file. */ 773 774 if (position > current) 775 { 776 change = position - current; 777 778 /* Move towards the end of available data. 779 Request new data if not enough is available. */ 780 781 if (change <= file_data_available(file)) 782 { 783 file->data_current += change; 784 return position; 785 } 786 } 787 788 /* Move backward in the file. */ 789 790 else 791 { 792 change = current - position; 793 794 /* Move towards the start of available data. 795 Request new data if moving beyond the start of the data. */ 796 797 if (change <= file->data_current) 798 { 799 file->data_current -= change; 800 return position; 801 } 802 } 803 804 /* Handle unwritten data and reset the buffer for reading. */ 805 806 if (_access(file, position)) 807 return current; 808 809 return position; 810 } 811 812 813 814 /* Set or unset blocking access for a file. */ 815 816 long client_set_blocking(file_t *file, notify_flags_t flags) 817 { 818 long err; 819 820 if (file->can_block == flags) 821 return L4_EOK; 822 823 /* Since blocking access is used with specific file notifications, the 824 per-task notifier is used. */ 825 826 notifier_t *notifier = client_notifier_task(); 827 828 if (flags) 829 err = client_subscribe(file, flags, notifier); 830 else 831 err = client_unsubscribe(file, notifier); 832 833 if (err) 834 return err; 835 836 file->can_block = flags; 837 return L4_EOK; 838 } 839 840 841 842 /* Subscribe to events concerning a file. */ 843 844 long client_subscribe(file_t *file, notify_flags_t flags, notifier_t *notifier) 845 { 846 if (!client_opened(file)) 847 return -L4_EINVAL; 848 849 return notify_subscribe(file_notifiable(file), flags, notifier); 850 } 851 852 853 854 /* Return the current position in the file. */ 855 856 offset_t client_tell(file_t *file) 857 { 858 if (!client_opened(file)) 859 return -L4_EINVAL; 860 861 return file_data_current_position(file); 862 } 863 864 865 866 /* Unsubscribe from events concerning a file. */ 867 868 long client_unsubscribe(file_t *file, notifier_t *notifier) 869 { 870 if (!client_opened(file)) 871 return -L4_EINVAL; 872 873 return notify_unsubscribe(file_notifiable(file), notifier); 874 } 875 876 877 878 /* Wait for events involving a specific file. */ 879 880 long client_wait_file(file_t *file, notifier_t *notifier) 881 { 882 if (!client_opened(file)) 883 return -L4_EINVAL; 884 885 return file_notify_wait_file(file, notifier); 886 } 887 888 /* Wait for events concerning files, referencing a file object if an event is 889 delivered. */ 890 891 long client_wait_files(file_t **file, notifier_t *notifier) 892 { 893 return file_notify_wait_files(file, notifier); 894 } 895 896 897 898 /* Write to the filesystem object from the buffer provided. */ 899 900 offset_t client_write(file_t *file, const void *buf, offset_t count) 901 { 902 if (!client_opened(file)) 903 return 0; 904 905 /* Map memory if none has been mapped so far. */ 906 907 if (_map_memory(file, count) == NULL) 908 return 0; 909 910 /* Attempt to ensure that the file can accept the amount of data to be 911 written. This may not resize to the needed amount if a file has a fixed 912 size, but data will still be written to any available space. */ 913 914 offset_t needed_size = file_data_current_position(file) + count; 915 916 if (file->object_flags & OBJECT_HAS_SIZE) 917 { 918 if (file->size < needed_size) 919 { 920 file_resize(file, needed_size); 921 922 if (file->size < needed_size) 923 count = file->size - file_data_current_position(file); 924 } 925 } 926 927 /* Space remaining in the descriptor buffer. */ 928 929 offset_t space = file_data_space(file); 930 offset_t to_transfer, total = 0; 931 932 while (count > 0) 933 { 934 /* If no space is available, try and send data, reset the buffer. */ 935 936 if (!space) 937 { 938 /* Flush any unwritten data and continue writing from the current data 939 position. */ 940 941 if (!_access_blocking(file, file_data_current_position(file), 0)) 942 break; 943 944 space = file_data_space(file); 945 } 946 947 /* Transfer data into the supplied buffer. */ 948 949 to_transfer = space <= count ? space : count; 950 951 file_data_write(file, (char *) buf, to_transfer); 952 953 /* Update counters. */ 954 955 space -= to_transfer; 956 957 count -= to_transfer; 958 total += to_transfer; 959 960 buf = ((char *) buf + to_transfer); 961 } 962 963 return total; 964 } 965 966 // vim: tabstop=2 expandtab shiftwidth=2