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