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