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