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