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)) 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 /* Read a directory entry. This must be freed by the caller after use. */ 373 374 struct dirent *client_readdir(file_t *file) 375 { 376 char buffer[DIRENT_CORE_SIZE]; 377 offset_t nread = client_read(file, buffer, DIRENT_CORE_SIZE); 378 379 /* Stop if no new structure can be successfully read. */ 380 381 if (nread != DIRENT_CORE_SIZE) 382 return NULL; 383 384 struct dirent *dirent = (struct dirent *) buffer; 385 offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE; 386 387 /* Allocate a buffer for the complete structure. */ 388 389 char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char)); 390 391 if (entry == NULL) 392 return NULL; 393 394 /* Copy the start of the entry into a new buffer. */ 395 396 memcpy(entry, buffer, DIRENT_CORE_SIZE); 397 398 /* Append to the entry buffer. */ 399 400 char *current = entry + DIRENT_CORE_SIZE; 401 402 nread = client_read(file, current, remaining); 403 404 /* Stop if no complete structure can be successfully read. */ 405 406 if (nread != remaining) 407 { 408 free(entry); 409 return NULL; 410 } 411 412 return (struct dirent *) entry; 413 } 414 415 416 417 /* Read from the filesystem object into the buffer provided. */ 418 419 offset_t client_read(file_t *file, void *buf, offset_t count) 420 { 421 if (file == NULL) 422 return 0; 423 424 /* Map memory if none has been mapped so far. */ 425 426 if (_map_memory(file, count) == NULL) 427 return 0; 428 429 /* Amount available in the descriptor buffer already. */ 430 431 offset_t available = file_data_available(file); 432 offset_t to_transfer, total = 0; 433 434 while (count > 0) 435 { 436 /* If there is no data, try and obtain more data. */ 437 438 if (!available) 439 { 440 /* Flush any unwritten data, preparing to read from the file position at 441 the end of the data, and returning if no new data is available. */ 442 443 if (!_access_blocking(file, file_data_end_position(file), 1)) 444 break; 445 446 available = file_data_available(file); 447 448 if (!available) 449 break; 450 } 451 452 /* Transfer data into the supplied buffer. */ 453 454 to_transfer = available <= count ? available : count; 455 456 file_data_read(file, (char *) buf, to_transfer); 457 458 /* Update counters. */ 459 460 available -= to_transfer; 461 462 count -= to_transfer; 463 total += to_transfer; 464 465 buf = ((char *) buf + to_transfer); 466 } 467 468 return total; 469 } 470 471 472 473 /* Ensure that the buffer can provide the needed data. */ 474 475 offset_t client_seek(file_t *file, offset_t offset, int whence) 476 { 477 if (file == NULL) 478 return 0; 479 480 offset_t position, current = file_data_current_position(file), change; 481 482 switch (whence) 483 { 484 case SEEK_SET: 485 position = offset; 486 break; 487 488 case SEEK_CUR: 489 position = current + offset; 490 break; 491 492 case SEEK_END: 493 position = file->size + offset; 494 break; 495 496 default: 497 /* NOTE: Set errno to EINVAL. */ 498 return current; 499 } 500 501 /* Retain the current position if unchanged. */ 502 503 if (position == current) 504 return position; 505 506 /* Move forward in the file. */ 507 508 if (position > current) 509 { 510 change = position - current; 511 512 /* Move towards the end of available data. 513 Request new data if not enough is available. */ 514 515 if (change <= file_data_available(file)) 516 { 517 file->data_current += change; 518 return position; 519 } 520 } 521 522 /* Move backward in the file. */ 523 524 else 525 { 526 change = current - position; 527 528 /* Move towards the start of available data. 529 Request new data if moving beyond the start of the data. */ 530 531 if (change <= file->data_current) 532 { 533 file->data_current -= change; 534 return position; 535 } 536 } 537 538 /* Handle unwritten data and reset the buffer for reading. */ 539 540 if (_access(file, position)) 541 return current; 542 543 return position; 544 } 545 546 547 548 /* Set or unset blocking access for a file. */ 549 550 long client_set_blocking(file_t *file, notify_flags_t flags) 551 { 552 long err; 553 554 if (file->can_block == flags) 555 return L4_EOK; 556 557 /* Since blocking access is used with specific file notifications, the 558 per-task notifier is used. */ 559 560 if (flags) 561 err = client_subscribe(file, flags, NOTIFIER_TASK); 562 else 563 err = client_unsubscribe(file, NOTIFIER_TASK); 564 565 if (err) 566 return err; 567 568 file->can_block = flags; 569 return L4_EOK; 570 } 571 572 573 574 /* Subscribe from events concerning a file. */ 575 576 long client_subscribe(file_t *file, notify_flags_t flags, notifier_t notifier_type) 577 { 578 if (file == NULL) 579 return -L4_EINVAL; 580 581 return file_notify_subscribe(file, flags, notifier_type); 582 } 583 584 585 586 /* Return the current position in the file. */ 587 588 long client_tell(file_t *file) 589 { 590 if (file == NULL) 591 return -L4_EINVAL; 592 593 return file_data_current_position(file); 594 } 595 596 597 598 /* Unsubscribe from events concerning a file. */ 599 600 long client_unsubscribe(file_t *file, notifier_t notifier_type) 601 { 602 if (file == NULL) 603 return -L4_EINVAL; 604 605 return file_notify_unsubscribe(file, notifier_type); 606 } 607 608 609 610 /* Wait for events involving a specific file. */ 611 612 long client_wait_file(file_t *file) 613 { 614 if (file == NULL) 615 return -L4_EINVAL; 616 617 return file_notify_wait_file(file); 618 } 619 620 /* Wait for events concerning files, referencing a file object if an event is 621 delivered. */ 622 623 long client_wait_files(file_t **file) 624 { 625 return file_notify_wait_files(file); 626 } 627 628 629 630 /* Write to the filesystem object from the buffer provided. */ 631 632 offset_t client_write(file_t *file, const void *buf, offset_t count) 633 { 634 if (file == NULL) 635 return 0; 636 637 /* Map memory if none has been mapped so far. */ 638 639 if (_map_memory(file, count) == NULL) 640 return 0; 641 642 /* Attempt to ensure that the file can accept the amount of data to be 643 written. This may not resize to the needed amount if a file has a fixed 644 size, but data will still be written to any available space. */ 645 646 offset_t needed_size = file_data_current_position(file) + count; 647 648 if (file->object_flags & OBJECT_HAS_SIZE) 649 { 650 if (file->size < needed_size) 651 { 652 file_resize(file, needed_size); 653 654 if (file->size < needed_size) 655 count = file->size - file_data_current_position(file); 656 } 657 } 658 659 /* Space remaining in the descriptor buffer. */ 660 661 offset_t space = file_data_space(file); 662 offset_t to_transfer, total = 0; 663 664 while (count > 0) 665 { 666 /* If no space is available, try and send data, reset the buffer. */ 667 668 if (!space) 669 { 670 /* Flush any unwritten data and continue writing from the current data 671 position. */ 672 673 if (!_access_blocking(file, file_data_current_position(file), 0)) 674 break; 675 676 space = file_data_space(file); 677 } 678 679 /* Transfer data into the supplied buffer. */ 680 681 to_transfer = space <= count ? space : count; 682 683 file_data_write(file, (char *) buf, to_transfer); 684 685 /* Update counters. */ 686 687 space -= to_transfer; 688 689 count -= to_transfer; 690 total += to_transfer; 691 692 buf = ((char *) buf + to_transfer); 693 } 694 695 return total; 696 } 697 698 // vim: tabstop=2 expandtab shiftwidth=2