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