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