1 /* 2 * File access convenience functions. 3 * 4 * Copyright (C) 2021, 2022, 2023, 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 <ipc/cap_alloc.h> 23 #include <ipc/mem_ipc.h> 24 #include <notifier/notifier.h> 25 #include <systypes/fcntl.h> 26 #include <systypes/stat.h> 27 28 #include <stdio.h> 29 #include <string.h> 30 31 #include "dataspace_client.h" 32 #include "directory_client.h" 33 #include "file_client.h" 34 #include "filesystem_client.h" 35 #include "flush_client.h" 36 #include "opener_client.h" 37 #include "opener_context_client.h" 38 #include "pipe_client.h" 39 #include "pipe_opener_client.h" 40 #include "mapped_file_client.h" 41 42 #include "file.h" 43 44 45 46 /* Update the extent of the file in a region using the region start and end 47 positions and the file size. */ 48 49 static void _update_extent(file_t *file) 50 { 51 /* Handle files ending after or within the region. */ 52 53 if (file->size > file->start_pos) 54 { 55 if (file->size > file->end_pos) 56 file->data_end = file->end_pos - file->start_pos; 57 else 58 file->data_end = file->size - file->start_pos; 59 } 60 61 /* Handle files ending before the region. */ 62 63 else 64 file->data_end = 0; 65 66 /* Reset the current position if a region is empty. */ 67 68 if (!file->data_end) 69 file->data_current = 0; 70 } 71 72 73 74 /* Obtain a reference to a file opener for the given user. */ 75 76 long file_open_for_user(user_t user, l4_cap_idx_t server, l4_cap_idx_t *opener) 77 { 78 client_Filesystem filesystem(server); 79 80 return filesystem.open_for_user(user, opener); 81 } 82 83 84 85 /* Initialise the given file structure. */ 86 87 void file_init(file_t *file) 88 { 89 file->memory = NULL; 90 file->ref = L4_INVALID_CAP; 91 file->start_pos = 0; 92 file->end_pos = 0; 93 file->data_end = 0; 94 file->data_current = 0; 95 file->object_flags = 0; 96 file->can_block = 0; 97 file->flags = 0; 98 file->error = 0; 99 file->metadata = NULL; 100 101 /* Initialise the notifiable section of the structure. */ 102 103 file->notifiable.notifications = 0; 104 file->notifiable.pending_notifications = 0; 105 file->notifiable.base = (notifiable_base_t *) file; 106 file->notifiable.handler = NULL; 107 } 108 109 110 111 /* Release resources for the given file. Note that this function does not 112 deallocate the structure since it may be statically allocated. */ 113 114 void file_close(file_t *file) 115 { 116 if (l4_is_valid_cap(file->ref)) 117 { 118 notifier_get_task_notifier()->unsubscribe(file_notifiable(file)); 119 ipc_cap_free_um(file->ref); 120 } 121 122 if (file->memory != NULL) 123 ipc_detach_dataspace(file->memory); 124 125 file_init(file); 126 } 127 128 /* Make a directory in the filesystem. This is a convenience function invoking 129 file_context and file_context_mkdir. */ 130 131 long file_mkdir(const char *filename, mode_t mode, l4_cap_idx_t server) 132 { 133 file_t context; 134 long err; 135 136 err = file_context(&context, server); 137 if (err) 138 return err; 139 140 if (!file_string_set(&context, filename, 0, NULL)) 141 return -L4_ENOMEM; 142 143 err = file_context_mkdir(systypes_to_sys_mode(mode), &context); 144 145 /* Close the context, although a separate mechanism could permit contexts to 146 remove several files. */ 147 148 file_close(&context); 149 return err; 150 } 151 152 /* Open a file using the given structure, indicating the filename and 153 filesystem server. The file_mmap function should be used to obtain access to 154 memory providing file data. This is a convenience function invoking 155 file_context and file_context_open. */ 156 157 long file_open(file_t *file, const char *filename, flags_t flags, l4_cap_idx_t server) 158 { 159 file_t context; 160 long err; 161 162 err = file_context(&context, server); 163 if (err) 164 return err; 165 166 if (!file_string_set(&context, filename, 0, NULL)) 167 return -L4_ENOMEM; 168 169 err = file_context_open(file, flags, &context); 170 171 /* Close the context, although a separate mechanism could permit contexts to 172 open several files. */ 173 174 file_close(&context); 175 return err; 176 } 177 178 /* Remove a file from the filesystem. This is a convenience function invoking 179 file_context and file_context_remove. */ 180 181 long file_remove(const char *filename, l4_cap_idx_t server) 182 { 183 file_t context; 184 long err; 185 186 err = file_context(&context, server); 187 if (err) 188 return err; 189 190 if (!file_string_set(&context, filename, 0, NULL)) 191 return -L4_ENOMEM; 192 193 err = file_context_remove(&context); 194 195 /* Close the context, although a separate mechanism could permit contexts to 196 remove several files. */ 197 198 file_close(&context); 199 return err; 200 } 201 202 /* Open a new instance of a file using the given structure. */ 203 204 long file_reopen(file_t *file, file_t *new_file, flags_t flags) 205 { 206 client_File _file(file->ref); 207 file_init(new_file); 208 new_file->flags = flags; 209 return _file.reopen(flags, &new_file->size, &new_file->ref, &new_file->object_flags); 210 } 211 212 /* Rename an object in the filesystem. This is a convenience function invoking 213 file_context and file_context_rename. */ 214 215 long file_rename(const char *source, const char *target, l4_cap_idx_t server) 216 { 217 file_t context; 218 offset_t written; 219 long err; 220 221 err = file_context(&context, server); 222 if (err) 223 return err; 224 225 if (!file_string_set(&context, source, 0, &written)) 226 return -L4_ENOMEM; 227 228 if (!file_string_set(&context, target, written + 1, NULL)) 229 return -L4_ENOMEM; 230 231 err = file_context_rename(&context); 232 233 /* Close the context, although a separate mechanism could permit contexts to 234 rename several files. */ 235 236 file_close(&context); 237 return err; 238 } 239 240 /* Obtain filesystem object statistics. This is a convenience function invoking 241 file_context and file_context_stat. */ 242 243 long file_stat(const char *filename, struct stat *st, l4_cap_idx_t server) 244 { 245 file_t context; 246 offset_t written; 247 long err; 248 249 err = file_context(&context, server); 250 if (err) 251 return err; 252 253 if (!file_string_set(&context, filename, 0, &written)) 254 return -L4_ENOMEM; 255 256 err = file_context_stat(st, &context); 257 258 /* Close the context, although a separate mechanism could permit contexts to 259 rename several files. */ 260 261 file_close(&context); 262 return err; 263 } 264 265 266 267 /* Initialise a file structure for a context obtained from the given server 268 attaching memory to communicate filename information. */ 269 270 long file_context(file_t *file, l4_cap_idx_t server) 271 { 272 if (l4_is_invalid_cap(server)) 273 return -L4_EINVAL; 274 275 client_Opener opener(server); 276 ds_stats_t stats; 277 long err; 278 279 file_init(file); 280 file->flags = O_RDWR; 281 282 err = opener.context(&file->ref); 283 if (err) 284 return err; 285 286 client_Dataspace context_ds(file->ref); 287 288 err = context_ds.info(&stats); 289 if (err) 290 return err; 291 292 file->start_pos = 0; 293 file->end_pos = stats.size; 294 295 return ipc_attach_dataspace(file->ref, stats.size, (void **) &file->memory); 296 } 297 298 /* Make a directory using the given context. */ 299 300 long file_context_mkdir(mode_t mode, file_t *context) 301 { 302 client_OpenerContext openercontext(context->ref); 303 return openercontext.mkdir(mode); 304 } 305 306 /* Open a file using the given structure and context. */ 307 308 long file_context_open(file_t *file, flags_t flags, file_t *context) 309 { 310 client_OpenerContext openercontext(context->ref); 311 file_init(file); 312 file->flags = flags; 313 return openercontext.open(flags, &file->size, &file->ref, &file->object_flags); 314 } 315 316 /* Remove a file using the given context. */ 317 318 long file_context_remove(file_t *context) 319 { 320 client_OpenerContext openercontext(context->ref); 321 return openercontext.remove(); 322 } 323 324 /* Rename a file using the given context. */ 325 326 long file_context_rename(file_t *context) 327 { 328 client_OpenerContext openercontext(context->ref); 329 return openercontext.rename(); 330 } 331 332 /* Obtain filesystem object statistics using the given context. */ 333 334 long file_context_stat(struct stat *st, file_t *context) 335 { 336 client_OpenerContext openercontext(context->ref); 337 long err = openercontext.stat(); 338 339 if (err) 340 return err; 341 342 /* Copy the stat structure manually in case of any divergence. */ 343 344 systypes_copy_from_sys_stat(st, (sys_stat_t *) context->memory); 345 346 return L4_EOK; 347 } 348 349 350 351 /* Flush populated data and obtain an updated file size. */ 352 353 long file_flush(file_t *file) 354 { 355 if (l4_is_invalid_cap(file->ref)) 356 return -L4_EINVAL; 357 358 client_Flush _file(file->ref); 359 long err = _file.flush(file->data_current, &file->size); 360 361 if (err) 362 return err; 363 364 _update_extent(file); 365 366 return L4_EOK; 367 } 368 369 /* Refresh position and size data from the file or pipe. */ 370 371 long file_refresh(file_t *file) 372 { 373 if (l4_is_invalid_cap(file->ref)) 374 return -L4_EINVAL; 375 376 client_Flush _file(file->ref); 377 long err; 378 379 /* Unmap any existing region since the endpoint may have been reconfigured. */ 380 381 if (file->memory != NULL) 382 { 383 err = ipc_detach_dataspace(file->memory); 384 if (err) 385 return err; 386 387 file->memory = NULL; 388 } 389 390 /* Obtain the new details for the region. */ 391 392 err = _file.refresh(&file->data_current, &file->start_pos, &file->end_pos, 393 &file->size, &file->object_flags); 394 395 if (err) 396 return err; 397 398 _update_extent(file); 399 400 /* Attempt to map any replacement region. */ 401 402 if (!file_span(file)) 403 return L4_EOK; 404 405 return ipc_attach_dataspace_align(file->ref, file_span(file), 406 L4RE_RM_F_SEARCH_ADDR | file_region_flags(file->flags), 407 L4_PAGESHIFT, 408 (void **) &file->memory); 409 } 410 411 /* Map a region of the given file to a memory region, obtaining an updated file 412 size and populated data details. Unmap any previously mapped region. */ 413 414 long file_mmap(file_t *file, offset_t position, offset_t length, 415 offset_t start_visible, offset_t end_visible, 416 rm_flags_t region_flags) 417 { 418 long err; 419 420 if (file->memory != NULL) 421 { 422 err = ipc_detach_dataspace(file->memory); 423 if (err) 424 return err; 425 426 file->memory = NULL; 427 } 428 429 err = file_mmap_only(file, position, length, start_visible, end_visible); 430 431 if (err) 432 return err; 433 434 return ipc_attach_dataspace_align(file->ref, file_span(file), 435 L4RE_RM_F_SEARCH_ADDR | region_flags, 436 L4_PAGESHIFT, 437 (void **) &file->memory); 438 } 439 440 /* Request access to a region of the given file, obtaining an updated file size 441 and populated data details. The region is not mapped, however. */ 442 443 long file_mmap_only(file_t *file, offset_t position, offset_t length, 444 offset_t start_visible, offset_t end_visible) 445 { 446 client_MappedFile mapped_file(file->ref); 447 long err = mapped_file.mmap(position, length, start_visible, end_visible, 448 &file->start_pos, &file->end_pos, &file->size); 449 450 if (err) 451 return err; 452 453 _update_extent(file); 454 455 return L4_EOK; 456 } 457 458 /* Return opening flags compatible with the given region flags. */ 459 460 flags_t file_opening_flags(rm_flags_t rm_flags) 461 { 462 if ((rm_flags & L4RE_RM_F_RW) == L4RE_RM_F_RW) 463 return O_RDWR; 464 else if (rm_flags & L4RE_RM_F_W) 465 return O_WRONLY; 466 else 467 return O_RDONLY; 468 } 469 470 /* Return mmap flags corresponding to the file access flags. */ 471 472 rm_flags_t file_region_flags(flags_t flags) 473 { 474 rm_flags_t rm_flags; 475 476 switch (flags & 3) 477 { 478 case O_WRONLY: 479 rm_flags = L4RE_RM_F_W; 480 break; 481 482 case O_RDWR: 483 rm_flags = L4RE_RM_F_RW; 484 break; 485 486 default: 487 rm_flags = L4RE_RM_F_R; 488 break; 489 } 490 491 return rm_flags; 492 } 493 494 /* Resize a file, obtaining updated file size details. */ 495 496 long file_resize(file_t *file, offset_t size) 497 { 498 if (!(file->object_flags & OBJECT_HAS_SIZE)) 499 return -L4_EIO; 500 501 client_File _file(file->ref); 502 offset_t file_size = size; 503 long err = _file.resize(&file_size); 504 505 if (err) 506 return err; 507 508 file->size = file_size; 509 _update_extent(file); 510 return L4_EOK; 511 } 512 513 514 515 /* Return the amount of data in the mapped region for the given file. */ 516 517 offset_t file_populated_span(file_t *file) 518 { 519 offset_t size = file_span(file); 520 return (file->data_end < size) ? file->data_end : size; 521 } 522 523 /* Return the size of the mapped region for the given file. */ 524 525 offset_t file_span(file_t *file) 526 { 527 return file->end_pos - file->start_pos; 528 } 529 530 531 532 /* Get a pointer to any terminated string at the given offset or NULL if the 533 data from offset is not terminated. */ 534 535 char *file_string_get(file_t *file, offset_t offset) 536 { 537 offset_t limit = file_span(file) - offset; 538 539 if (strnlen(file->memory + offset, limit) < limit) 540 return file->memory + offset; 541 else 542 return NULL; 543 } 544 545 /* Copy a string to the mapped region at the given offset, returning 1 (true) 546 where all characters were copied, 0 (false) otherwise. The precise number of 547 characters copied, excluding the zero terminator is provided via the written 548 parameter if it is not specified as NULL. */ 549 550 int file_string_set(file_t *file, const char *data, offset_t offset, 551 offset_t *written) 552 { 553 offset_t i, pos, limit = file_span(file); 554 555 /* Do not attempt to copy data with an invalid offset. */ 556 557 if (offset >= limit) 558 { 559 if (written != NULL) 560 *written = 0; 561 return 0; 562 } 563 564 /* Copy the data to the given offset, stopping at the end of the region. */ 565 566 for (i = 0, pos = offset; pos < limit; i++, pos++) 567 { 568 file->memory[pos] = data[i]; 569 570 /* Terminator written, can return immediately. */ 571 572 if (!data[i]) 573 { 574 if (written != NULL) 575 *written = pos - offset; 576 return 1; 577 } 578 } 579 580 /* Terminate the incomplete string at the end of the region. */ 581 582 file->memory[limit - 1] = '\0'; 583 if (written != NULL) 584 *written = limit - 1 - offset; 585 return 0; 586 } 587 588 589 590 /* Return the number of remaining populated bytes in the region. */ 591 592 offset_t file_data_available(file_t *file) 593 { 594 if (file_populated_span(file) > file->data_current) 595 return file_populated_span(file) - file->data_current; 596 else 597 return 0; 598 } 599 600 /* Return the current data offset in the region. */ 601 602 char *file_data_current(file_t *file) 603 { 604 return file->memory + file->data_current; 605 } 606 607 /* Return the current access position in the file. */ 608 609 offset_t file_data_current_position(file_t *file) 610 { 611 return file->start_pos + file->data_current; 612 } 613 614 /* Return the position of the end of the populated bytes in the region. */ 615 616 offset_t file_data_end_position(file_t *file) 617 { 618 return file->start_pos + file->data_end; 619 } 620 621 /* Return the amount of remaining space in the region. */ 622 623 offset_t file_data_space(file_t *file) 624 { 625 return file_span(file) - file->data_current; 626 } 627 628 629 630 /* Copy data to the given buffer from the current data position, updating the 631 position. */ 632 633 void file_data_read(file_t *file, char *buf, offset_t to_transfer) 634 { 635 memcpy(buf, file_data_current(file), to_transfer); 636 637 /* Update position details. */ 638 639 file->data_current += to_transfer; 640 } 641 642 /* Copy data from the given buffer to the current data position, updating the 643 position and the extent of populated data if this was exceeded. */ 644 645 void file_data_write(file_t *file, char *buf, offset_t to_transfer) 646 { 647 memcpy(file_data_current(file), buf, to_transfer); 648 649 /* Update position details. */ 650 651 file->data_current += to_transfer; 652 653 if (file->data_current > file->data_end) 654 file->data_end = file->data_current; 655 } 656 657 658 659 /* Conversion to the generic notification types. */ 660 661 notifiable_t *file_notifiable(file_t *file) 662 { 663 return notify_notifiable((notifiable_base_t *) file); 664 } 665 666 /* Return the notification flags for a file. */ 667 668 notify_flags_t file_notifications(file_t *file) 669 { 670 return notify_notifications((notifiable_base_t *) file); 671 } 672 673 /* Wait for a notification event on a file. */ 674 675 long file_notify_wait_file(file_t *file, notifier_t *notifier) 676 { 677 long err = notify_wait(file_notifiable(file), notifier); 678 679 /* Unsubscribe if a closure notification has been received. */ 680 681 if (!err && (file_notifications(file) & NOTIFY_PEER_CLOSED)) 682 notify_unsubscribe(file_notifiable(file), notifier); 683 684 return err; 685 } 686 687 /* Wait for notification events on files. */ 688 689 long file_notify_wait_files(file_t **file, notifier_t *notifier) 690 { 691 notifiable_t *notifiable; 692 long err = notify_wait_many(¬ifiable, notifier); 693 694 *file = (file_t *) notifiable->base; 695 696 /* Unsubscribe if a closure notification has been received. */ 697 698 if (!err && (notifiable->notifications & NOTIFY_PEER_CLOSED)) 699 notify_unsubscribe(notifiable, notifier); 700 701 return err; 702 } 703 704 705 706 /* Open two pipe endpoints using the given pipe server. */ 707 708 long pipe_open(offset_t size, file_t *reader, file_t *writer, l4_cap_idx_t server) 709 { 710 if (l4_is_invalid_cap(server)) 711 return -L4_EINVAL; 712 713 client_PipeOpener opener(server); 714 715 file_init(reader); 716 file_init(writer); 717 reader->flags = O_RDONLY; 718 writer->flags = O_WRONLY; 719 720 return opener.pipe(size, &reader->ref, &writer->ref); 721 } 722 723 /* Access the current region for a pipe endpoint. */ 724 725 long pipe_current(file_t *pipe) 726 { 727 client_Flush _pipe(pipe->ref); 728 729 /* The current position and flags are not updated by the refresh operation. */ 730 731 offset_t data_current; 732 object_flags_t object_flags; 733 long err = _pipe.refresh(&data_current, &pipe->start_pos, &pipe->end_pos, 734 &pipe->size, &object_flags); 735 736 if (err) 737 return err; 738 739 _update_extent(pipe); 740 741 /* Attach memory if necessary. */ 742 743 if (pipe->memory == NULL) 744 { 745 err = ipc_attach_dataspace(pipe->ref, file_span(pipe), (void **) &pipe->memory); 746 if (err) 747 return err; 748 } 749 750 return L4_EOK; 751 } 752 753 /* Access the next region for a pipe endpoint, updating the eventual size of 754 the current region if writing. */ 755 756 long pipe_next(file_t *pipe) 757 { 758 client_Pipe _pipe(pipe->ref); 759 offset_t size = pipe->data_current; 760 long err = _pipe.next_region(&size, &pipe->end_pos); 761 762 if (err) 763 return err; 764 765 pipe->size = size; 766 _update_extent(pipe); 767 768 if (pipe->memory != NULL) 769 err = ipc_detach_dataspace(pipe->memory); 770 771 if (err) 772 return err; 773 774 pipe->data_current = 0; 775 pipe->memory = NULL; 776 777 err = ipc_attach_dataspace(pipe->ref, file_span(pipe), (void **) &pipe->memory); 778 if (err) 779 return err; 780 781 return L4_EOK; 782 } 783 784 /* Set the size of the written region. */ 785 786 long pipe_written(file_t *pipe, offset_t size) 787 { 788 if (size <= pipe->size) 789 { 790 pipe->data_end = size; 791 return L4_EOK; 792 } 793 else 794 return -L4_EINVAL; 795 } 796 797 798 799 /* Obtain a directory listing stream from a directory. */ 800 801 long directory_opendir(file_t *file, file_t *reader) 802 { 803 client_Directory directory(file->ref); 804 file_init(reader); 805 reader->flags = O_RDONLY; 806 return directory.opendir(&reader->size, &reader->ref, &reader->object_flags); 807 } 808 809 // vim: tabstop=2 expandtab shiftwidth=2