1 /* 2 * File access convenience functions. 3 * 4 * Copyright (C) 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 <ipc/cap_alloc.h> 23 #include <ipc/mem_ipc.h> 24 25 #include <string.h> 26 27 #include "dataspace_client.h" 28 #include "file_client.h" 29 #include "filesystem_client.h" 30 #include "flush_client.h" 31 #include "opener_client.h" 32 #include "opener_context_client.h" 33 #include "pipe_client.h" 34 #include "pipe_opener_client.h" 35 #include "mapped_file_client.h" 36 37 #include "file.h" 38 #include "notifier.h" 39 40 41 42 /* Update the extent of the file in a region using the region start and end 43 positions and the file size. */ 44 45 static void _update_extent(file_t *file) 46 { 47 /* Handle files ending after or within the region. */ 48 49 if (file->size > file->start_pos) 50 { 51 if (file->size > file->end_pos) 52 file->data_end = file->end_pos - file->start_pos; 53 else 54 file->data_end = file->size - file->start_pos; 55 } 56 57 /* Handle files ending before the region. */ 58 59 else 60 file->data_end = 0; 61 } 62 63 64 65 /* Obtain a reference to a file opener for the given user. */ 66 67 long file_open_for_user(user_t user, l4_cap_idx_t server, l4_cap_idx_t *opener) 68 { 69 client_Filesystem filesystem(server); 70 71 return filesystem.open_for_user(user, opener); 72 } 73 74 75 76 /* Initialise the given file structure. */ 77 78 void file_init(file_t *file) 79 { 80 file->memory = NULL; 81 file->ref = L4_INVALID_CAP; 82 file->notifier = L4_INVALID_CAP; 83 file->start_pos = 0; 84 file->end_pos = 0; 85 file->data_end = 0; 86 file->data_current = 0; 87 file->can_mmap = 1; 88 file->has_size = 1; 89 file->can_block = 0; 90 file->notifications = 0; 91 } 92 93 94 95 /* Release resources for the given file. */ 96 97 void file_close(file_t *file) 98 { 99 if (l4_is_valid_cap(file->ref)) 100 ipc_cap_free_um(file->ref); 101 102 if (l4_is_valid_cap(file->notifier)) 103 ipc_cap_free_um(file->notifier); 104 105 if (file->memory != NULL) 106 ipc_detach_dataspace(file->memory); 107 108 file_init(file); 109 } 110 111 /* Open a file using the given structure, indicating the filename and 112 filesystem server. The file_mmap function should be used to obtain access to 113 memory providing file data. This is a convenience function invoking 114 file_context and file_context_open. */ 115 116 long file_open(file_t *file, const char *filename, flags_t flags, l4_cap_idx_t server) 117 { 118 file_t context; 119 long err; 120 121 err = file_context(&context, server); 122 if (err) 123 return err; 124 125 if (!file_string_set(&context, filename, 0, NULL)) 126 return -L4_ENOMEM; 127 128 err = file_context_open(file, flags, &context); 129 130 /* Close the context, although a separate mechanism could permit contexts to 131 open several files. */ 132 133 file_close(&context); 134 return err; 135 } 136 137 138 139 /* Initialise a file structure for a context obtained from the given server 140 attaching memory to communicate filename information. */ 141 142 long file_context(file_t *file, l4_cap_idx_t server) 143 { 144 if (l4_is_invalid_cap(server)) 145 return -L4_EINVAL; 146 147 client_Opener opener(server); 148 offset_t size; 149 flags_t flags; 150 long err; 151 152 file_init(file); 153 154 err = opener.context(&file->ref); 155 if (err) 156 return err; 157 158 client_Dataspace context_ds(file->ref); 159 160 err = context_ds.info(&size, &flags); 161 if (err) 162 return err; 163 164 file->start_pos = 0; 165 file->end_pos = size; 166 167 return ipc_attach_dataspace(file->ref, size, (void **) &file->memory); 168 } 169 170 /* Open a file using the given structure and context. */ 171 172 long file_context_open(file_t *file, flags_t flags, file_t *context) 173 { 174 client_OpenerContext openercontext(context->ref); 175 file_init(file); 176 return openercontext.open(flags, &file->size, &file->ref); 177 } 178 179 180 181 /* Flush populated data and obtain an updated file size and populated data 182 details. */ 183 184 long file_flush(file_t *file) 185 { 186 client_Flush _file(file->ref); 187 long err = _file.flush(file->data_current, &file->size); 188 189 if (err) 190 return err; 191 192 _update_extent(file); 193 194 return L4_EOK; 195 } 196 197 /* Map a region of the given file to a memory region, obtaining an updated file 198 size and populated data details. Unmap any previously mapped region. */ 199 200 long file_mmap(file_t *file, offset_t position, offset_t length) 201 { 202 char *memory = file->memory; 203 client_MappedFile mapped_file(file->ref); 204 long err = mapped_file.mmap(position, length, &file->start_pos, 205 &file->end_pos, &file->size); 206 207 if (err) 208 return err; 209 210 _update_extent(file); 211 212 err = ipc_attach_dataspace(file->ref, file_span(file), (void **) &file->memory); 213 if (err) 214 return err; 215 216 if (memory != NULL) 217 ipc_detach_dataspace(memory); 218 219 return L4_EOK; 220 } 221 222 /* Resize a file, obtaining updated file size and populated data details. */ 223 224 long file_resize(file_t *file, offset_t size) 225 { 226 if (!file->has_size) 227 return -L4_EIO; 228 229 client_File _file(file->ref); 230 offset_t file_size = size; 231 long err = _file.resize(&file_size); 232 233 if (err) 234 return err; 235 236 file->size = file_size; 237 _update_extent(file); 238 return L4_EOK; 239 } 240 241 242 243 /* Return the amount of data in the mapped region for the given file. */ 244 245 offset_t file_populated_span(file_t *file) 246 { 247 offset_t size = file_span(file); 248 return (file->data_end < size) ? file->data_end : size; 249 } 250 251 /* Return the size of the mapped region for the given file. */ 252 253 offset_t file_span(file_t *file) 254 { 255 return file->end_pos - file->start_pos; 256 } 257 258 259 260 /* Get a pointer to any terminated string at the given offset or NULL if the 261 data from offset is not terminated. */ 262 263 char *file_string_get(file_t *file, offset_t offset) 264 { 265 offset_t limit = file_span(file) - offset; 266 267 if (strnlen(file->memory + offset, limit) < limit) 268 return file->memory + offset; 269 else 270 return NULL; 271 } 272 273 /* Copy a string to the mapped region at the given offset, returning 1 (true) 274 where all characters were copied, 0 (false) otherwise. The precise number of 275 characters copied, excluding the zero terminator is provided via the written 276 parameter if it is not specified as NULL. */ 277 278 int file_string_set(file_t *file, const char *data, offset_t offset, 279 offset_t *written) 280 { 281 offset_t i, pos, limit = file_span(file); 282 283 /* Do not attempt to copy data with an invalid offset. */ 284 285 if (offset >= limit) 286 { 287 if (written != NULL) 288 *written = 0; 289 return 0; 290 } 291 292 /* Copy the data to the given offset, stopping at the end of the region. */ 293 294 for (i = 0, pos = offset; pos < limit; i++, pos++) 295 { 296 file->memory[pos] = data[i]; 297 298 /* Terminator written, can return immediately. */ 299 300 if (!data[i]) 301 { 302 if (written != NULL) 303 *written = pos - offset; 304 return 1; 305 } 306 } 307 308 /* Terminate the incomplete string at the end of the region. */ 309 310 file->memory[limit - 1] = '\0'; 311 if (written != NULL) 312 *written = limit - 1 - offset; 313 return 0; 314 } 315 316 317 318 /* Return the number of remaining populated bytes in the region. */ 319 320 offset_t file_data_available(file_t *file) 321 { 322 return file_populated_span(file) - file->data_current; 323 } 324 325 /* Return the current data offset in the region. */ 326 327 char *file_data_current(file_t *file) 328 { 329 return file->memory + file->data_current; 330 } 331 332 /* Return the current access position in the file. */ 333 334 offset_t file_data_current_position(file_t *file) 335 { 336 return file->start_pos + file->data_current; 337 } 338 339 /* Return the position of the end of the populated bytes in the region. */ 340 341 offset_t file_data_end_position(file_t *file) 342 { 343 return file->start_pos + file->data_end; 344 } 345 346 /* Return the amount of remaining space in the region. */ 347 348 offset_t file_data_space(file_t *file) 349 { 350 return file_span(file) - file->data_current; 351 } 352 353 354 355 /* Copy data to the given buffer from the current data position, updating the 356 position. */ 357 358 void file_data_read(file_t *file, char *buf, offset_t to_transfer) 359 { 360 memcpy(buf, file_data_current(file), to_transfer); 361 362 /* Update position details. */ 363 364 file->data_current += to_transfer; 365 } 366 367 /* Copy data from the given buffer to the current data position, updating the 368 position and the extent of populated data if this was exceeded. */ 369 370 void file_data_write(file_t *file, char *buf, offset_t to_transfer) 371 { 372 memcpy(file_data_current(file), buf, to_transfer); 373 374 /* Update position details. */ 375 376 file->data_current += to_transfer; 377 378 if (file->data_current > file->data_end) 379 file->data_end = file->data_current; 380 } 381 382 383 384 /* Subscribe to notification events on a file. */ 385 386 long file_notify_subscribe(file_t *file, notify_flags_t flags) 387 { 388 FileNotifier *notifier = get_notifier(); 389 390 return notifier->subscribe(file, flags); 391 } 392 393 /* Unsubscribe from notification events on a file. */ 394 395 long file_notify_unsubscribe(file_t *file) 396 { 397 if (l4_is_invalid_cap(file->notifier)) 398 return -L4_EINVAL; 399 400 FileNotifier *notifier = get_notifier(); 401 402 return notifier->unsubscribe(file); 403 } 404 405 /* Wait for a notification event on a file. */ 406 407 long file_notify_wait_file(file_t *file) 408 { 409 FileNotifier *notifier = get_notifier(); 410 411 return notifier->wait_file(file); 412 } 413 414 /* Wait for notification events on files. */ 415 416 long file_notify_wait_files(file_t **file) 417 { 418 FileNotifier *notifier = get_notifier(); 419 420 return notifier->wait(file); 421 } 422 423 424 425 /* Open two pipe endpoints using the given pipe server. */ 426 427 long pipe_open(offset_t size, file_t *reader, file_t *writer, l4_cap_idx_t server) 428 { 429 if (l4_is_invalid_cap(server)) 430 return -L4_EINVAL; 431 432 client_PipeOpener opener(server); 433 434 file_init(reader); 435 file_init(writer); 436 437 /* Pipes can usually only be accessed via region navigation. */ 438 439 reader->can_mmap = 0; 440 reader->has_size = 0; 441 writer->can_mmap = 0; 442 writer->has_size = 0; 443 444 long err = opener.pipe(size, &reader->ref, &writer->ref); 445 if (err) 446 return err; 447 448 err = pipe_next(writer) || pipe_next(reader); 449 450 if (err) 451 { 452 file_close(reader); 453 file_close(writer); 454 } 455 456 return err; 457 } 458 459 /* Access the current region for a pipe endpoint. */ 460 461 long pipe_current(file_t *pipe) 462 { 463 client_Pipe _pipe(pipe->ref); 464 long err = _pipe.current_region(&pipe->data_end, &pipe->size); 465 466 if (err) 467 return err; 468 469 pipe->end_pos = pipe->size; 470 471 /* Attach memory if necessary. */ 472 473 if (pipe->memory == NULL) 474 { 475 err = ipc_attach_dataspace(pipe->ref, file_span(pipe), (void **) &pipe->memory); 476 if (err) 477 return err; 478 } 479 480 return L4_EOK; 481 } 482 483 /* Access the next region for a pipe endpoint, updating the eventual size of 484 the current region. */ 485 486 long pipe_next(file_t *pipe) 487 { 488 client_Pipe _pipe(pipe->ref); 489 long err = _pipe.next_region(&pipe->data_end, &pipe->size); 490 char *memory = pipe->memory; 491 492 if (err) 493 return err; 494 495 pipe->end_pos = pipe->size; 496 497 err = ipc_attach_dataspace(pipe->ref, file_span(pipe), (void **) &pipe->memory); 498 if (err) 499 return err; 500 501 if (memory != NULL) 502 ipc_detach_dataspace(memory); 503 504 return L4_EOK; 505 } 506 507 /* Set the size of the written region. */ 508 509 long pipe_written(file_t *pipe, offset_t size) 510 { 511 if (size <= pipe->size) 512 { 513 pipe->data_end = size; 514 return L4_EOK; 515 } 516 else 517 return -L4_EINVAL; 518 } 519 520 // vim: tabstop=2 expandtab shiftwidth=2