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