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 <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 /* Where the position is outside the current region, re-map. */ 83 84 if ((position < file->start_pos) || (position >= file->end_pos)) 85 { 86 if (file_mmap(file, position, file_span(file), 0, 0, 87 file_region_flags(file->flags))) 88 return -L4_EIO; 89 } 90 91 /* Otherwise, flush any written data in the current region and update the 92 file size details. */ 93 94 else 95 { 96 err = client_flush(file); 97 if (err) 98 return err; 99 } 100 101 /* Update the current data offset. */ 102 103 file->data_current = position - file->start_pos; 104 105 return L4_EOK; 106 } 107 else 108 { 109 /* Handle the initial condition with no current region. */ 110 111 if (file->memory == NULL) 112 { 113 err = client_current_region(file); 114 if (err) 115 return err; 116 } 117 118 /* Strict conditions for region navigation in pipes. */ 119 120 if ((position < file->start_pos) || (position > file->end_pos)) 121 { 122 return -L4_EIO; 123 } 124 125 /* The next region is only available at the end of the mapped memory. */ 126 127 else if (position == file->end_pos) 128 { 129 err = client_next_region(file); 130 if (err) 131 return err; 132 133 file->data_current = 0; 134 return L4_EOK; 135 } 136 137 /* Within the current pipe region, synchronise with the pipe object. */ 138 139 else 140 { 141 return client_current_region(file); 142 } 143 } 144 } 145 146 147 148 /* Return whether an operation on file should block for more content or more 149 space. A file must be configured for blocking, not be closed, and must either 150 be lacking content (if reading) or space (if writing). */ 151 152 static int _operation_blocking(file_t *file, int reading) 153 { 154 return (file->can_block && !(file->notifiable.notifications & NOTIFY_PEER_CLOSED) && ( 155 (reading && !file_data_available(file)) || 156 (!reading && !file_data_space(file)))); 157 } 158 159 160 161 /* Return whether an access could occur, blocking if necessary. */ 162 163 static int _access_blocking(file_t *file, offset_t position, int reading) 164 { 165 long err; 166 167 /* Attempt to access the position, handling an error condition or a blocking 168 condition. */ 169 170 while ((err = _access(file, position)) || _operation_blocking(file, reading)) 171 { 172 position = file->data_current; 173 174 /* Exit if blocking is not configured or suitable. */ 175 176 if ((err && (err != -L4_EBUSY)) || !file->can_block) 177 return 0; 178 179 /* Handle an inability to access by blocking, exiting if waiting failed. */ 180 181 if (client_wait_file(file, client_notifier_task())) 182 return 0; 183 } 184 185 return 1; 186 } 187 188 189 190 /* Ensure that memory is mapped for accessing the given file, using the 191 indicated count as a region size hint. */ 192 193 static void *_map_memory(file_t *file, offset_t count) 194 { 195 if (file->memory == NULL) 196 { 197 if (file->object_flags & OBJECT_SUPPORTS_MMAP) 198 return client_mmap(file, client_tell(file), count, 0, 0, 199 file_region_flags(file->flags)); 200 else if (pipe_current(file)) 201 return NULL; 202 } 203 204 return file->memory; 205 } 206 207 208 209 /* Open a file opening object. */ 210 211 l4_cap_idx_t client_open_for_user(user_t user) 212 { 213 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 214 215 return client_open_for_user_using(user, server); 216 } 217 218 /* Open a file opening object via a named capability. */ 219 220 l4_cap_idx_t client_open_for_user_using(user_t user, l4_cap_idx_t server) 221 { 222 if (l4_is_invalid_cap(server)) 223 return L4_INVALID_CAP; 224 225 l4_cap_idx_t opener; 226 long err = file_open_for_user(user, server, &opener); 227 228 if (err) 229 return L4_INVALID_CAP; 230 231 return opener; 232 } 233 234 235 236 /* Close a filesystem object. */ 237 238 void client_close(file_t *file) 239 { 240 if (file == NULL) 241 return; 242 243 file_flush(file); 244 file_close(file); 245 free(file); 246 } 247 248 249 250 /* Open a filesystem object. */ 251 252 file_t *client_open(const char *name, flags_t flags) 253 { 254 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 255 256 return client_open_using(name, flags, server); 257 } 258 259 /* Open a filesystem object via a named capability. */ 260 261 file_t *client_open_using(const char *name, flags_t flags, l4_cap_idx_t server) 262 { 263 if (l4_is_invalid_cap(server)) 264 return NULL; 265 266 file_t *file = (file_t *) malloc(sizeof(file_t)); 267 268 if (file == NULL) 269 return NULL; 270 271 /* Return any allocated structure even if an error occurs. */ 272 273 file->error = file_open(file, name, flags, server); 274 return file; 275 } 276 277 278 279 /* Open a directory listing stream via the given named directory. */ 280 281 file_t *client_opendir(const char *name) 282 { 283 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 284 285 return client_opendir_using(name, server); 286 } 287 288 /* Open a directory listing stream via the given named directory and a named 289 capability. */ 290 291 file_t *client_opendir_using(const char *name, l4_cap_idx_t server) 292 { 293 file_t *file = client_open_using(name, O_DIRECTORY, server); 294 295 if (file == NULL) 296 return NULL; 297 298 /* Return the directory structure itself for error handling. */ 299 300 if (!client_opened(file)) 301 return file; 302 303 file_t *reader = client_opendir_at(file); 304 305 /* Release the directory and return the reader even if an error occurs. */ 306 307 client_close(file); 308 return reader; 309 } 310 311 312 313 /* Open a directory listing stream via the given directory. */ 314 315 file_t *client_opendir_at(file_t *file) 316 { 317 file_t *reader = (file_t *) malloc(sizeof(file_t)); 318 319 if (reader == NULL) 320 return NULL; 321 322 /* Return any allocated structure even if an error occurs. */ 323 324 reader->error = directory_opendir(file, reader); 325 326 /* Set blocking read mode to be able to conveniently read directory entries 327 from the stream. If this fails, the error is set on the structure, but the 328 stream will be open. */ 329 330 if (!reader->error) 331 reader->error = client_set_blocking(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 332 333 return reader; 334 } 335 336 337 338 /* Open another instance of an opened filesystem object. */ 339 340 file_t *client_reopen(file_t *file, flags_t flags) 341 { 342 if (file == NULL) 343 return NULL; 344 345 file_t *new_file = (file_t *) malloc(sizeof(file_t)); 346 347 if (new_file == NULL) 348 return NULL; 349 350 /* Return any allocated structure even if an error occurs. */ 351 352 new_file->error = file_reopen(file, new_file, flags); 353 return new_file; 354 } 355 356 357 358 /* Open a pipe object, returning any error condition. */ 359 360 long client_pipe(file_t **reader, file_t **writer, flags_t flags) 361 { 362 l4_cap_idx_t server = l4re_env_get_cap(ENV_PIPE_SERVER_NAME); 363 364 return client_pipe_using(reader, writer, flags, server); 365 } 366 367 long client_pipe_using(file_t **reader, file_t **writer, flags_t flags, l4_cap_idx_t server) 368 { 369 *reader = NULL; 370 *writer = NULL; 371 372 if (l4_is_invalid_cap(server)) 373 return -L4_EINVAL; 374 375 *reader = (file_t *) malloc(sizeof(file_t)); 376 377 if (*reader == NULL) 378 return -L4_ENOMEM; 379 380 *writer = (file_t *) malloc(sizeof(file_t)); 381 382 if (*writer == NULL) 383 { 384 free(*reader); 385 return -L4_ENOMEM; 386 } 387 388 long err = pipe_open(DEFAULT_PIPE_SIZE, *reader, *writer, server); 389 390 /* Set blocking if successful and non-blocking is not indicated. */ 391 392 if (!err && !(flags & O_NONBLOCK)) 393 { 394 err = client_set_blocking(*reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); 395 if (!err) 396 err = client_set_blocking(*writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); 397 } 398 399 if (err) 400 { 401 free(*reader); 402 free(*writer); 403 } 404 405 return err; 406 } 407 408 409 410 /* Determine whether a file has been successfully opened. */ 411 412 int client_opened(file_t *file) 413 { 414 return (file != NULL) && !file->error; 415 } 416 417 418 419 /* Make a directory in the filesystem. */ 420 421 long client_mkdir(const char *path, mode_t mode) 422 { 423 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 424 425 return client_mkdir_using(path, mode, server); 426 } 427 428 /* Make a directory in the filesystem via a named capability. */ 429 430 long client_mkdir_using(const char *path, mode_t mode, l4_cap_idx_t server) 431 { 432 return file_mkdir(path, mode, server); 433 } 434 435 436 437 /* Remove a file from the filesystem. */ 438 439 long client_remove(const char *path) 440 { 441 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 442 443 return client_remove_using(path, server); 444 } 445 446 /* Remove a file from the filesystem via a named capability. */ 447 448 long client_remove_using(const char *path, l4_cap_idx_t server) 449 { 450 return file_remove(path, server); 451 } 452 453 454 455 /* Rename a file in the filesystem. */ 456 457 long client_rename(const char *source, const char *target) 458 { 459 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 460 461 return client_rename_using(source, target, server); 462 } 463 464 /* Rename a file in the filesystem via a named capability. */ 465 466 long client_rename_using(const char *source, const char *target, l4_cap_idx_t server) 467 { 468 return file_rename(source, target, server); 469 } 470 471 472 473 /* Obtain filesystem object statistics. */ 474 475 long client_stat(const char *path, struct stat *st) 476 { 477 l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME); 478 479 return client_stat_using(path, st, server); 480 } 481 482 /* Obtain object statistics from the filesystem via a named capability. */ 483 484 long client_stat_using(const char *path, struct stat *st, l4_cap_idx_t server) 485 { 486 return file_stat(path, st, server); 487 } 488 489 490 491 /* Obtain the current region of a pipe. */ 492 493 long client_current_region(file_t *file) 494 { 495 if (!client_opened(file)) 496 return -L4_EINVAL; 497 498 return pipe_current(file); 499 } 500 501 502 503 /* Flush data explicitly to the filesystem object. */ 504 505 long client_flush(file_t *file) 506 { 507 if (!client_opened(file)) 508 return -L4_EINVAL; 509 510 /* Flush and retain most buffer settings. */ 511 512 return file_flush(file); 513 } 514 515 516 517 /* Map a memory region to a file. */ 518 519 void *client_mmap(file_t *file, offset_t position, offset_t length, 520 offset_t start_visible, offset_t end_visible, 521 l4re_rm_flags_t region_flags) 522 { 523 if (!client_opened(file) || file_mmap(file, position, length, start_visible, 524 end_visible, region_flags)) 525 return NULL; 526 527 return file->memory; 528 } 529 530 531 532 /* Obtain the next region of a pipe. */ 533 534 long client_next_region(file_t *file) 535 { 536 if (!client_opened(file)) 537 return -L4_EINVAL; 538 539 return pipe_next(file); 540 } 541 542 543 544 /* Close a notifier object. */ 545 546 void client_notifier_close(file_notifier_t *notifier) 547 { 548 file_notify_close(notifier); 549 } 550 551 /* Obtain a local notifier object. */ 552 553 file_notifier_t *client_notifier_local() 554 { 555 return file_notify_local(); 556 } 557 558 /* Obtain a task-wide notifier object. */ 559 560 file_notifier_t *client_notifier_task() 561 { 562 return file_notify_task(); 563 } 564 565 566 567 /* Read a directory entry. This must be freed by the caller after use. */ 568 569 struct dirent *client_readdir(file_t *file) 570 { 571 char buffer[DIRENT_CORE_SIZE]; 572 offset_t nread = client_read(file, buffer, DIRENT_CORE_SIZE); 573 574 /* Stop if no new structure can be successfully read. */ 575 576 if (nread != DIRENT_CORE_SIZE) 577 return NULL; 578 579 struct dirent *dirent = (struct dirent *) buffer; 580 offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE; 581 582 /* Allocate a buffer for the complete structure. */ 583 584 char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char)); 585 586 if (entry == NULL) 587 return NULL; 588 589 /* Copy the start of the entry into a new buffer. */ 590 591 memcpy(entry, buffer, DIRENT_CORE_SIZE); 592 593 /* Append to the entry buffer. */ 594 595 char *current = entry + DIRENT_CORE_SIZE; 596 597 nread = client_read(file, current, remaining); 598 599 /* Stop if no complete structure can be successfully read. */ 600 601 if (nread != remaining) 602 { 603 free(entry); 604 return NULL; 605 } 606 607 return (struct dirent *) entry; 608 } 609 610 611 612 /* Read from the filesystem object into the buffer provided. */ 613 614 offset_t client_read(file_t *file, void *buf, offset_t count) 615 { 616 if (!client_opened(file)) 617 return 0; 618 619 /* Map memory if none has been mapped so far. */ 620 621 if (_map_memory(file, count) == NULL) 622 return 0; 623 624 /* Amount available in the descriptor buffer already. */ 625 626 offset_t available = file_data_available(file); 627 offset_t to_transfer, total = 0; 628 629 while (count > 0) 630 { 631 /* If there is no data, try and obtain more data. */ 632 633 if (!available) 634 { 635 /* Flush any unwritten data, preparing to read from the file position at 636 the end of the data, and returning if no new data is available. */ 637 638 if (!_access_blocking(file, file_data_end_position(file), 1)) 639 break; 640 641 available = file_data_available(file); 642 643 if (!available) 644 break; 645 } 646 647 /* Transfer data into the supplied buffer. */ 648 649 to_transfer = available <= count ? available : count; 650 651 file_data_read(file, (char *) buf, to_transfer); 652 653 /* Update counters. */ 654 655 available -= to_transfer; 656 657 count -= to_transfer; 658 total += to_transfer; 659 660 buf = ((char *) buf + to_transfer); 661 } 662 663 return total; 664 } 665 666 667 668 /* Ensure that the buffer can provide the needed data. */ 669 670 offset_t client_seek(file_t *file, offset_t offset, int whence) 671 { 672 if (!client_opened(file)) 673 return 0; 674 675 offset_t position, current = file_data_current_position(file), change; 676 677 switch (whence) 678 { 679 case SEEK_SET: 680 position = offset; 681 break; 682 683 case SEEK_CUR: 684 position = current + offset; 685 break; 686 687 case SEEK_END: 688 position = file->size + offset; 689 break; 690 691 default: 692 /* NOTE: Set errno to EINVAL. */ 693 return current; 694 } 695 696 /* Retain the current position if unchanged. */ 697 698 if (position == current) 699 return position; 700 701 /* Move forward in the file. */ 702 703 if (position > current) 704 { 705 change = position - current; 706 707 /* Move towards the end of available data. 708 Request new data if not enough is available. */ 709 710 if (change <= file_data_available(file)) 711 { 712 file->data_current += change; 713 return position; 714 } 715 } 716 717 /* Move backward in the file. */ 718 719 else 720 { 721 change = current - position; 722 723 /* Move towards the start of available data. 724 Request new data if moving beyond the start of the data. */ 725 726 if (change <= file->data_current) 727 { 728 file->data_current -= change; 729 return position; 730 } 731 } 732 733 /* Handle unwritten data and reset the buffer for reading. */ 734 735 if (_access(file, position)) 736 return current; 737 738 return position; 739 } 740 741 742 743 /* Set or unset blocking access for a file. */ 744 745 long client_set_blocking(file_t *file, notify_flags_t flags) 746 { 747 long err; 748 749 if (file->can_block == flags) 750 return L4_EOK; 751 752 /* Since blocking access is used with specific file notifications, the 753 per-task notifier is used. */ 754 755 file_notifier_t *notifier = client_notifier_task(); 756 757 if (flags) 758 err = client_subscribe(file, flags, notifier); 759 else 760 err = client_unsubscribe(file, notifier); 761 762 if (err) 763 return err; 764 765 file->can_block = flags; 766 return L4_EOK; 767 } 768 769 770 771 /* Subscribe from events concerning a file. */ 772 773 long client_subscribe(file_t *file, notify_flags_t flags, file_notifier_t *notifier) 774 { 775 if (!client_opened(file)) 776 return -L4_EINVAL; 777 778 return file_notify_subscribe(file, flags, notifier); 779 } 780 781 782 783 /* Return the current position in the file. */ 784 785 offset_t client_tell(file_t *file) 786 { 787 if (!client_opened(file)) 788 return -L4_EINVAL; 789 790 return file_data_current_position(file); 791 } 792 793 794 795 /* Unsubscribe from events concerning a file. */ 796 797 long client_unsubscribe(file_t *file, file_notifier_t *notifier) 798 { 799 if (!client_opened(file)) 800 return -L4_EINVAL; 801 802 return file_notify_unsubscribe(file, notifier); 803 } 804 805 806 807 /* Wait for events involving a specific file. */ 808 809 long client_wait_file(file_t *file, file_notifier_t *notifier) 810 { 811 if (!client_opened(file)) 812 return -L4_EINVAL; 813 814 return file_notify_wait_file(file, notifier); 815 } 816 817 /* Wait for events concerning files, referencing a file object if an event is 818 delivered. */ 819 820 long client_wait_files(file_t **file, file_notifier_t *notifier) 821 { 822 return file_notify_wait_files(file, notifier); 823 } 824 825 826 827 /* Write to the filesystem object from the buffer provided. */ 828 829 offset_t client_write(file_t *file, const void *buf, offset_t count) 830 { 831 if (!client_opened(file)) 832 return 0; 833 834 /* Map memory if none has been mapped so far. */ 835 836 if (_map_memory(file, count) == NULL) 837 return 0; 838 839 /* Attempt to ensure that the file can accept the amount of data to be 840 written. This may not resize to the needed amount if a file has a fixed 841 size, but data will still be written to any available space. */ 842 843 offset_t needed_size = file_data_current_position(file) + count; 844 845 if (file->object_flags & OBJECT_HAS_SIZE) 846 { 847 if (file->size < needed_size) 848 { 849 file_resize(file, needed_size); 850 851 if (file->size < needed_size) 852 count = file->size - file_data_current_position(file); 853 } 854 } 855 856 /* Space remaining in the descriptor buffer. */ 857 858 offset_t space = file_data_space(file); 859 offset_t to_transfer, total = 0; 860 861 while (count > 0) 862 { 863 /* If no space is available, try and send data, reset the buffer. */ 864 865 if (!space) 866 { 867 /* Flush any unwritten data and continue writing from the current data 868 position. */ 869 870 if (!_access_blocking(file, file_data_current_position(file), 0)) 871 break; 872 873 space = file_data_space(file); 874 } 875 876 /* Transfer data into the supplied buffer. */ 877 878 to_transfer = space <= count ? space : count; 879 880 file_data_write(file, (char *) buf, to_transfer); 881 882 /* Update counters. */ 883 884 space -= to_transfer; 885 886 count -= to_transfer; 887 total += to_transfer; 888 889 buf = ((char *) buf + to_transfer); 890 } 891 892 return total; 893 } 894 895 // vim: tabstop=2 expandtab shiftwidth=2