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