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