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