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