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