]> Dogcows Code - chaz/tar/blob - src/sparse.c
(dump_file0): The conditional at line
[chaz/tar] / src / sparse.c
1 /* Functions for dealing with sparse files
2
3 Copyright (C) 2003 Free Software Foundation, Inc.
4
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License as published by the
7 Free Software Foundation; either version 2, or (at your option) any later
8 version.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18
19 #include "system.h"
20 #include <quotearg.h>
21 #include "common.h"
22
23 struct tar_sparse_file;
24
25 enum sparse_scan_state
26 {
27 scan_begin,
28 scan_block,
29 scan_end
30 };
31
32 struct tar_sparse_optab
33 {
34 bool (*init) (struct tar_sparse_file *);
35 bool (*done) (struct tar_sparse_file *);
36 bool (*dump_header) (struct tar_sparse_file *);
37 bool (*decode_header) (struct tar_sparse_file *);
38 bool (*scan_block) (struct tar_sparse_file *, enum sparse_scan_state,
39 void *);
40 bool (*dump_region) (struct tar_sparse_file *, size_t index);
41 bool (*extract_region) (struct tar_sparse_file *, size_t index);
42 };
43
44 struct tar_sparse_file
45 {
46 int fd; /* File descriptor */
47 size_t dumped_size; /* Number of bytes actually written
48 to the archive */
49 struct tar_stat_info *stat_info; /* Information about the file */
50 struct tar_sparse_optab *optab;
51 void *closure; /* Any additional data optab calls might
52 reqiure */
53 };
54
55 static bool
56 tar_sparse_init (struct tar_sparse_file *file)
57 {
58 file->dumped_size = 0;
59 if (file->optab->init)
60 return file->optab->init (file);
61 return true;
62 }
63
64 static bool
65 tar_sparse_done (struct tar_sparse_file *file)
66 {
67 if (file->optab->done)
68 return file->optab->done (file);
69 return true;
70 }
71
72 static bool
73 tar_sparse_scan (struct tar_sparse_file *file, enum sparse_scan_state state,
74 void *block)
75 {
76 if (file->optab->scan_block)
77 return file->optab->scan_block (file, state, block);
78 return true;
79 }
80
81 static bool
82 tar_sparse_dump_region (struct tar_sparse_file *file, size_t index)
83 {
84 if (file->optab->dump_region)
85 return file->optab->dump_region (file, index);
86 return false;
87 }
88
89 static bool
90 tar_sparse_extract_region (struct tar_sparse_file *file, size_t index)
91 {
92 if (file->optab->extract_region)
93 return file->optab->extract_region (file, index);
94 return false;
95 }
96
97 static bool
98 tar_sparse_dump_header (struct tar_sparse_file *file)
99 {
100 if (file->optab->dump_header)
101 return file->optab->dump_header (file);
102 return false;
103 }
104
105 static bool
106 tar_sparse_decode_header (struct tar_sparse_file *file)
107 {
108 if (file->optab->decode_header)
109 return file->optab->decode_header (file);
110 return false;
111 }
112
113 \f
114 static bool
115 lseek_or_error (struct tar_sparse_file *file, off_t offset, int whence)
116 {
117 if (lseek (file->fd, offset, whence) < 0)
118 {
119 seek_diag_details (file->stat_info->orig_file_name, offset);
120 return false;
121 }
122 return true;
123 }
124
125 /* Takes a blockful of data and basically cruises through it to see if
126 it's made *entirely* of zeros, returning a 0 the instant it finds
127 something that is a nonzero, i.e., useful data. */
128 static bool
129 zero_block_p (char *buffer, size_t size)
130 {
131 while (size--)
132 if (*buffer++)
133 return false;
134 return true;
135 }
136
137 #define clear_block(p) memset (p, 0, BLOCKSIZE);
138
139 #define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
140
141 static void
142 sparse_add_map (struct tar_sparse_file *file, struct sp_array *sp)
143 {
144 if (file->stat_info->sparse_map == NULL)
145 {
146 file->stat_info->sparse_map =
147 xmalloc (SPARSES_INIT_COUNT * sizeof file->stat_info->sparse_map[0]);
148 file->stat_info->sparse_map_size = SPARSES_INIT_COUNT;
149 }
150 else if (file->stat_info->sparse_map_avail == file->stat_info->sparse_map_size)
151 {
152 file->stat_info->sparse_map_size *= 2;
153 file->stat_info->sparse_map =
154 xrealloc (file->stat_info->sparse_map,
155 file->stat_info->sparse_map_size
156 * sizeof file->stat_info->sparse_map[0]);
157 }
158 file->stat_info->sparse_map[file->stat_info->sparse_map_avail++] = *sp;
159 }
160
161 /* Scan the sparse file and create its map */
162 static bool
163 sparse_scan_file (struct tar_sparse_file *file)
164 {
165 static char buffer[BLOCKSIZE];
166 size_t count;
167 size_t offset = 0;
168 struct sp_array sp = {0, 0};
169
170 if (!lseek_or_error (file, 0, SEEK_SET))
171 return false;
172 clear_block (buffer);
173
174 file->stat_info->sparse_map_size = 0;
175 file->stat_info->archive_file_size = 0;
176
177 if (!tar_sparse_scan (file, scan_begin, NULL))
178 return false;
179
180 while ((count = safe_read (file->fd, buffer, sizeof buffer)) > 0)
181 {
182 /* Analize the block */
183 if (zero_block_p (buffer, count))
184 {
185 if (sp.numbytes)
186 {
187 sparse_add_map (file, &sp);
188 sp.numbytes = 0;
189 if (!tar_sparse_scan (file, scan_block, NULL))
190 return false;
191 }
192 }
193 else
194 {
195 if (sp.numbytes == 0)
196 sp.offset = offset;
197 sp.numbytes += count;
198 file->stat_info->archive_file_size += count;
199 if (!tar_sparse_scan (file, scan_block, buffer))
200 return false;
201 }
202
203 offset += count;
204 clear_block (buffer);
205 }
206
207 if (sp.numbytes == 0)
208 sp.offset = offset;
209
210 sparse_add_map (file, &sp);
211 file->stat_info->archive_file_size += count;
212 return tar_sparse_scan (file, scan_end, NULL);
213 }
214
215 static struct tar_sparse_optab oldgnu_optab;
216 static struct tar_sparse_optab star_optab;
217 static struct tar_sparse_optab pax_optab;
218
219 static bool
220 sparse_select_optab (struct tar_sparse_file *file)
221 {
222 switch (current_format == DEFAULT_FORMAT ? archive_format : current_format)
223 {
224 case V7_FORMAT:
225 case USTAR_FORMAT:
226 return false;
227
228 case OLDGNU_FORMAT:
229 case GNU_FORMAT: /*FIXME: This one should disappear? */
230 file->optab = &oldgnu_optab;
231 break;
232
233 case POSIX_FORMAT:
234 file->optab = &pax_optab;
235 break;
236
237 case STAR_FORMAT:
238 file->optab = &star_optab;
239 break;
240
241 default:
242 return false;
243 }
244 return true;
245 }
246
247 static bool
248 sparse_dump_region (struct tar_sparse_file *file, size_t index)
249 {
250 union block *blk;
251 off_t bytes_left = file->stat_info->sparse_map[index].numbytes;
252
253 if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
254 SEEK_SET))
255 return false;
256
257 while (bytes_left > 0)
258 {
259 size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left;
260 off_t bytes_read;
261
262 blk = find_next_block ();
263 memset (blk->buffer, 0, BLOCKSIZE);
264 bytes_read = safe_read (file->fd, blk->buffer, bufsize);
265 if (bytes_read < 0)
266 {
267 read_diag_details (file->stat_info->orig_file_name,
268 file->stat_info->sparse_map[index].offset
269 + file->stat_info->sparse_map[index].numbytes
270 - bytes_left,
271 bufsize);
272 return false;
273 }
274
275 bytes_left -= bytes_read;
276 file->dumped_size += bytes_read;
277 set_next_block_after (blk);
278 }
279
280 return true;
281 }
282
283 static bool
284 sparse_extract_region (struct tar_sparse_file *file, size_t index)
285 {
286 size_t write_size;
287
288 if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
289 SEEK_SET))
290 return false;
291
292 write_size = file->stat_info->sparse_map[index].numbytes;
293
294 if (write_size == 0)
295 {
296 /* Last block of the file is a hole */
297 if (sys_truncate (file->fd))
298 truncate_warn (file->stat_info->orig_file_name);
299 }
300 else while (write_size > 0)
301 {
302 size_t count;
303 size_t wrbytes = (write_size > BLOCKSIZE) ? BLOCKSIZE : write_size;
304 union block *blk = find_next_block ();
305 if (!blk)
306 {
307 ERROR ((0, 0, _("Unexpected EOF in archive")));
308 return false;
309 }
310 set_next_block_after (blk);
311 count = full_write (file->fd, blk->buffer, wrbytes);
312 write_size -= count;
313 file->dumped_size += count;
314 if (count != wrbytes)
315 {
316 write_error_details (file->stat_info->orig_file_name,
317 count, wrbytes);
318 return false;
319 }
320 }
321 return true;
322 }
323
324 \f
325
326 /* Interface functions */
327 enum dump_status
328 sparse_dump_file (int fd, struct tar_stat_info *stat)
329 {
330 bool rc;
331 struct tar_sparse_file file;
332
333 file.stat_info = stat;
334 file.fd = fd;
335
336 if (!sparse_select_optab (&file)
337 || !tar_sparse_init (&file))
338 return dump_status_not_implemented;
339
340 rc = sparse_scan_file (&file);
341 if (rc && file.optab->dump_region)
342 {
343 tar_sparse_dump_header (&file);
344
345 if (fd >= 0)
346 {
347 size_t i;
348
349 for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
350 rc = tar_sparse_dump_region (&file, i);
351 }
352 }
353
354 pad_archive(file.stat_info->archive_file_size - file.dumped_size);
355 return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
356 }
357
358 /* Returns true if the file represented by stat is a sparse one */
359 bool
360 sparse_file_p (struct tar_stat_info *stat)
361 {
362 return (ST_NBLOCKS (stat->stat)
363 < (stat->stat.st_size / ST_NBLOCKSIZE
364 + (stat->stat.st_size % ST_NBLOCKSIZE != 0)));
365 }
366
367 enum dump_status
368 sparse_extract_file (int fd, struct tar_stat_info *stat, off_t *size)
369 {
370 bool rc = true;
371 struct tar_sparse_file file;
372 size_t i;
373
374 file.stat_info = stat;
375 file.fd = fd;
376
377 if (!sparse_select_optab (&file)
378 || !tar_sparse_init (&file))
379 return dump_status_not_implemented;
380
381 rc = tar_sparse_decode_header (&file);
382 for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
383 rc = tar_sparse_extract_region (&file, i);
384 *size = file.stat_info->archive_file_size - file.dumped_size;
385 return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
386 }
387
388 \f
389 static char diff_buffer[BLOCKSIZE];
390
391 static bool
392 check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
393 {
394 if (!lseek_or_error (file, beg, SEEK_SET))
395 return false;
396
397 while (beg < end)
398 {
399 size_t bytes_read;
400 size_t rdsize = end - beg;
401
402 if (rdsize > BLOCKSIZE)
403 rdsize = BLOCKSIZE;
404 clear_block (diff_buffer);
405 bytes_read = safe_read (file->fd, diff_buffer, rdsize);
406 if (bytes_read < 0)
407 {
408 read_diag_details (file->stat_info->orig_file_name,
409 beg,
410 rdsize);
411 return false;
412 }
413 if (!zero_block_p (diff_buffer, bytes_read))
414 {
415 report_difference (file->stat_info,
416 _("File fragment at %lu is not a hole"), beg);
417 return false;
418 }
419
420 beg += bytes_read;
421 }
422 return true;
423 }
424
425 static bool
426 check_data_region (struct tar_sparse_file *file, size_t index)
427 {
428 size_t size_left;
429
430 if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
431 SEEK_SET))
432 return false;
433 size_left = file->stat_info->sparse_map[index].numbytes;
434 while (size_left > 0)
435 {
436 size_t bytes_read;
437 size_t rdsize = (size_left > BLOCKSIZE) ? BLOCKSIZE : size_left;
438
439 union block *blk = find_next_block ();
440 if (!blk)
441 {
442 ERROR ((0, 0, _("Unexpected EOF in archive")));
443 return false;
444 }
445 set_next_block_after (blk);
446 bytes_read = safe_read (file->fd, diff_buffer, rdsize);
447 if (bytes_read < 0)
448 {
449 read_diag_details (file->stat_info->orig_file_name,
450 file->stat_info->sparse_map[index].offset
451 + file->stat_info->sparse_map[index].numbytes
452 - size_left,
453 rdsize);
454 return false;
455 }
456 file->dumped_size += bytes_read;
457 size_left -= bytes_read;
458 if (memcmp (blk->buffer, diff_buffer, rdsize))
459 {
460 report_difference (file->stat_info, _("Contents differ"));
461 return false;
462 }
463 }
464 return true;
465 }
466
467 bool
468 sparse_diff_file (int fd, struct tar_stat_info *stat)
469 {
470 bool rc = true;
471 struct tar_sparse_file file;
472 size_t i;
473 off_t offset = 0;
474
475 file.stat_info = stat;
476 file.fd = fd;
477
478 if (!sparse_select_optab (&file)
479 || !tar_sparse_init (&file))
480 return dump_status_not_implemented;
481
482 rc = tar_sparse_decode_header (&file);
483 for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
484 {
485 rc = check_sparse_region (&file,
486 offset, file.stat_info->sparse_map[i].offset)
487 && check_data_region (&file, i);
488 offset = file.stat_info->sparse_map[i].offset
489 + file.stat_info->sparse_map[i].numbytes;
490 }
491
492 if (!rc)
493 skip_file (file.stat_info->archive_file_size - file.dumped_size);
494
495 tar_sparse_done (&file);
496 return rc;
497 }
498
499 \f
500 /* Old GNU Format. The sparse file information is stored in the
501 oldgnu_header in the following manner:
502
503 The header is marked with type 'S'. Its `size' field contains
504 the cumulative size of all non-empty blocks of the file. The
505 actual file size is stored in `realsize' member of oldgnu_header.
506
507 The map of the file is stored in a list of `struct sparse'.
508 Each struct contains offset to the block of data and its
509 size (both as octal numbers). The first file header contains
510 at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
511 contains more structs, then the field `isextended' of the main
512 header is set to 1 (binary) and the `struct sparse_header'
513 header follows, containing at most 21 following structs
514 (SPARSES_IN_SPARSE_HEADER). If more structs follow, `isextended'
515 field of the extended header is set and next next extension header
516 follows, etc... */
517
518 enum oldgnu_add_status
519 {
520 add_ok,
521 add_finish,
522 add_fail
523 };
524
525 /* Add a sparse item to the sparse file and its obstack */
526 static enum oldgnu_add_status
527 oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
528 {
529 struct sp_array sp;
530
531 if (s->numbytes[0] == '\0')
532 return add_finish;
533 sp.offset = OFF_FROM_HEADER (s->offset);
534 sp.numbytes = SIZE_FROM_HEADER (s->numbytes);
535 if (sp.offset < 0
536 || file->stat_info->stat.st_size < sp.offset + sp.numbytes
537 || file->stat_info->archive_file_size < 0)
538 return add_fail;
539
540 sparse_add_map (file, &sp);
541 return add_ok;
542 }
543
544 /* Convert old GNU format sparse data to internal representation
545 FIXME: Clubbers current_header! */
546 static bool
547 oldgnu_get_sparse_info (struct tar_sparse_file *file)
548 {
549 size_t i;
550 union block *h = current_header;
551 int ext_p;
552 static enum oldgnu_add_status rc;
553
554 /* FIXME: note this! st_size was initialized from the header
555 which actually contains archived size. The following fixes it */
556 file->stat_info->archive_file_size = file->stat_info->stat.st_size;
557 file->stat_info->stat.st_size =
558 OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
559
560 file->stat_info->sparse_map_size = 0;
561 for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
562 {
563 rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
564 if (rc != add_ok)
565 break;
566 }
567
568 for (ext_p = h->oldgnu_header.isextended;
569 rc == add_ok && ext_p; ext_p = h->sparse_header.isextended)
570 {
571 h = find_next_block ();
572 if (!h)
573 {
574 ERROR ((0, 0, _("Unexpected EOF in archive")));
575 return false;
576 }
577 set_next_block_after (h);
578 for (i = 0; i < SPARSES_IN_SPARSE_HEADER && rc == add_ok; i++)
579 rc = oldgnu_add_sparse (file, &h->sparse_header.sp[i]);
580 }
581
582 if (rc == add_fail)
583 {
584 ERROR ((0, 0, _("%s: invalid sparse archive member"),
585 file->stat_info->orig_file_name));
586 return false;
587 }
588 return true;
589 }
590
591 static void
592 oldgnu_store_sparse_info (struct tar_sparse_file *file, size_t *pindex,
593 struct sparse *sp, size_t sparse_size)
594 {
595 for (; *pindex < file->stat_info->sparse_map_avail
596 && sparse_size > 0; sparse_size--, sp++, ++*pindex)
597 {
598 OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].offset,
599 sp->offset);
600 SIZE_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
601 sp->numbytes);
602 }
603 }
604
605 static bool
606 oldgnu_dump_header (struct tar_sparse_file *file)
607 {
608 off_t block_ordinal = current_block_ordinal ();
609 union block *blk;
610 size_t i;
611
612 blk = start_header (file->stat_info);
613 blk->header.typeflag = GNUTYPE_SPARSE;
614 if (file->stat_info->sparse_map_avail > SPARSES_IN_OLDGNU_HEADER)
615 blk->oldgnu_header.isextended = 1;
616
617 /* Store the real file size */
618 OFF_TO_CHARS (file->stat_info->stat.st_size, blk->oldgnu_header.realsize);
619 /* Store the effective (shrunken) file size */
620 OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
621
622 i = 0;
623 oldgnu_store_sparse_info (file, &i,
624 blk->oldgnu_header.sp,
625 SPARSES_IN_OLDGNU_HEADER);
626 blk->oldgnu_header.isextended = i < file->stat_info->sparse_map_avail;
627 finish_header (file->stat_info, blk, block_ordinal);
628
629 while (i < file->stat_info->sparse_map_avail)
630 {
631 blk = find_next_block ();
632 memset (blk->buffer, 0, BLOCKSIZE);
633 oldgnu_store_sparse_info (file, &i,
634 blk->sparse_header.sp,
635 SPARSES_IN_SPARSE_HEADER);
636 set_next_block_after (blk);
637 if (i < file->stat_info->sparse_map_avail)
638 blk->sparse_header.isextended = 1;
639 else
640 break;
641 }
642 return true;
643 }
644
645 static struct tar_sparse_optab oldgnu_optab = {
646 NULL, /* No init function */
647 NULL, /* No done function */
648 oldgnu_dump_header,
649 oldgnu_get_sparse_info,
650 NULL, /* No scan_block function */
651 sparse_dump_region,
652 sparse_extract_region,
653 };
654
655 \f
656 /* Star */
657
658 /* Convert STAR format sparse data to internal representation
659 FIXME: Clubbers current_header! */
660 static bool
661 star_get_sparse_info (struct tar_sparse_file *file)
662 {
663 size_t i;
664 union block *h = current_header;
665 int ext_p;
666 static enum oldgnu_add_status rc;
667
668 /* FIXME: note this! st_size was initialized from the header
669 which actually contains archived size. The following fixes it */
670 file->stat_info->archive_file_size = file->stat_info->stat.st_size;
671 file->stat_info->stat.st_size =
672 OFF_FROM_HEADER (current_header->star_in_header.realsize);
673
674 file->stat_info->sparse_map_size = 0;
675
676 if (h->star_in_header.prefix[0] == '\0'
677 && h->star_in_header.sp[0].offset[10] != '\0')
678 {
679 /* Old star format */
680 for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
681 {
682 rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
683 if (rc != add_ok)
684 break;
685 }
686 ext_p = h->star_in_header.isextended;
687 }
688 else
689 ext_p = 1;
690
691 for (; rc == add_ok && ext_p; ext_p = h->star_ext_header.isextended)
692 {
693 h = find_next_block ();
694 if (!h)
695 {
696 ERROR ((0, 0, _("Unexpected EOF in archive")));
697 return false;
698 }
699 set_next_block_after (h);
700 for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
701 rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
702 }
703
704 if (rc == add_fail)
705 {
706 ERROR ((0, 0, _("%s: invalid sparse archive member"),
707 file->stat_info->orig_file_name));
708 return false;
709 }
710 return true;
711 }
712
713
714 static struct tar_sparse_optab star_optab = {
715 NULL, /* No init function */
716 NULL, /* No done function */
717 NULL,
718 star_get_sparse_info,
719 NULL, /* No scan_block function */
720 NULL, /* No dump region function */
721 sparse_extract_region,
722 };
723
724 \f
725 /* GNU PAX sparse file format. The sparse file map is stored in
726 x header:
727
728 GNU.sparse.size Real size of the stored file
729 GNU.sparse.numblocks Number of blocks in the sparse map
730 repeat numblocks time
731 GNU.sparse.offset Offset of the next data block
732 GNU.sparse.numbytes Size of the next data block
733 end repeat
734 */
735
736 static bool
737 pax_dump_header (struct tar_sparse_file *file)
738 {
739 off_t block_ordinal = current_block_ordinal ();
740 union block *blk;
741 size_t i;
742
743 /* Store the real file size */
744 xheader_store ("GNU.sparse.size", file->stat_info, NULL);
745 xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
746 for (i = 0; i < file->stat_info->sparse_map_avail; i++)
747 {
748 xheader_store ("GNU.sparse.offset", file->stat_info, &i);
749 xheader_store ("GNU.sparse.numbytes", file->stat_info, &i);
750 }
751
752 blk = start_header (file->stat_info);
753 /* Store the effective (shrunken) file size */
754 OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
755 finish_header (file->stat_info, blk, block_ordinal);
756 return true;
757 }
758
759 static bool
760 pax_decode_header (struct tar_sparse_file *file)
761 {
762 /* Restore actual size */
763 size_t s = file->stat_info->archive_file_size;
764 file->stat_info->archive_file_size = file->stat_info->stat.st_size;
765 file->stat_info->stat.st_size = s;
766 return true;
767 }
768
769 static struct tar_sparse_optab pax_optab = {
770 NULL, /* No init function */
771 NULL, /* No done function */
772 pax_dump_header,
773 pax_decode_header,
774 NULL, /* No scan_block function */
775 sparse_dump_region,
776 sparse_extract_region,
777 };
778
This page took 0.068697 seconds and 4 git commands to generate.