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