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