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