]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - wimboot/wimboot-2.7.3/src/vdisk.c
keep up with 1.0.67 (#1464)
[Ventoy.git] / wimboot / wimboot-2.7.3 / src / vdisk.c
1 /*
2 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 */
19
20 /**
21 * @file
22 *
23 * Virtual disk
24 *
25 */
26
27 #include <stddef.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <assert.h>
31 #include "ctype.h"
32 #include "wimboot.h"
33 #include "vdisk.h"
34
35 /** Virtual files */
36 struct vdisk_file vdisk_files[VDISK_MAX_FILES];
37
38 /**
39 * Read from virtual Master Boot Record
40 *
41 * @v lba Starting LBA
42 * @v count Number of blocks to read
43 * @v data Data buffer
44 */
45 static void vdisk_mbr ( uint64_t lba __unused, unsigned int count __unused,
46 void *data ) {
47 struct vdisk_mbr *mbr = data;
48
49 /* Construct MBR */
50 memset ( mbr, 0, sizeof ( *mbr ) );
51 mbr->partitions[0].bootable = VDISK_MBR_BOOTABLE;
52 mbr->partitions[0].type = VDISK_MBR_TYPE_FAT32;
53 mbr->partitions[0].start = VDISK_PARTITION_LBA;
54 mbr->partitions[0].length = VDISK_PARTITION_COUNT;
55 mbr->signature = VDISK_MBR_SIGNATURE;
56 mbr->magic = VDISK_MBR_MAGIC;
57 }
58
59 /**
60 * Read from virtual Volume Boot Record
61 *
62 * @v lba Starting LBA
63 * @v count Number of blocks to read
64 * @v data Data buffer
65 */
66 static void vdisk_vbr ( uint64_t lba __unused, unsigned int count __unused,
67 void *data ) {
68 struct vdisk_vbr *vbr = data;
69
70 /* Construct VBR */
71 memset ( vbr, 0, sizeof ( *vbr ) );
72 vbr->jump[0] = VDISK_VBR_JUMP_WTF_MS;
73 memcpy ( vbr->oemid, VDISK_VBR_OEMID, sizeof ( vbr->oemid ) );
74 vbr->bytes_per_sector = VDISK_SECTOR_SIZE;
75 vbr->sectors_per_cluster = VDISK_CLUSTER_COUNT;
76 vbr->reserved_sectors = VDISK_RESERVED_COUNT;
77 vbr->fats = 1;
78 vbr->media = VDISK_VBR_MEDIA;
79 vbr->sectors_per_track = VDISK_SECTORS_PER_TRACK;
80 vbr->heads = VDISK_HEADS;
81 vbr->hidden_sectors = VDISK_VBR_LBA;
82 vbr->sectors = VDISK_PARTITION_COUNT;
83 vbr->sectors_per_fat = VDISK_SECTORS_PER_FAT;
84 vbr->root = VDISK_ROOT_CLUSTER;
85 vbr->fsinfo = VDISK_FSINFO_SECTOR;
86 vbr->backup = VDISK_BACKUP_VBR_SECTOR;
87 vbr->signature = VDISK_VBR_SIGNATURE;
88 vbr->serial = VDISK_VBR_SERIAL;
89 memcpy ( vbr->label, VDISK_VBR_LABEL, sizeof ( vbr->label ) );
90 memcpy ( vbr->system, VDISK_VBR_SYSTEM, sizeof ( vbr->system ) );
91 vbr->magic = VDISK_VBR_MAGIC;
92 }
93
94 /**
95 * Read from virtual FSInfo
96 *
97 * @v lba Starting LBA
98 * @v count Number of blocks to read
99 * @v data Data buffer
100 */
101 static void vdisk_fsinfo ( uint64_t lba __unused, unsigned int count __unused,
102 void *data ) {
103 struct vdisk_fsinfo *fsinfo = data;
104
105 /* Construct FSInfo */
106 memset ( fsinfo, 0, sizeof ( *fsinfo ) );
107 fsinfo->magic1 = VDISK_FSINFO_MAGIC1;
108 fsinfo->magic2 = VDISK_FSINFO_MAGIC2;
109 fsinfo->next_free = VDISK_FSINFO_NEXT_FREE;
110 fsinfo->magic3 = VDISK_FSINFO_MAGIC3;
111 }
112
113 /**
114 * Read from virtual FAT
115 *
116 * @v lba Starting LBA
117 * @v count Number of blocks to read
118 * @v data Data buffer
119 */
120 static void vdisk_fat ( uint64_t lba, unsigned int count, void *data ) {
121 uint32_t *next = data;
122 uint32_t start;
123 uint32_t end;
124 uint32_t file_end_marker;
125 unsigned int i;
126
127 /* Calculate window within FAT */
128 start = ( ( lba - VDISK_FAT_LBA ) *
129 ( VDISK_SECTOR_SIZE / sizeof ( *next ) ) );
130 end = ( start + ( count * ( VDISK_SECTOR_SIZE / sizeof ( *next ) ) ) );
131 next -= start;
132
133 /* Start by marking each cluster as chaining to the next */
134 for ( i = start ; i < end ; i++ )
135 next[i] = ( i + 1 );
136
137 /* Add first-sector special values, if applicable */
138 if ( start == 0 ) {
139 next[0] = ( ( VDISK_FAT_END_MARKER & ~0xff ) |
140 VDISK_VBR_MEDIA );
141 for ( i = 1; i < ( VDISK_SECTOR_SIZE / sizeof ( *next ) ); i++ )
142 next[i] = VDISK_FAT_END_MARKER;
143 }
144
145 /* Add end-of-file markers, if applicable */
146 for ( i = 0 ; i < VDISK_MAX_FILES ; i++ ) {
147 if ( vdisk_files[i].read ) {
148 file_end_marker = ( VDISK_FILE_CLUSTER ( i ) +
149 ( ( vdisk_files[i].xlen - 1 ) /
150 VDISK_CLUSTER_SIZE ) );
151 if ( ( file_end_marker >= start ) &&
152 ( file_end_marker < end ) ) {
153 next[file_end_marker] = VDISK_FAT_END_MARKER;
154 }
155 }
156 }
157 }
158
159 /**
160 * Initialise empty directory
161 *
162 * @v dir Virtual directory
163 * @ret dirent Starting (i.e. final) directory entry
164 */
165 static union vdisk_directory_entry *
166 vdisk_empty_dir ( struct vdisk_directory *dir ) {
167 unsigned int i;
168
169 /* Mark all entries as present and deleted */
170 memset ( dir, 0, sizeof ( *dir ) );
171 for ( i = 0 ; i < VDISK_DIRENT_PER_SECTOR ; i++ ) {
172 dir->entry[i].deleted = VDISK_DIRENT_DELETED;
173 }
174
175 return &dir->entry[ VDISK_DIRENT_PER_SECTOR - 1 ];
176 }
177
178 /**
179 * Construct directory entry
180 *
181 * @v dirent Starting (i.e. final) directory entry
182 * @v name File name
183 * @v len File length
184 * @v attr File attributes
185 * @v cluster File starting cluster
186 * @ret next Next available directory entry
187 */
188 static union vdisk_directory_entry *
189 vdisk_directory_entry ( union vdisk_directory_entry *dirent, const char *name,
190 size_t len,unsigned int attr, uint32_t cluster ) {
191 union vdisk_directory_entry *dos = dirent;
192 union vdisk_directory_entry *lfn = ( dos - 1 );
193 uint8_t *checksum_data;
194 uint8_t checksum;
195 unsigned int sequence;
196 uint16_t *lfn_char;
197 char c;
198 unsigned int i;
199
200 /* Populate directory entry (with invalid 8.3 filename) */
201 memset ( dos->dos.filename.raw, ' ', sizeof ( dos->dos.filename.raw ) );
202 dos->dos.attr = attr;
203 dos->dos.size = len;
204 dos->dos.cluster_high = ( cluster >> 16 );
205 dos->dos.cluster_low = ( cluster & 0xffff );
206
207 /* Calculate checksum of 8.3 filename */
208 checksum = 0;
209 checksum_data = dos->dos.filename.raw;
210 for ( i = 0 ; i < sizeof ( dos->dos.filename.raw ) ; i++ ) {
211 checksum = ( ( ( ( checksum & 1 ) << 7 ) |
212 ( checksum >> 1 ) ) +
213 *(checksum_data++) );
214 }
215
216 /* Construct long filename record */
217 lfn_char = &lfn->lfn.name_1[0];
218 sequence = 1;
219 while ( 1 ) {
220
221 /* Initialise long filename, if necessary */
222 if ( lfn->lfn.attr != VDISK_LFN_ATTR ) {
223 lfn->lfn.sequence = sequence++;
224 memset ( lfn->lfn.name_1, 0xff,
225 sizeof ( lfn->lfn.name_1 ) );
226 lfn->lfn.attr = VDISK_LFN_ATTR;
227 lfn->lfn.checksum = checksum;
228 memset ( lfn->lfn.name_2, 0xff,
229 sizeof ( lfn->lfn.name_2 ) );
230 memset ( lfn->lfn.name_3, 0xff,
231 sizeof ( lfn->lfn.name_3 ) );
232 }
233
234 /* Add character to long filename */
235 c = *(name++);
236 *lfn_char = c;
237 if ( ! c )
238 break;
239
240 /* Move to next character within long filename */
241 if ( lfn_char == &lfn->lfn.name_1[4] ) {
242 lfn_char = &lfn->lfn.name_2[0];
243 } else if ( lfn_char == &lfn->lfn.name_2[5] ) {
244 lfn_char = &lfn->lfn.name_3[0];
245 } else if ( lfn_char == &lfn->lfn.name_3[1] ) {
246 lfn--;
247 lfn_char = &lfn->lfn.name_1[0];
248 } else {
249 lfn_char++;
250 }
251 }
252 lfn->lfn.sequence |= VDISK_LFN_END;
253
254 return ( lfn - 1 );
255 }
256
257 /**
258 * Read subdirectories from virtual root directory
259 *
260 * @v lba Starting LBA
261 * @v count Number of blocks to read
262 * @v data Data buffer
263 */
264 static void vdisk_root ( uint64_t lba __unused, unsigned int count __unused,
265 void *data ) {
266 struct vdisk_directory *dir = data;
267 union vdisk_directory_entry *dirent;
268
269 /* Construct subdirectories */
270 dirent = vdisk_empty_dir ( dir );
271 dirent = vdisk_directory_entry ( dirent, "BOOT", 0, VDISK_DIRECTORY,
272 VDISK_BOOT_CLUSTER );
273 dirent = vdisk_directory_entry ( dirent, "SOURCES", 0, VDISK_DIRECTORY,
274 VDISK_SOURCES_CLUSTER );
275 dirent = vdisk_directory_entry ( dirent, "EFI", 0, VDISK_DIRECTORY,
276 VDISK_EFI_CLUSTER );
277 }
278
279 /**
280 * Read subdirectories from virtual boot directory
281 *
282 * @v lba Starting LBA
283 * @v count Number of blocks to read
284 * @v data Data buffer
285 */
286 static void vdisk_boot ( uint64_t lba __unused, unsigned int count __unused,
287 void *data ) {
288 struct vdisk_directory *dir = data;
289 union vdisk_directory_entry *dirent;
290
291 /* Construct subdirectories */
292 dirent = vdisk_empty_dir ( dir );
293 dirent = vdisk_directory_entry ( dirent, "FONTS", 0, VDISK_DIRECTORY,
294 VDISK_FONTS_CLUSTER );
295 dirent = vdisk_directory_entry ( dirent, "RESOURCES", 0,
296 VDISK_DIRECTORY,
297 VDISK_RESOURCES_CLUSTER );
298 }
299
300 /**
301 * Read subdirectories from virtual sources directory
302 *
303 * @v lba Starting LBA
304 * @v count Number of blocks to read
305 * @v data Data buffer
306 */
307 static void vdisk_sources ( uint64_t lba __unused, unsigned int count __unused,
308 void *data ) {
309 struct vdisk_directory *dir = data;
310
311 /* Construct subdirectories */
312 vdisk_empty_dir ( dir );
313 }
314
315 /**
316 * Read subdirectories from virtual fonts directory
317 *
318 * @v lba Starting LBA
319 * @v count Number of blocks to read
320 * @v data Data buffer
321 */
322 static void vdisk_fonts ( uint64_t lba __unused, unsigned int count __unused,
323 void *data ) {
324 struct vdisk_directory *dir = data;
325
326 /* Construct subdirectories */
327 vdisk_empty_dir ( dir );
328 }
329
330 /**
331 * Read subdirectories from virtual resources directory
332 *
333 * @v lba Starting LBA
334 * @v count Number of blocks to read
335 * @v data Data buffer
336 */
337 static void vdisk_resources ( uint64_t lba __unused,
338 unsigned int count __unused, void *data ) {
339 struct vdisk_directory *dir = data;
340
341 /* Construct subdirectories */
342 vdisk_empty_dir ( dir );
343 }
344
345 /**
346 * Read subdirectories from virtual EFI directory
347 *
348 * @v lba Starting LBA
349 * @v count Number of blocks to read
350 * @v data Data buffer
351 */
352 static void vdisk_efi ( uint64_t lba __unused, unsigned int count __unused,
353 void *data ) {
354 struct vdisk_directory *dir = data;
355 union vdisk_directory_entry *dirent;
356
357 /* Construct subdirectories */
358 dirent = vdisk_empty_dir ( dir );
359 dirent = vdisk_directory_entry ( dirent, "BOOT", 0, VDISK_DIRECTORY,
360 VDISK_BOOT_CLUSTER );
361 dirent = vdisk_directory_entry ( dirent, "MICROSOFT", 0,
362 VDISK_DIRECTORY,
363 VDISK_MICROSOFT_CLUSTER );
364 }
365
366 /**
367 * Read subdirectories from virtual Microsoft directory
368 *
369 * @v lba Starting LBA
370 * @v count Number of blocks to read
371 * @v data Data buffer
372 */
373 static void vdisk_microsoft ( uint64_t lba __unused,
374 unsigned int count __unused, void *data ) {
375 struct vdisk_directory *dir = data;
376 union vdisk_directory_entry *dirent;
377
378 /* Construct subdirectories */
379 dirent = vdisk_empty_dir ( dir );
380 dirent = vdisk_directory_entry ( dirent, "BOOT", 0, VDISK_DIRECTORY,
381 VDISK_BOOT_CLUSTER );
382 }
383
384 /**
385 * Read files from virtual directory
386 *
387 * @v lba Starting LBA
388 * @v count Number of blocks to read
389 * @v data Data buffer
390 */
391 static void vdisk_dir_files ( uint64_t lba, unsigned int count, void *data ) {
392 struct vdisk_directory *dir;
393 union vdisk_directory_entry *dirent;
394 struct vdisk_file *file;
395 unsigned int idx;
396
397 for ( ; count ; lba++, count--, data += VDISK_SECTOR_SIZE ) {
398
399 /* Initialise directory */
400 dir = data;
401 vdisk_empty_dir ( dir );
402 dirent = &dir->entry[ VDISK_DIRENT_PER_SECTOR - 1 ];
403
404 /* Identify file */
405 idx = VDISK_FILE_DIRENT_IDX ( lba );
406 assert ( idx < ( sizeof ( vdisk_files ) /
407 sizeof ( vdisk_files[0] ) ) );
408 file = &vdisk_files[idx];
409 if ( ! file->read )
410 continue;
411
412 /* Populate directory entry */
413 vdisk_directory_entry ( dirent, file->name, file->xlen,
414 VDISK_READ_ONLY,
415 VDISK_FILE_CLUSTER ( idx ) );
416 }
417 }
418
419 /**
420 * Read from virtual file (or empty space)
421 *
422 * @v lba Starting LBA
423 * @v count Number of blocks to read
424 * @v data Data buffer
425 */
426 static void vdisk_file ( uint64_t lba, unsigned int count, void *data ) {
427 struct vdisk_file *file;
428 size_t offset;
429 size_t len;
430 size_t copy_len;
431 size_t pad_len;
432 size_t patch_len;
433
434 /* Construct file portion */
435 file = &vdisk_files[ VDISK_FILE_IDX ( lba ) ];
436 offset = VDISK_FILE_OFFSET ( lba );
437 len = ( count * VDISK_SECTOR_SIZE );
438
439 /* Copy any initialised-data portion */
440 copy_len = ( ( offset < file->len ) ? ( file->len - offset ) : 0 );
441 if ( copy_len > len )
442 copy_len = len;
443 if ( copy_len )
444 file->read ( file, data, offset, copy_len );
445
446 /* Zero any uninitialised-data portion */
447 pad_len = ( len - copy_len );
448 memset ( ( data + copy_len ), 0, pad_len );
449
450 /* Patch any applicable portion */
451 patch_len = ( ( offset < file->xlen ) ? ( file->xlen - offset ) : 0 );
452 if ( patch_len > len )
453 patch_len = len;
454 if ( file->patch )
455 file->patch ( file, data, offset, patch_len );
456 }
457
458 /** A virtual disk region */
459 struct vdisk_region {
460 /** Name */
461 const char *name;
462 /** Starting LBA */
463 uint64_t lba;
464 /** Number of blocks */
465 unsigned int count;
466 /**
467 * Build data from this region
468 *
469 * @v start Starting LBA
470 * @v count Number of blocks to read
471 * @v data Data buffer
472 */
473 void ( * build ) ( uint64_t lba, unsigned int count, void *data );
474 };
475
476 /** Define a virtual disk region */
477 #define VDISK_REGION( _name, _build, _lba, _count ) { \
478 .name = _name, \
479 .lba = _lba, \
480 .count = _count, \
481 .build = _build, \
482 }
483
484 /** Define a virtual disk directory region */
485 #define VDISK_DIRECTORY_REGION( _name, _build_subdirs, _lba ) { \
486 .name = _name " subdirs", \
487 .lba = _lba, \
488 .count = 1, \
489 .build = _build_subdirs, \
490 }, { \
491 .name = _name " files", \
492 .lba = ( _lba + 1 ), \
493 .count = ( VDISK_CLUSTER_COUNT - 1 ), \
494 .build = vdisk_dir_files, \
495 }
496
497 /** Virtual disk regions */
498 static struct vdisk_region vdisk_regions[] = {
499 VDISK_REGION ( "MBR", vdisk_mbr,
500 VDISK_MBR_LBA, VDISK_MBR_COUNT ),
501 VDISK_REGION ( "VBR", vdisk_vbr,
502 VDISK_VBR_LBA, VDISK_VBR_COUNT ),
503 VDISK_REGION ( "FSInfo", vdisk_fsinfo,
504 VDISK_FSINFO_LBA, VDISK_FSINFO_COUNT ),
505 VDISK_REGION ( "VBR Backup", vdisk_vbr,
506 VDISK_BACKUP_VBR_LBA, VDISK_BACKUP_VBR_COUNT ),
507 VDISK_REGION ( "FAT", vdisk_fat,
508 VDISK_FAT_LBA, VDISK_FAT_COUNT ),
509 VDISK_DIRECTORY_REGION ( "Root", vdisk_root, VDISK_ROOT_LBA ),
510 VDISK_DIRECTORY_REGION ( "Boot", vdisk_boot, VDISK_BOOT_LBA ),
511 VDISK_DIRECTORY_REGION ( "Sources", vdisk_sources, VDISK_SOURCES_LBA ),
512 VDISK_DIRECTORY_REGION ( "Fonts", vdisk_fonts, VDISK_FONTS_LBA ),
513 VDISK_DIRECTORY_REGION ( "Resources", vdisk_resources,
514 VDISK_RESOURCES_LBA ),
515 VDISK_DIRECTORY_REGION ( "EFI", vdisk_efi, VDISK_EFI_LBA ),
516 VDISK_DIRECTORY_REGION ( "Microsoft", vdisk_microsoft,
517 VDISK_MICROSOFT_LBA ),
518 };
519
520 /**
521 * Read from virtual disk
522 *
523 * @v lba Starting LBA
524 * @v count Number of blocks to read
525 * @v data Data buffer
526 */
527 void vdisk_read ( uint64_t lba, unsigned int count, void *data ) {
528 struct vdisk_region *region;
529 void ( * build ) ( uint64_t lba, unsigned int count, void *data );
530 const char *name;
531 uint64_t start = lba;
532 uint64_t end = ( lba + count );
533 uint64_t frag_start = start;
534 uint64_t frag_end;
535 int file_idx;
536 uint64_t file_end;
537 uint64_t region_start;
538 uint64_t region_end;
539 unsigned int frag_count;
540 unsigned int i;
541
542 DBG2 ( "Read to %p from %#llx+%#x: ", data, lba, count );
543
544 do {
545 /* Initialise fragment to fill remaining space */
546 frag_end = end;
547 name = NULL;
548 build = NULL;
549
550 /* Truncate fragment and generate data */
551 file_idx = VDISK_FILE_IDX ( frag_start );
552 if ( file_idx >= 0 ) {
553
554 /* Truncate fragment to end of file */
555 file_end = VDISK_FILE_LBA ( file_idx + 1 );
556 if ( frag_end > file_end )
557 frag_end = file_end;
558
559 /* Generate data from file */
560 if ( file_idx < VDISK_MAX_FILES ) {
561 name = vdisk_files[file_idx].name;
562 build = vdisk_file;
563 }
564
565 } else {
566
567 /* Truncate fragment to region boundaries */
568 for ( i = 0 ; i < ( sizeof ( vdisk_regions ) /
569 sizeof ( vdisk_regions[0] ) ); i++){
570 region = &vdisk_regions[i];
571 region_start = region->lba;
572 region_end = ( region_start + region->count );
573
574 /* Avoid crossing start of any region */
575 if ( ( frag_start < region_start ) &&
576 ( frag_end > region_start ) ){
577 frag_end = region_start;
578 }
579
580 /* Ignore unless we overlap with this region */
581 if ( ( frag_start >= region_end ) ||
582 ( frag_end <= region_start ) ) {
583 continue;
584 }
585
586 /* Avoid crossing end of region */
587 if ( frag_end > region_end )
588 frag_end = region_end;
589
590 /* Found a suitable region */
591 name = region->name;
592 build = region->build;
593 break;
594 }
595 }
596
597 /* Generate data from this region */
598 frag_count = ( frag_end - frag_start );
599 DBG2 ( "%s%s (%#x)", ( ( frag_start == start ) ? "" : ", " ),
600 ( name ? name : "empty" ), frag_count );
601 if ( build ) {
602 build ( frag_start, frag_count, data );
603 } else {
604 memset ( data, 0, ( frag_count * VDISK_SECTOR_SIZE ) );
605 }
606
607 /* Move to next fragment */
608 frag_start += frag_count;
609 data += ( frag_count * VDISK_SECTOR_SIZE );
610
611 } while ( frag_start != end );
612
613 DBG2 ( "\n" );
614 }
615
616 /**
617 * Add file to virtual disk
618 *
619 * @v name Name
620 * @v opaque Opaque token
621 * @v len Length
622 * @v read Read data method
623 * @ret file Virtual file
624 */
625 struct vdisk_file * vdisk_add_file ( const char *name, void *opaque, size_t len,
626 void ( * read ) ( struct vdisk_file *file,
627 void *data,
628 size_t offset,
629 size_t len ) ) {
630 static unsigned int index = 0;
631 struct vdisk_file *file;
632
633 /* Sanity check */
634 if ( index >= VDISK_MAX_FILES )
635 die ( "Too many files\n" );
636
637 /* Store file */
638 file = &vdisk_files[index++];
639 snprintf ( file->name, sizeof ( file->name ), "%s", name );
640 file->opaque = opaque;
641 file->len = len;
642 file->xlen = len;
643 file->read = read;
644 DBG ( "Using %s via %p len %#zx\n", file->name, file->opaque,
645 file->len );
646
647 return file;
648 }
649
650 /**
651 * Patch virtual file
652 *
653 * @v file Virtual file
654 * @v patch Patch method
655 */
656 void vdisk_patch_file ( struct vdisk_file *file,
657 void ( * patch ) ( struct vdisk_file *file, void *data,
658 size_t offset, size_t len ) ) {
659
660 /* Record patch method */
661 file->patch = patch;
662
663 /* Allow patch method to update file length */
664 patch ( file, NULL, 0, 0 );
665 }