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