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