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 err = client_next_region(file); 144 if (err) 145 return err; 146 147 file->data_current = 0; 148 return L4_EOK; 149 } 150 151 /* Within the current pipe region, synchronise with the pipe object. */ 152 153 else 154 { 155 if (initial) 156 return L4_EOK; 157 else 158 return client_current_region(file); 159 } 160 } 161 } 162 163 164 165 /* Return whether an operation on file should block for more content or more 166 space. A file must be configured for blocking, not be closed, and must either 167 be lacking content (if reading) or space (if writing). */ 168 169 static int _operation_blocking(file_t *file, int reading) 170 { 171 return (file->can_block && !(file->notifiable.notifications & NOTIFY_PEER_CLOSED) && ( 172 (reading && !file_data_available(file)) || 173 (!reading && !file_data_space(file)))); 174 } 175 176 177 178 /* Return whether an access could occur, blocking if necessary. */ 179 180 static int _access_blocking(file_t *file, offset_t position, int reading) 181 { 182 long err; 183 184 /* Attempt to access the position, handling an error condition or a blocking 185 condition. */ 186 187 while ((err = _access(file, position)) || _operation_blocking(file, reading)) 188 { 189 position = file->data_current; 190 191 /* Exit if blocking is not configured or suitable. */ 192 193 if ((err && (err != -L4_EBUSY)) || !file->can_block) 194 return 0; 195 196 /* Handle an inability to access by blocking, exiting if waiting failed. */ 197 198 if (client_wait_file(file, client_notifier_task())) 199 return 0; 200 } 201 202 return 1; 203 } 204 205 206 207 /* Ensure that memory is mapped for accessing the given file, using the 208 indicated count as a region size hint. */ 209 210 static void *_map_memory(file_t *file, offset_t count) 211 { 212 if (file->memory == NULL) 213 { 214 if (file->object_flags & OBJECT_SUPPORTS_MMAP) 215 return client_mmap(file, client_tell(file), count, 0, 0, 216 file_region_flags(file->flags)); 217 else if (pipe_current(file)) 218 return NULL; 219 } 220 221 return file->memory; 222 } 223 224 225 226 /* Open a file opening object. */ 227 228 l4_cap_idx_t client_open_for_user(user_t user) 229 { 230 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 231 232 return client_open_for_user_using(user, server); 233 } 234 235 /* Open a file opening object via a named capability. */ 236 237 l4_cap_idx_t client_open_for_user_using(user_t user, l4_cap_idx_t server) 238 { 239 if (l4_is_invalid_cap(server)) 240 return L4_INVALID_CAP; 241 242 l4_cap_idx_t opener; 243 long err = file_open_for_user(user, server, &opener); 244 245 if (err) 246 return L4_INVALID_CAP; 247 248 return opener; 249 } 250 251 252 253 /* Close a filesystem object. */ 254 255 void client_close(file_t *file) 256 { 257 if (file == NULL) 258 return; 259 260 file_flush(file); 261 file_close(file); 262 free(file); 263 } 264 265 266 267 /* Obtain a stream from the environment. */ 268 269 file_t *client_get_stream(const char *name, flags_t flags) 270 { 271 l4_cap_idx_t ref = l4re_env_get_cap(name); 272 file_t *stream; 273 274 if (l4_is_invalid_cap(ref)) 275 return NULL; 276 277 stream = (file_t *) malloc(sizeof(file_t)); 278 279 file_init(stream); 280 stream->flags = flags; 281 stream->ref = ref; 282 283 /* Test for pipe-based access, switching to memory mapped access if not 284 supported. */ 285 286 long err = client_current_region(stream); 287 288 if (err == -L4_EBADPROTO) 289 stream->object_flags |= OBJECT_SUPPORTS_MMAP; 290 291 /* Enforce blocking if necessary. 292 NOTE: Ignoring any event subscription error. */ 293 294 if (!(flags & O_NONBLOCK)) 295 { 296 notify_flags_t nflags = 0; 297 298 if ((flags & O_WRONLY) || (flags & O_RDWR)) 299 nflags |= NOTIFY_SPACE_AVAILABLE; 300 301 if ((flags & O_RDONLY) || (flags & O_RDWR)) 302 nflags |= NOTIFY_CONTENT_AVAILABLE; 303 304 client_set_blocking(stream, nflags | NOTIFY_PEER_CLOSED); 305 } 306 307 return stream; 308 } 309 310 311 312 /* Open a filesystem object. */ 313 314 file_t *client_open(const char *name, flags_t flags) 315 { 316 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 317 318 return client_open_using(name, flags, server); 319 } 320 321 /* Open a filesystem object via a named capability. */ 322 323 file_t *client_open_using(const char *name, flags_t flags, l4_cap_idx_t server) 324 { 325 if (l4_is_invalid_cap(server)) 326 return NULL; 327 328 file_t *file = (file_t *) malloc(sizeof(file_t)); 329 330 if (file == NULL) 331 return NULL; 332 333 /* Return any allocated structure even if an error occurs. */ 334 335 file->error = file_open(file, name, flags, server); 336 return file; 337 } 338 339 340 341 /* Open a directory listing stream via the given named directory. */ 342 343 file_t *client_opendir(const char *name) 344 { 345 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 346 347 return client_opendir_using(name, server); 348 } 349 350 /* Open a directory listing stream via the given named directory and a named 351 capability. */ 352 353 file_t *client_opendir_using(const char *name, l4_cap_idx_t server) 354 { 355 file_t *file = client_open_using(name, O_DIRECTORY, server); 356 357 if (file == NULL) 358 return NULL; 359 360 /* Return the directory structure itself for error handling. */ 361 362 if (!client_opened(file)) 363 return file; 364 365 file_t *reader = client_opendir_at(file); 366 367 /* Release the directory and return the reader even if an error occurs. */ 368 369 client_close(file); 370 return reader; 371 } 372 373 374 375 /* Open a directory listing stream via the given directory. */ 376 377 file_t *client_opendir_at(file_t *file) 378 { 379 file_t *reader = (file_t *) malloc(sizeof(file_t)); 380 381 if (reader == NULL) 382 return NULL; 383 384 /* Return any allocated structure even if an error occurs. */ 385 386 reader->error = directory_opendir(file, reader); 387 388 /* Set blocking read mode to be able to conveniently read directory entries 389 from the stream. If this fails, the error is set on the structure, but the 390 stream will be open. */ 391 392 if (!reader->error) 393 reader->error = client_set_blocking(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 394 395 return reader; 396 } 397 398 399 400 /* Open another instance of an opened filesystem object. */ 401 402 file_t *client_reopen(file_t *file, flags_t flags) 403 { 404 if (file == NULL) 405 return NULL; 406 407 file_t *new_file = (file_t *) malloc(sizeof(file_t)); 408 409 if (new_file == NULL) 410 return NULL; 411 412 /* Return any allocated structure even if an error occurs. */ 413 414 new_file->error = file_reopen(file, new_file, flags); 415 return new_file; 416 } 417 418 419 420 /* Open a pipe object, returning any error condition. */ 421 422 long client_pipe(file_t **reader, file_t **writer, flags_t flags) 423 { 424 l4_cap_idx_t server = l4re_env_get_cap(ENV_PIPE_SERVER_NAME); 425 426 return client_pipe_using(reader, writer, flags, server); 427 } 428 429 long client_pipe_using(file_t **reader, file_t **writer, flags_t flags, l4_cap_idx_t server) 430 { 431 *reader = NULL; 432 *writer = NULL; 433 434 if (l4_is_invalid_cap(server)) 435 return -L4_EINVAL; 436 437 *reader = (file_t *) malloc(sizeof(file_t)); 438 439 if (*reader == NULL) 440 return -L4_ENOMEM; 441 442 *writer = (file_t *) malloc(sizeof(file_t)); 443 444 if (*writer == NULL) 445 { 446 free(*reader); 447 return -L4_ENOMEM; 448 } 449 450 long err = pipe_open(DEFAULT_PIPE_SIZE, *reader, *writer, server); 451 452 /* Set blocking if successful and non-blocking is not indicated. */ 453 454 if (!err && !(flags & O_NONBLOCK)) 455 { 456 err = client_set_blocking(*reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 457 if (!err) 458 err = client_set_blocking(*writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); 459 } 460 461 if (err) 462 { 463 free(*reader); 464 free(*writer); 465 } 466 467 return err; 468 } 469 470 471 472 /* Determine whether a file has been successfully opened. */ 473 474 int client_opened(file_t *file) 475 { 476 return (file != NULL) && !file->error; 477 } 478 479 480 481 /* Make a directory in the filesystem. */ 482 483 long client_mkdir(const char *path, mode_t mode) 484 { 485 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 486 487 return client_mkdir_using(path, mode, server); 488 } 489 490 /* Make a directory in the filesystem via a named capability. */ 491 492 long client_mkdir_using(const char *path, mode_t mode, l4_cap_idx_t server) 493 { 494 return file_mkdir(path, mode, server); 495 } 496 497 498 499 /* Remove a file from the filesystem. */ 500 501 long client_remove(const char *path) 502 { 503 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 504 505 return client_remove_using(path, server); 506 } 507 508 /* Remove a file from the filesystem via a named capability. */ 509 510 long client_remove_using(const char *path, l4_cap_idx_t server) 511 { 512 return file_remove(path, server); 513 } 514 515 516 517 /* Rename a file in the filesystem. */ 518 519 long client_rename(const char *source, const char *target) 520 { 521 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 522 523 return client_rename_using(source, target, server); 524 } 525 526 /* Rename a file in the filesystem via a named capability. */ 527 528 long client_rename_using(const char *source, const char *target, l4_cap_idx_t server) 529 { 530 return file_rename(source, target, server); 531 } 532 533 534 535 /* Obtain filesystem object statistics. */ 536 537 long client_stat(const char *path, struct stat *st) 538 { 539 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 540 541 return client_stat_using(path, st, server); 542 } 543 544 /* Obtain object statistics from the filesystem via a named capability. */ 545 546 long client_stat_using(const char *path, struct stat *st, l4_cap_idx_t server) 547 { 548 return file_stat(path, st, server); 549 } 550 551 552 553 /* Obtain the current region of a pipe. */ 554 555 long client_current_region(file_t *file) 556 { 557 if (!client_opened(file)) 558 return -L4_EINVAL; 559 560 long err = pipe_current(file); 561 562 if (err) 563 return err; 564 565 /* Handle any case where the current region has been exhausted. */ 566 567 if (!file_populated_span(file)) 568 file->data_current = 0; 569 570 return L4_EOK; 571 } 572 573 574 575 /* Flush data explicitly to the filesystem object. */ 576 577 long client_flush(file_t *file) 578 { 579 if (!client_opened(file)) 580 return -L4_EINVAL; 581 582 /* Flush and retain most buffer settings. */ 583 584 return file_flush(file); 585 } 586 587 588 589 /* Map a memory region to a file. */ 590 591 void *client_mmap(file_t *file, offset_t position, offset_t length, 592 offset_t start_visible, offset_t end_visible, 593 rm_flags_t region_flags) 594 { 595 if (!client_opened(file) || file_mmap(file, position, length, start_visible, 596 end_visible, region_flags)) 597 return NULL; 598 599 return file->memory; 600 } 601 602 603 604 /* Obtain the next region of a pipe. */ 605 606 long client_next_region(file_t *file) 607 { 608 if (!client_opened(file)) 609 return -L4_EINVAL; 610 611 return pipe_next(file); 612 } 613 614 615 616 /* Close a notifier object. */ 617 618 void client_notifier_close(notifier_t *notifier) 619 { 620 notify_close(notifier); 621 } 622 623 /* Obtain a local notifier object. */ 624 625 notifier_t *client_notifier_local() 626 { 627 return notify_get_local(); 628 } 629 630 /* Obtain a task-wide notifier object. */ 631 632 notifier_t *client_notifier_task() 633 { 634 return notify_get_task(); 635 } 636 637 638 639 /* Read a directory entry. This must be freed by the caller after use. */ 640 641 struct dirent *client_readdir(file_t *file) 642 { 643 char buffer[DIRENT_CORE_SIZE]; 644 offset_t nread = client_read(file, buffer, DIRENT_CORE_SIZE); 645 646 /* Stop if no new structure can be successfully read. */ 647 648 if (nread != DIRENT_CORE_SIZE) 649 return NULL; 650 651 struct dirent *dirent = (struct dirent *) buffer; 652 offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE; 653 654 /* Allocate a buffer for the complete structure. */ 655 656 char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char)); 657 658 if (entry == NULL) 659 return NULL; 660 661 /* Copy the start of the entry into a new buffer. */ 662 663 memcpy(entry, buffer, DIRENT_CORE_SIZE); 664 665 /* Append to the entry buffer. */ 666 667 char *current = entry + DIRENT_CORE_SIZE; 668 669 nread = client_read(file, current, remaining); 670 671 /* Stop if no complete structure can be successfully read. */ 672 673 if (nread != remaining) 674 { 675 free(entry); 676 return NULL; 677 } 678 679 return (struct dirent *) entry; 680 } 681 682 683 684 /* Read from the filesystem object into the buffer provided. */ 685 686 offset_t client_read(file_t *file, void *buf, offset_t count) 687 { 688 if (!client_opened(file)) 689 return 0; 690 691 /* Map memory if none has been mapped so far. */ 692 693 if (_map_memory(file, count) == NULL) 694 return 0; 695 696 /* Amount available in the descriptor buffer already. */ 697 698 offset_t available = file_data_available(file); 699 offset_t to_transfer, total = 0; 700 701 while (count > 0) 702 { 703 /* If there is no data, try and obtain more data. */ 704 705 if (!available) 706 { 707 /* Flush any unwritten data, preparing to read from the file position at 708 the end of the data, and returning if no new data is available. */ 709 710 if (!_access_blocking(file, file_data_end_position(file), 1)) 711 break; 712 713 available = file_data_available(file); 714 715 if (!available) 716 break; 717 } 718 719 /* Transfer data into the supplied buffer. */ 720 721 to_transfer = available <= count ? available : count; 722 723 file_data_read(file, (char *) buf, to_transfer); 724 725 /* Update counters. */ 726 727 available -= to_transfer; 728 729 count -= to_transfer; 730 total += to_transfer; 731 732 buf = ((char *) buf + to_transfer); 733 } 734 735 return total; 736 } 737 738 739 740 /* Ensure that the buffer can provide the needed data. */ 741 742 offset_t client_seek(file_t *file, offset_t offset, int whence) 743 { 744 if (!client_opened(file)) 745 return 0; 746 747 offset_t position, current = file_data_current_position(file), change; 748 749 switch (whence) 750 { 751 case SEEK_SET: 752 position = offset; 753 break; 754 755 case SEEK_CUR: 756 position = current + offset; 757 break; 758 759 case SEEK_END: 760 position = file->size + offset; 761 break; 762 763 default: 764 /* NOTE: Set errno to EINVAL. */ 765 return current; 766 } 767 768 /* Retain the current position if unchanged. */ 769 770 if (position == current) 771 return position; 772 773 /* Move forward in the file. */ 774 775 if (position > current) 776 { 777 change = position - current; 778 779 /* Move towards the end of available data. 780 Request new data if not enough is available. */ 781 782 if (change <= file_data_available(file)) 783 { 784 file->data_current += change; 785 return position; 786 } 787 } 788 789 /* Move backward in the file. */ 790 791 else 792 { 793 change = current - position; 794 795 /* Move towards the start of available data. 796 Request new data if moving beyond the start of the data. */ 797 798 if (change <= file->data_current) 799 { 800 file->data_current -= change; 801 return position; 802 } 803 } 804 805 /* Handle unwritten data and reset the buffer for reading. */ 806 807 if (_access(file, position)) 808 return current; 809 810 return position; 811 } 812 813 814 815 /* Set or unset blocking access for a file. */ 816 817 long client_set_blocking(file_t *file, notify_flags_t flags) 818 { 819 long err; 820 821 if (file->can_block == flags) 822 return L4_EOK; 823 824 /* Since blocking access is used with specific file notifications, the 825 per-task notifier is used. */ 826 827 notifier_t *notifier = client_notifier_task(); 828 829 if (flags) 830 err = client_subscribe(file, flags, notifier); 831 else 832 err = client_unsubscribe(file, notifier); 833 834 if (err) 835 return err; 836 837 file->can_block = flags; 838 return L4_EOK; 839 } 840 841 842 843 /* Subscribe to events concerning a file. */ 844 845 long client_subscribe(file_t *file, notify_flags_t flags, notifier_t *notifier) 846 { 847 if (!client_opened(file)) 848 return -L4_EINVAL; 849 850 return notify_subscribe(file_notifiable(file), flags, notifier); 851 } 852 853 854 855 /* Return the current position in the file. */ 856 857 offset_t client_tell(file_t *file) 858 { 859 if (!client_opened(file)) 860 return -L4_EINVAL; 861 862 return file_data_current_position(file); 863 } 864 865 866 867 /* Unsubscribe from events concerning a file. */ 868 869 long client_unsubscribe(file_t *file, notifier_t *notifier) 870 { 871 if (!client_opened(file)) 872 return -L4_EINVAL; 873 874 return notify_unsubscribe(file_notifiable(file), notifier); 875 } 876 877 878 879 /* Wait for events involving a specific file. */ 880 881 long client_wait_file(file_t *file, notifier_t *notifier) 882 { 883 if (!client_opened(file)) 884 return -L4_EINVAL; 885 886 return file_notify_wait_file(file, notifier); 887 } 888 889 /* Wait for events concerning files, referencing a file object if an event is 890 delivered. */ 891 892 long client_wait_files(file_t **file, notifier_t *notifier) 893 { 894 return file_notify_wait_files(file, notifier); 895 } 896 897 898 899 /* Write to the filesystem object from the buffer provided. */ 900 901 offset_t client_write(file_t *file, const void *buf, offset_t count) 902 { 903 if (!client_opened(file)) 904 return 0; 905 906 /* Map memory if none has been mapped so far. */ 907 908 if (_map_memory(file, count) == NULL) 909 return 0; 910 911 /* Attempt to ensure that the file can accept the amount of data to be 912 written. This may not resize to the needed amount if a file has a fixed 913 size, but data will still be written to any available space. */ 914 915 offset_t needed_size = file_data_current_position(file) + count; 916 917 if (file->object_flags & OBJECT_HAS_SIZE) 918 { 919 if (file->size < needed_size) 920 { 921 file_resize(file, needed_size); 922 923 if (file->size < needed_size) 924 count = file->size - file_data_current_position(file); 925 } 926 } 927 928 /* Space remaining in the descriptor buffer. */ 929 930 offset_t space = file_data_space(file); 931 offset_t to_transfer, total = 0; 932 933 while (count > 0) 934 { 935 /* If no space is available, try and send data, reset the buffer. */ 936 937 if (!space) 938 { 939 /* Flush any unwritten data and continue writing from the current data 940 position. */ 941 942 if (!_access_blocking(file, file_data_current_position(file), 0)) 943 break; 944 945 space = file_data_space(file); 946 } 947 948 /* Transfer data into the supplied buffer. */ 949 950 to_transfer = space <= count ? space : count; 951 952 file_data_write(file, (char *) buf, to_transfer); 953 954 /* Update counters. */ 955 956 space -= to_transfer; 957 958 count -= to_transfer; 959 total += to_transfer; 960 961 buf = ((char *) buf + to_transfer); 962 } 963 964 return total; 965 } 966 967 // vim: tabstop=2 expandtab shiftwidth=2