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