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