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