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