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