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