]> Dogcows Code - chaz/tar/blob - src/create.c
01324d353bf62d082ab8599f6e5c36b0f6121f9a
[chaz/tar] / src / create.c
1 /* Create a tar archive.
2
3 Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
4 2003 Free Software Foundation, Inc.
5
6 Written by John Gilmore, on 1985-08-25.
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 Public License for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21
22 #include "system.h"
23
24 #if HAVE_UTIME_H
25 # include <utime.h>
26 #else
27 struct utimbuf
28 {
29 long actime;
30 long modtime;
31 };
32 #endif
33
34 #include <quotearg.h>
35
36 #include "common.h"
37 #include <hash.h>
38
39 struct link
40 {
41 dev_t dev;
42 ino_t ino;
43 size_t nlink;
44 char name[1];
45 };
46 \f
47 /* The maximum uintmax_t value that can be represented with DIGITS digits,
48 assuming that each digit is BITS_PER_DIGIT wide. */
49 #define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \
50 ((digits) * (bits_per_digit) < sizeof (uintmax_t) * CHAR_BIT \
51 ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
52 : (uintmax_t) -1)
53
54 /* Convert VALUE to an octal representation suitable for tar headers.
55 Output to buffer WHERE with size SIZE.
56 The result is undefined if SIZE is 0 or if VALUE is too large to fit. */
57
58 static void
59 to_octal (uintmax_t value, char *where, size_t size)
60 {
61 uintmax_t v = value;
62 size_t i = size;
63
64 do
65 {
66 where[--i] = '0' + (v & ((1 << LG_8) - 1));
67 v >>= LG_8;
68 }
69 while (i);
70 }
71
72 /* Convert NEGATIVE VALUE to a base-256 representation suitable for
73 tar headers. NEGATIVE is 1 if VALUE was negative before being cast
74 to uintmax_t, 0 otherwise. Output to buffer WHERE with size SIZE.
75 The result is undefined if SIZE is 0 or if VALUE is too large to
76 fit. */
77
78 static void
79 to_base256 (int negative, uintmax_t value, char *where, size_t size)
80 {
81 uintmax_t v = value;
82 uintmax_t propagated_sign_bits =
83 ((uintmax_t) - negative << (CHAR_BIT * sizeof v - LG_256));
84 size_t i = size;
85
86 do
87 {
88 where[--i] = v & ((1 << LG_256) - 1);
89 v = propagated_sign_bits | (v >> LG_256);
90 }
91 while (i);
92 }
93
94 /* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
95 external form, using SUBSTITUTE (...) if VALUE won't fit. Output
96 to buffer WHERE with size SIZE. NEGATIVE is 1 iff VALUE was
97 negative before being cast to uintmax_t; its original bitpattern
98 can be deduced from VALSIZE, its original size before casting.
99 TYPE is the kind of value being output (useful for diagnostics).
100 Prefer the POSIX format of SIZE - 1 octal digits (with leading zero
101 digits), followed by '\0'. If this won't work, and if GNU or
102 OLDGNU format is allowed, use '\200' followed by base-256, or (if
103 NEGATIVE is nonzero) '\377' followed by two's complement base-256.
104 If neither format works, use SUBSTITUTE (...) instead. Pass to
105 SUBSTITUTE the address of an 0-or-1 flag recording whether the
106 substitute value is negative. */
107
108 static void
109 to_chars (int negative, uintmax_t value, size_t valsize,
110 uintmax_t (*substitute) (int *),
111 char *where, size_t size, const char *type)
112 {
113 int base256_allowed = (archive_format == GNU_FORMAT
114 || archive_format == OLDGNU_FORMAT);
115
116 /* Generate the POSIX octal representation if the number fits. */
117 if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
118 {
119 where[size - 1] = '\0';
120 to_octal (value, where, size - 1);
121 }
122
123 /* Otherwise, generate the base-256 representation if we are
124 generating an old or new GNU format and if the number fits. */
125 else if (((negative ? -1 - value : value)
126 <= MAX_VAL_WITH_DIGITS (size - 1, LG_256))
127 && base256_allowed)
128 {
129 where[0] = negative ? -1 : 1 << (LG_256 - 1);
130 to_base256 (negative, value, where + 1, size - 1);
131 }
132
133 /* Otherwise, if the number is negative, and if it would not cause
134 ambiguity on this host by confusing positive with negative
135 values, then generate the POSIX octal representation of the value
136 modulo 2**(field bits). The resulting tar file is
137 machine-dependent, since it depends on the host word size. Yuck!
138 But this is the traditional behavior. */
139 else if (negative && valsize * CHAR_BIT <= (size - 1) * LG_8)
140 {
141 static int warned_once;
142 if (! warned_once)
143 {
144 warned_once = 1;
145 WARN ((0, 0, _("Generating negative octal headers")));
146 }
147 where[size - 1] = '\0';
148 to_octal (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
149 where, size - 1);
150 }
151
152 /* Otherwise, output a substitute value if possible (with a
153 warning), and an error message if not. */
154 else
155 {
156 uintmax_t maxval = (base256_allowed
157 ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
158 : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
159 char valbuf[UINTMAX_STRSIZE_BOUND + 1];
160 char maxbuf[UINTMAX_STRSIZE_BOUND];
161 char minbuf[UINTMAX_STRSIZE_BOUND + 1];
162 char const *minval_string;
163 char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf);
164 char const *value_string;
165
166 if (base256_allowed)
167 {
168 uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1;
169 char *p = STRINGIFY_BIGINT (m, minbuf + 1);
170 *--p = '-';
171 minval_string = p;
172 }
173 else
174 minval_string = "0";
175
176 if (negative)
177 {
178 char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
179 *--p = '-';
180 value_string = p;
181 }
182 else
183 value_string = STRINGIFY_BIGINT (value, valbuf);
184
185 if (substitute)
186 {
187 int negsub;
188 uintmax_t sub = substitute (&negsub) & maxval;
189 uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub;
190 char subbuf[UINTMAX_STRSIZE_BOUND + 1];
191 char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1);
192 if (negsub)
193 *--sub_string = '-';
194 WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
195 value_string, type, minval_string, maxval_string,
196 sub_string));
197 to_chars (negsub, s, valsize, 0, where, size, type);
198 }
199 else
200 ERROR ((0, 0, _("value %s out of %s range %s..%s"),
201 value_string, type, minval_string, maxval_string));
202 }
203 }
204
205 static uintmax_t
206 gid_substitute (int *negative)
207 {
208 gid_t r;
209 #ifdef GID_NOBODY
210 r = GID_NOBODY;
211 #else
212 static gid_t gid_nobody;
213 if (!gid_nobody && !gname_to_gid ("nobody", &gid_nobody))
214 gid_nobody = -2;
215 r = gid_nobody;
216 #endif
217 *negative = r < 0;
218 return r;
219 }
220
221 void
222 gid_to_chars (gid_t v, char *p, size_t s)
223 {
224 to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
225 }
226
227 void
228 major_to_chars (major_t v, char *p, size_t s)
229 {
230 to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
231 }
232
233 void
234 minor_to_chars (minor_t v, char *p, size_t s)
235 {
236 to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
237 }
238
239 void
240 mode_to_chars (mode_t v, char *p, size_t s)
241 {
242 /* In the common case where the internal and external mode bits are the same,
243 and we are not using POSIX or GNU format,
244 propagate all unknown bits to the external mode.
245 This matches historical practice.
246 Otherwise, just copy the bits we know about. */
247 int negative;
248 uintmax_t u;
249 if (S_ISUID == TSUID && S_ISGID == TSGID && S_ISVTX == TSVTX
250 && S_IRUSR == TUREAD && S_IWUSR == TUWRITE && S_IXUSR == TUEXEC
251 && S_IRGRP == TGREAD && S_IWGRP == TGWRITE && S_IXGRP == TGEXEC
252 && S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC
253 && archive_format != POSIX_FORMAT
254 && archive_format != USTAR_FORMAT
255 && archive_format != GNU_FORMAT)
256 {
257 negative = v < 0;
258 u = v;
259 }
260 else
261 {
262 negative = 0;
263 u = ((v & S_ISUID ? TSUID : 0)
264 | (v & S_ISGID ? TSGID : 0)
265 | (v & S_ISVTX ? TSVTX : 0)
266 | (v & S_IRUSR ? TUREAD : 0)
267 | (v & S_IWUSR ? TUWRITE : 0)
268 | (v & S_IXUSR ? TUEXEC : 0)
269 | (v & S_IRGRP ? TGREAD : 0)
270 | (v & S_IWGRP ? TGWRITE : 0)
271 | (v & S_IXGRP ? TGEXEC : 0)
272 | (v & S_IROTH ? TOREAD : 0)
273 | (v & S_IWOTH ? TOWRITE : 0)
274 | (v & S_IXOTH ? TOEXEC : 0));
275 }
276 to_chars (negative, u, sizeof v, 0, p, s, "mode_t");
277 }
278
279 void
280 off_to_chars (off_t v, char *p, size_t s)
281 {
282 to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
283 }
284
285 void
286 size_to_chars (size_t v, char *p, size_t s)
287 {
288 to_chars (0, (uintmax_t) v, sizeof v, 0, p, s, "size_t");
289 }
290
291 void
292 time_to_chars (time_t v, char *p, size_t s)
293 {
294 to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "time_t");
295 }
296
297 static uintmax_t
298 uid_substitute (int *negative)
299 {
300 uid_t r;
301 #ifdef UID_NOBODY
302 r = UID_NOBODY;
303 #else
304 static uid_t uid_nobody;
305 if (!uid_nobody && !uname_to_uid ("nobody", &uid_nobody))
306 uid_nobody = -2;
307 r = uid_nobody;
308 #endif
309 *negative = r < 0;
310 return r;
311 }
312
313 void
314 uid_to_chars (uid_t v, char *p, size_t s)
315 {
316 to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
317 }
318
319 void
320 uintmax_to_chars (uintmax_t v, char *p, size_t s)
321 {
322 to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
323 }
324
325 void
326 string_to_chars (char *str, char *p, size_t s)
327 {
328 strncpy (p, str, s);
329 p[s-1] = 0;
330 }
331
332 \f
333 /* A file is not dumpable if
334 a) it is empty *and* world-readable, or
335 b) current archive is /dev/null */
336
337 bool
338 file_dumpable_p (struct tar_stat_info *stat)
339 {
340 return !(dev_null_output
341 || (stat->archive_file_size == 0
342 && (stat->stat.st_mode & MODE_R) == MODE_R));
343 }
344
345 \f
346 /* Writing routines. */
347
348 /* Write the EOT block(s). Zero at least two blocks, through the end
349 of the record. Old tar, as previous versions of GNU tar, writes
350 garbage after two zeroed blocks. */
351 void
352 write_eot (void)
353 {
354 union block *pointer = find_next_block ();
355 memset (pointer->buffer, 0, BLOCKSIZE);
356 set_next_block_after (pointer);
357 pointer = find_next_block ();
358 memset (pointer->buffer, 0, available_space_after (pointer));
359 set_next_block_after (pointer);
360 }
361
362 /* Copy at most LEN bytes from SRC to DST. Terminate with NUL unless
363 SRC is LEN characters long */
364 static void
365 tar_copy_str (char *dst, const char *src, size_t len)
366 {
367 dst[len-1] = 0;
368 strncpy (dst, src, len);
369 }
370
371 /* Write a "private" header */
372 static union block *
373 start_private_header (const char *name, size_t size)
374 {
375 time_t t;
376 union block *header = find_next_block ();
377
378 memset (header->buffer, 0, sizeof (union block));
379
380 tar_copy_str (header->header.name, name, NAME_FIELD_SIZE);
381 OFF_TO_CHARS (size, header->header.size);
382
383 time (&t);
384 TIME_TO_CHARS (t, header->header.mtime);
385 MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
386 UID_TO_CHARS (getuid (), header->header.uid);
387 GID_TO_CHARS (getgid (), header->header.gid);
388 MAJOR_TO_CHARS (0, header->header.devmajor);
389 MAJOR_TO_CHARS (0, header->header.devminor);
390 strncpy (header->header.magic, TMAGIC, TMAGLEN);
391 strncpy (header->header.version, TVERSION, TVERSLEN);
392 return header;
393 }
394
395 /* Create a new header and store there at most NAME_FIELD_SIZE bytes of
396 the file name */
397
398 static union block *
399 write_short_name (struct tar_stat_info *st)
400 {
401 union block *header = find_next_block ();
402 memset (header->buffer, 0, sizeof (union block));
403 tar_copy_str (header->header.name, st->file_name, NAME_FIELD_SIZE);
404 return header;
405 }
406
407 /* Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block. */
408 static void
409 write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
410 {
411 size_t size = strlen (p) + 1;
412 size_t bufsize;
413 union block *header;
414
415 header = start_private_header ("././@LongLink", size);
416 strcpy (header->header.magic, OLDGNU_MAGIC);
417 header->header.typeflag = type;
418 finish_header (st, header, -1);
419
420 header = find_next_block ();
421
422 bufsize = available_space_after (header);
423
424 while (bufsize < size)
425 {
426 memcpy (header->buffer, p, bufsize);
427 p += bufsize;
428 size -= bufsize;
429 set_next_block_after (header + (bufsize - 1) / BLOCKSIZE);
430 header = find_next_block ();
431 bufsize = available_space_after (header);
432 }
433 memcpy (header->buffer, p, size);
434 memset (header->buffer + size, 0, bufsize - size);
435 set_next_block_after (header + (size - 1) / BLOCKSIZE);
436 }
437
438 static size_t
439 split_long_name (const char *name, size_t length)
440 {
441 size_t i;
442
443 if (length > PREFIX_FIELD_SIZE)
444 length = PREFIX_FIELD_SIZE+2;
445 for (i = length - 1; i > 0; i--)
446 if (ISSLASH (name[i]))
447 break;
448 return i;
449 }
450
451 static union block *
452 write_ustar_long_name (const char *name)
453 {
454 size_t length = strlen (name);
455 size_t i;
456 union block *header;
457
458 if (length > PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1)
459 {
460 WARN ((0, 0, _("%s: file name is too long (max %d); not dumped"),
461 quotearg_colon (name),
462 PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1));
463 return NULL;
464 }
465
466 i = split_long_name (name, length);
467 if (i == 0 || length - i - 1 > NAME_FIELD_SIZE)
468 {
469 WARN ((0, 0,
470 _("%s: file name is too long (cannot be split); not dumped"),
471 quotearg_colon (name)));
472 return NULL;
473 }
474
475 header = find_next_block ();
476 memset (header->buffer, 0, sizeof (header->buffer));
477 memcpy (header->header.prefix, name, i);
478 memcpy (header->header.name, name + i + 1, length - i - 1);
479
480 return header;
481 }
482
483 /* Write a long link name, depending on the current archive format */
484 static void
485 write_long_link (struct tar_stat_info *st)
486 {
487 switch (archive_format)
488 {
489 case POSIX_FORMAT:
490 xheader_store ("linkpath", st, NULL);
491 break;
492
493 case V7_FORMAT: /* old V7 tar format */
494 case USTAR_FORMAT:
495 case STAR_FORMAT:
496 WARN ((0, 0,
497 _("%s: link name is too long; not dumped"),
498 quotearg_colon (st->link_name)));
499 break;
500
501 case OLDGNU_FORMAT:
502 case GNU_FORMAT:
503 write_gnu_long_link (st, st->link_name, GNUTYPE_LONGLINK);
504 break;
505
506 default:
507 abort(); /*FIXME*/
508 }
509 }
510
511 static union block *
512 write_long_name (struct tar_stat_info *st)
513 {
514 switch (archive_format)
515 {
516 case POSIX_FORMAT:
517 xheader_store ("path", st, NULL);
518 break;
519
520 case V7_FORMAT:
521 case USTAR_FORMAT:
522 case STAR_FORMAT:
523 return write_ustar_long_name (st->file_name);
524
525 case OLDGNU_FORMAT:
526 case GNU_FORMAT:
527 write_gnu_long_link (st, st->file_name, GNUTYPE_LONGNAME);
528 break;
529
530 default:
531 abort(); /*FIXME*/
532 }
533 return write_short_name (st);
534 }
535
536 static union block *
537 write_extended (struct tar_stat_info *st, union block *old_header, char type)
538 {
539 union block *header, hp;
540 size_t size;
541 char *p;
542
543 if (extended_header.buffer || extended_header.stk == NULL)
544 return old_header;
545
546 xheader_finish (&extended_header);
547 size = extended_header.size;
548
549 memcpy (hp.buffer, old_header, sizeof (hp));
550
551 header = start_private_header (p = xheader_xhdr_name (st), size);
552 free (p);
553 header->header.typeflag = type;
554
555 finish_header (st, header, -1);
556
557 p = extended_header.buffer;
558
559 do
560 {
561 size_t len;
562
563 header = find_next_block ();
564 len = BLOCKSIZE;
565 if (len > size)
566 len = size;
567 memcpy (header->buffer, p, len);
568 if (len < BLOCKSIZE)
569 memset (header->buffer + len, 0, BLOCKSIZE - len);
570 p += len;
571 size -= len;
572 set_next_block_after (header);
573 }
574 while (size > 0);
575
576 xheader_destroy (&extended_header);
577 header = find_next_block ();
578 memcpy (header, &hp.buffer, sizeof (hp.buffer));
579 return header;
580 }
581
582 static union block *
583 write_header_name (struct tar_stat_info *st)
584 {
585 if (NAME_FIELD_SIZE < strlen (st->file_name))
586 return write_long_name (st);
587 else
588 return write_short_name (st);
589 }
590
591 \f
592 /* Header handling. */
593
594 /* Make a header block for the file whose stat info is st,
595 and return its address. */
596
597 union block *
598 start_header (struct tar_stat_info *st)
599 {
600 union block *header;
601
602 header = write_header_name (st);
603 if (!header)
604 return NULL;
605
606 /* Override some stat fields, if requested to do so. */
607
608 if (owner_option != (uid_t) -1)
609 st->stat.st_uid = owner_option;
610 if (group_option != (gid_t) -1)
611 st->stat.st_gid = group_option;
612 if (mode_option)
613 st->stat.st_mode = ((st->stat.st_mode & ~MODE_ALL)
614 | mode_adjust (st->stat.st_mode, mode_option));
615
616 /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
617 for a few tars and came up with the following interoperability
618 matrix:
619
620 WRITER
621 1 2 3 4 5 6 7 8 9 READER
622 . . . . . . . . . 1 = SunOS 4.2 tar
623 # . . # # . . # # 2 = NEC SVR4.0.2 tar
624 . . . # # . . # . 3 = Solaris 2.1 tar
625 . . . . . . . . . 4 = GNU tar 1.11.1
626 . . . . . . . . . 5 = HP-UX 8.07 tar
627 . . . . . . . . . 6 = Ultrix 4.1
628 . . . . . . . . . 7 = AIX 3.2
629 . . . . . . . . . 8 = Hitachi HI-UX 1.03
630 . . . . . . . . . 9 = Omron UNIOS-B 4.3BSD 1.60Beta
631
632 . = works
633 # = ``impossible file type''
634
635 The following mask for old archive removes the `#'s in column 4
636 above, thus making GNU tar both a universal donor and a universal
637 acceptor for Paul's test. */
638
639 if (archive_format == V7_FORMAT || archive_format == USTAR_FORMAT)
640 MODE_TO_CHARS (st->stat.st_mode & MODE_ALL, header->header.mode);
641 else
642 MODE_TO_CHARS (st->stat.st_mode, header->header.mode);
643
644 if (st->stat.st_uid > MAXOCTAL7 && archive_format == POSIX_FORMAT)
645 xheader_store ("uid", st, NULL);
646 else
647 UID_TO_CHARS (st->stat.st_uid, header->header.uid);
648
649 if (st->stat.st_gid > MAXOCTAL7 && archive_format == POSIX_FORMAT)
650 xheader_store ("gid", st, NULL);
651 else
652 GID_TO_CHARS (st->stat.st_gid, header->header.gid);
653
654 if (st->stat.st_size > MAXOCTAL11 && archive_format == POSIX_FORMAT)
655 xheader_store ("size", st, NULL);
656 else
657 OFF_TO_CHARS (st->stat.st_size, header->header.size);
658
659 TIME_TO_CHARS (st->stat.st_mtime, header->header.mtime);
660
661 /* FIXME */
662 if (S_ISCHR (st->stat.st_mode)
663 || S_ISBLK (st->stat.st_mode))
664 {
665 st->devmajor = major (st->stat.st_rdev);
666 st->devminor = minor (st->stat.st_rdev);
667
668 if (st->devmajor > MAXOCTAL7 && archive_format == POSIX_FORMAT)
669 xheader_store ("devmajor", st, NULL);
670 else
671 MAJOR_TO_CHARS (st->devmajor, header->header.devmajor);
672
673 if (st->devminor > MAXOCTAL7 && archive_format == POSIX_FORMAT)
674 xheader_store ("devminor", st, NULL);
675 else
676 MAJOR_TO_CHARS (st->devminor, header->header.devminor);
677 }
678 else
679 {
680 MAJOR_TO_CHARS (0, header->header.devmajor);
681 MINOR_TO_CHARS (0, header->header.devminor);
682 }
683
684 if (archive_format == POSIX_FORMAT)
685 {
686 xheader_store ("atime", st, NULL);
687 xheader_store ("ctime", st, NULL);
688 }
689 else if (incremental_option)
690 if (archive_format == OLDGNU_FORMAT)
691 {
692 TIME_TO_CHARS (st->stat.st_atime, header->oldgnu_header.atime);
693 TIME_TO_CHARS (st->stat.st_ctime, header->oldgnu_header.ctime);
694 }
695
696 header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
697
698 switch (archive_format)
699 {
700 case V7_FORMAT:
701 break;
702
703 case OLDGNU_FORMAT:
704 case GNU_FORMAT: /*FIXME?*/
705 /* Overwrite header->header.magic and header.version in one blow. */
706 strcpy (header->header.magic, OLDGNU_MAGIC);
707 break;
708
709 case POSIX_FORMAT:
710 case USTAR_FORMAT:
711 strncpy (header->header.magic, TMAGIC, TMAGLEN);
712 strncpy (header->header.version, TVERSION, TVERSLEN);
713 break;
714
715 default:
716 abort ();
717 }
718
719 if (archive_format == V7_FORMAT || numeric_owner_option)
720 {
721 /* header->header.[ug]name are left as the empty string. */
722 }
723 else
724 {
725 uid_to_uname (st->stat.st_uid, &st->uname);
726 gid_to_gname (st->stat.st_gid, &st->gname);
727
728 if (archive_format == POSIX_FORMAT
729 && strlen (st->uname) > UNAME_FIELD_SIZE)
730 xheader_store ("uname", st, NULL);
731 else
732 UNAME_TO_CHARS (st->uname, header->header.uname);
733
734 if (archive_format == POSIX_FORMAT
735 && strlen (st->gname) > GNAME_FIELD_SIZE)
736 xheader_store ("gname", st, NULL);
737 else
738 GNAME_TO_CHARS (st->gname, header->header.gname);
739 }
740
741 return header;
742 }
743
744 /* Finish off a filled-in header block and write it out. We also
745 print the file name and/or full info if verbose is on. If BLOCK_ORDINAL
746 is not negative, is the block ordinal of the first record for this
747 file, which may be a preceding long name or long link record. */
748 void
749 finish_header (struct tar_stat_info *st,
750 union block *header, off_t block_ordinal)
751 {
752 size_t i;
753 int sum;
754 char *p;
755
756 /* Note: It is important to do this before the call to write_extended(),
757 so that the actual ustar header is printed */
758 if (verbose_option
759 && header->header.typeflag != GNUTYPE_LONGLINK
760 && header->header.typeflag != GNUTYPE_LONGNAME
761 && header->header.typeflag != XHDTYPE
762 && header->header.typeflag != XGLTYPE)
763 {
764 /* These globals are parameters to print_header, sigh. */
765
766 current_header = header;
767 current_format = archive_format;
768 print_header (st, block_ordinal);
769 }
770
771 header = write_extended (st, header, XHDTYPE);
772
773 memcpy (header->header.chksum, CHKBLANKS, sizeof header->header.chksum);
774
775 sum = 0;
776 p = header->buffer;
777 for (i = sizeof *header; i-- != 0; )
778 /* We can't use unsigned char here because of old compilers, e.g. V7. */
779 sum += 0xFF & *p++;
780
781 /* Fill in the checksum field. It's formatted differently from the
782 other fields: it has [6] digits, a null, then a space -- rather than
783 digits, then a null. We use to_chars.
784 The final space is already there, from
785 checksumming, and to_chars doesn't modify it.
786
787 This is a fast way to do:
788
789 sprintf(header->header.chksum, "%6o", sum); */
790
791 uintmax_to_chars ((uintmax_t) sum, header->header.chksum, 7);
792
793 set_next_block_after (header);
794 }
795 \f
796
797 void
798 pad_archive (off_t size_left)
799 {
800 union block *blk;
801 while (size_left > 0)
802 {
803 save_sizeleft = size_left;
804 blk = find_next_block ();
805 memset (blk->buffer, 0, BLOCKSIZE);
806 set_next_block_after (blk);
807 size_left -= BLOCKSIZE;
808 }
809 }
810
811 static enum dump_status
812 dump_regular_file (int fd, struct tar_stat_info *stat)
813 {
814 off_t size_left = stat->stat.st_size;
815 off_t block_ordinal;
816 union block *blk;
817
818 block_ordinal = current_block_ordinal ();
819 blk = start_header (stat);
820 if (!blk)
821 return dump_status_fail;
822
823 /* Mark contiguous files, if we support them. */
824 if (archive_format != V7_FORMAT && S_ISCTG (stat->stat.st_mode))
825 blk->header.typeflag = CONTTYPE;
826
827 finish_header (stat, blk, block_ordinal);
828
829 while (size_left > 0)
830 {
831 size_t bufsize, count;
832
833 if (multi_volume_option)
834 {
835 assign_string (&save_name, stat->file_name);
836 save_sizeleft = size_left;
837 save_totsize = stat->stat.st_size;
838 }
839 blk = find_next_block ();
840
841 bufsize = available_space_after (blk);
842
843 if (size_left < bufsize)
844 {
845 /* Last read -- zero out area beyond. */
846 bufsize = size_left;
847 count = bufsize % BLOCKSIZE;
848 if (count)
849 memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
850 }
851
852 count = (fd < 0) ? bufsize : safe_read (fd, blk->buffer, bufsize);
853 if (count < 0)
854 {
855 read_diag_details (stat->orig_file_name,
856 stat->stat.st_size - size_left, bufsize);
857 pad_archive (size_left);
858 return dump_status_short;
859 }
860 size_left -= count;
861
862 set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
863
864 if (count != bufsize)
865 {
866 char buf[UINTMAX_STRSIZE_BOUND];
867 memset (blk->buffer + count, 0, bufsize - count);
868 WARN ((0, 0,
869 ngettext ("%s: File shrank by %s byte; padding with zeros",
870 "%s: File shrank by %s bytes; padding with zeros",
871 size_left),
872 quotearg_colon (stat->orig_file_name),
873 STRINGIFY_BIGINT (size_left, buf)));
874 if (! ignore_failed_read_option)
875 exit_status = TAREXIT_FAILURE;
876 pad_archive (size_left);
877 return dump_status_short;
878 }
879 }
880 return dump_status_ok;
881 }
882
883 void
884 dump_regular_finish (int fd, struct tar_stat_info *st, time_t original_ctime)
885 {
886 if (fd >= 0)
887 {
888 struct stat final_stat;
889 if (fstat (fd, &final_stat) != 0)
890 {
891 stat_diag (st->orig_file_name);
892 }
893 else if (final_stat.st_ctime != original_ctime)
894 {
895 WARN ((0, 0, _("%s: file changed as we read it"),
896 quotearg_colon (st->orig_file_name)));
897 }
898 if (close (fd) != 0)
899 {
900 close_diag (st->orig_file_name);
901 }
902 }
903 if (remove_files_option)
904 {
905 if (unlink (st->orig_file_name) == -1)
906 unlink_error (st->orig_file_name);
907 }
908 }
909
910 void
911 dump_dir0 (char *directory,
912 struct tar_stat_info *stat, int top_level, dev_t parent_device)
913 {
914 dev_t our_device = stat->stat.st_dev;
915
916 if (!is_avoided_name (stat->orig_file_name))
917 {
918 union block *blk = NULL;
919 off_t block_ordinal = current_block_ordinal ();
920 stat->stat.st_size = 0; /* force 0 size on dir */
921
922 blk = start_header (stat);
923 if (!blk)
924 return;
925
926 if (incremental_option)
927 blk->header.typeflag = GNUTYPE_DUMPDIR;
928 else /* if (standard_option) */
929 blk->header.typeflag = DIRTYPE;
930
931 /* If we're gnudumping, we aren't done yet so don't close it. */
932
933 if (!incremental_option)
934 finish_header (stat, blk, block_ordinal);
935 else if (gnu_list_name->dir_contents)
936 {
937 off_t size_left;
938 off_t totsize;
939 size_t bufsize;
940 ssize_t count;
941 const char *buffer, *p_buffer;
942 off_t block_ordinal = current_block_ordinal ();
943
944 buffer = gnu_list_name->dir_contents; /* FOO */
945 totsize = 0;
946 if (buffer)
947 for (p_buffer = buffer; *p_buffer; )
948 {
949 size_t size = strlen (p_buffer) + 1;
950 totsize += size;
951 p_buffer += size;
952 }
953 totsize++;
954 OFF_TO_CHARS (totsize, blk->header.size);
955 finish_header (stat, blk, block_ordinal);
956 p_buffer = buffer;
957 size_left = totsize;
958 while (size_left > 0)
959 {
960 if (multi_volume_option)
961 {
962 assign_string (&save_name, stat->orig_file_name);
963 save_sizeleft = size_left;
964 save_totsize = totsize;
965 }
966 blk = find_next_block ();
967 bufsize = available_space_after (blk);
968 if (size_left < bufsize)
969 {
970 bufsize = size_left;
971 count = bufsize % BLOCKSIZE;
972 if (count)
973 memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
974 }
975 memcpy (blk->buffer, p_buffer, bufsize);
976 size_left -= bufsize;
977 p_buffer += bufsize;
978 set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
979 }
980 if (multi_volume_option)
981 assign_string (&save_name, 0);
982 return;
983 }
984 }
985
986 if (!recursion_option)
987 return;
988
989 if (one_file_system_option
990 && !top_level
991 && parent_device != stat->stat.st_dev)
992 {
993 if (verbose_option)
994 WARN ((0, 0,
995 _("%s: file is on a different filesystem; not dumped"),
996 quotearg_colon (stat->orig_file_name)));
997 return;
998 }
999
1000 {
1001 char const *entry;
1002 size_t entry_len;
1003 char *name_buf = strdup (stat->orig_file_name);
1004 size_t name_size = strlen (name_buf);
1005 size_t name_len = name_size;
1006
1007 /* Now output all the files in the directory. */
1008 /* FIXME: Should speed this up by cd-ing into the dir. */
1009
1010 for (entry = directory; (entry_len = strlen (entry)) != 0;
1011 entry += entry_len + 1)
1012 {
1013 if (name_size < name_len + entry_len)
1014 {
1015 name_size = name_len + entry_len;
1016 name_buf = xrealloc (name_buf, name_size + 1);
1017 }
1018 strcpy (name_buf + name_len, entry);
1019 if (!excluded_name (name_buf))
1020 dump_file (name_buf, 0, our_device);
1021 }
1022
1023 free (name_buf);
1024 }
1025 }
1026
1027 /* Ensure exactly one trailing slash. */
1028 static void
1029 ensure_slash (char **pstr)
1030 {
1031 size_t len = strlen (*pstr);
1032 while (len >= 1 && ISSLASH ((*pstr)[len - 1]))
1033 len--;
1034 if (!ISSLASH ((*pstr)[len]))
1035 *pstr = xrealloc (*pstr, len + 2);
1036 (*pstr)[len++] = '/';
1037 (*pstr)[len] = '\0';
1038 }
1039
1040 bool
1041 dump_dir (struct tar_stat_info *stat, int top_level, dev_t parent_device)
1042 {
1043 char *directory;
1044
1045 directory = savedir (stat->orig_file_name);
1046 if (!directory)
1047 {
1048 savedir_diag (stat->orig_file_name);
1049 return false;
1050 }
1051
1052 ensure_slash (&stat->orig_file_name);
1053 ensure_slash (&stat->file_name);
1054
1055 dump_dir0 (directory, stat, top_level, parent_device);
1056
1057 free (directory);
1058 return true;
1059 }
1060
1061 \f
1062 /* Main functions of this module. */
1063
1064 void
1065 create_archive (void)
1066 {
1067 char *p;
1068
1069 open_archive (ACCESS_WRITE);
1070
1071 if (incremental_option)
1072 {
1073 size_t buffer_size = 1000;
1074 char *buffer = xmalloc (buffer_size);
1075 const char *q;
1076
1077 collect_and_sort_names ();
1078
1079 while ((p = name_from_list ()) != NULL)
1080 if (!excluded_name (p))
1081 dump_file (p, -1, (dev_t) 0);
1082
1083 blank_name_list ();
1084 while ((p = name_from_list ()) != NULL)
1085 if (!excluded_name (p))
1086 {
1087 size_t plen = strlen (p);
1088 if (buffer_size <= plen)
1089 {
1090 while ((buffer_size *= 2) <= plen)
1091 continue;
1092 buffer = xrealloc (buffer, buffer_size);
1093 }
1094 memcpy (buffer, p, plen);
1095 if (! ISSLASH (buffer[plen - 1]))
1096 buffer[plen++] = '/';
1097 q = gnu_list_name->dir_contents;
1098 if (q)
1099 while (*q)
1100 {
1101 size_t qlen = strlen (q);
1102 if (*q == 'Y')
1103 {
1104 if (buffer_size < plen + qlen)
1105 {
1106 while ((buffer_size *=2 ) < plen + qlen)
1107 continue;
1108 buffer = xrealloc (buffer, buffer_size);
1109 }
1110 strcpy (buffer + plen, q + 1);
1111 dump_file (buffer, -1, (dev_t) 0);
1112 }
1113 q += qlen + 1;
1114 }
1115 }
1116 free (buffer);
1117 }
1118 else
1119 {
1120 while ((p = name_next (1)) != NULL)
1121 if (!excluded_name (p))
1122 dump_file (p, 1, (dev_t) 0);
1123 }
1124
1125 write_eot ();
1126 close_archive ();
1127
1128 if (listed_incremental_option)
1129 write_directory_file ();
1130 }
1131
1132
1133 /* Calculate the hash of a link. */
1134 static unsigned
1135 hash_link (void const *entry, unsigned n_buckets)
1136 {
1137 struct link const *link = entry;
1138 return (uintmax_t) (link->dev ^ link->ino) % n_buckets;
1139 }
1140
1141 /* Compare two links for equality. */
1142 static bool
1143 compare_links (void const *entry1, void const *entry2)
1144 {
1145 struct link const *link1 = entry1;
1146 struct link const *link2 = entry2;
1147 return ((link1->dev ^ link2->dev) | (link1->ino ^ link2->ino)) == 0;
1148 }
1149
1150 static void
1151 unknown_file_error (char *p)
1152 {
1153 WARN ((0, 0, _("%s: Unknown file type; file ignored"),
1154 quotearg_colon (p)));
1155 if (!ignore_failed_read_option)
1156 exit_status = TAREXIT_FAILURE;
1157 }
1158
1159 \f
1160 /* Handling of hard links */
1161
1162 /* Table of all non-directories that we've written so far. Any time
1163 we see another, we check the table and avoid dumping the data
1164 again if we've done it once already. */
1165 static Hash_table *link_table;
1166
1167 /* Try to dump stat as a hard link to another file in the archive. If
1168 succeeded returns true */
1169 static bool
1170 dump_hard_link (struct tar_stat_info *stat)
1171 {
1172 if (link_table && stat->stat.st_nlink > 1)
1173 {
1174 struct link lp;
1175 struct link *dup;
1176 off_t block_ordinal;
1177 union block *blk;
1178
1179 lp.ino = stat->stat.st_ino;
1180 lp.dev = stat->stat.st_dev;
1181
1182 if ((dup = hash_lookup (link_table, &lp)))
1183 {
1184 /* We found a link. */
1185 char const *link_name = safer_name_suffix (dup->name, true);
1186
1187 dup->nlink--;
1188
1189 block_ordinal = current_block_ordinal ();
1190 assign_string (&stat->link_name, link_name);
1191 if (NAME_FIELD_SIZE < strlen (link_name))
1192 write_long_link (stat);
1193
1194 stat->stat.st_size = 0;
1195 blk = start_header (stat);
1196 if (!blk)
1197 return true;
1198 tar_copy_str (blk->header.linkname, link_name, NAME_FIELD_SIZE);
1199
1200 blk->header.typeflag = LNKTYPE;
1201 finish_header (stat, blk, block_ordinal);
1202
1203 if (remove_files_option && unlink (stat->orig_file_name) != 0)
1204 unlink_error (stat->orig_file_name);
1205
1206 return true;
1207 }
1208 }
1209 return false;
1210 }
1211
1212 static void
1213 file_count_links (struct tar_stat_info *stat)
1214 {
1215 if (stat->stat.st_nlink > 1)
1216 {
1217 struct link *dup;
1218 struct link *lp = xmalloc (offsetof (struct link, name)
1219 + strlen (stat->orig_file_name) + 1);
1220 lp->ino = stat->stat.st_ino;
1221 lp->dev = stat->stat.st_dev;
1222 lp->nlink = stat->stat.st_nlink;
1223 strcpy (lp->name, stat->orig_file_name);
1224
1225 if (! ((link_table
1226 || (link_table = hash_initialize (0, 0, hash_link,
1227 compare_links, 0)))
1228 && (dup = hash_insert (link_table, lp))))
1229 xalloc_die ();
1230
1231 if (dup != lp)
1232 abort ();
1233 lp->nlink--;
1234 }
1235 }
1236
1237 /* For each dumped file, check if all its links were dumped. Emit
1238 warnings if it is not so. */
1239 void
1240 check_links ()
1241 {
1242 struct link *lp;
1243
1244 if (!link_table)
1245 return;
1246
1247 for (lp = hash_get_first (link_table); lp;
1248 lp = hash_get_next (link_table, lp))
1249 {
1250 if (lp->nlink)
1251 {
1252 WARN ((0, 0, _("Missing links to '%s'.\n"), lp->name));
1253 }
1254 }
1255 }
1256
1257
1258 /* Dump a single file, recursing on directories. P is the file name
1259 to dump. TOP_LEVEL tells whether this is a top-level call; zero
1260 means no, positive means yes, and negative means the top level
1261 of an incremental dump. PARENT_DEVICE is the device of P's
1262 parent directory; it is examined only if TOP_LEVEL is zero. */
1263
1264 /* FIXME: One should make sure that for *every* path leading to setting
1265 exit_status to failure, a clear diagnostic has been issued. */
1266
1267 void
1268 dump_file0 (struct tar_stat_info *stat, char *p,
1269 int top_level, dev_t parent_device)
1270 {
1271 union block *header;
1272 char type;
1273 time_t original_ctime;
1274 struct utimbuf restore_times;
1275 off_t block_ordinal = -1;
1276
1277 if (interactive_option && !confirm ("add", p))
1278 return;
1279
1280 assign_string (&stat->orig_file_name, p);
1281 assign_string (&stat->file_name, safer_name_suffix (p, false));
1282
1283 if (deref_stat (dereference_option, p, &stat->stat) != 0)
1284 {
1285 stat_diag (p);
1286 return;
1287 }
1288 stat->archive_file_size = stat->stat.st_size;
1289 sys_stat_nanoseconds(stat);
1290 original_ctime = stat->stat.st_ctime;
1291 restore_times.actime = stat->stat.st_atime;
1292 restore_times.modtime = stat->stat.st_mtime;
1293
1294 #ifdef S_ISHIDDEN
1295 if (S_ISHIDDEN (stat->stat.st_mode))
1296 {
1297 char *new = (char *) alloca (strlen (p) + 2);
1298 if (new)
1299 {
1300 strcpy (new, p);
1301 strcat (new, "@");
1302 p = new;
1303 }
1304 }
1305 #endif
1306
1307 /* See if we want only new files, and check if this one is too old to
1308 put in the archive. */
1309
1310 if ((0 < top_level || !incremental_option)
1311 && !S_ISDIR (stat->stat.st_mode)
1312 && stat->stat.st_mtime < newer_mtime_option
1313 && (!after_date_option || stat->stat.st_ctime < newer_ctime_option))
1314 {
1315 if (0 < top_level)
1316 WARN ((0, 0, _("%s: file is unchanged; not dumped"),
1317 quotearg_colon (p)));
1318 /* FIXME: recheck this return. */
1319 return;
1320 }
1321
1322 /* See if we are trying to dump the archive. */
1323 if (sys_file_is_archive (stat))
1324 {
1325 WARN ((0, 0, _("%s: file is the archive; not dumped"),
1326 quotearg_colon (p)));
1327 return;
1328 }
1329
1330 if (S_ISDIR (stat->stat.st_mode))
1331 {
1332 dump_dir (stat, top_level, parent_device);
1333 if (atime_preserve_option)
1334 utime (p, &restore_times);
1335 return;
1336 }
1337 else if (is_avoided_name (p))
1338 return;
1339 else
1340 {
1341 /* Check for multiple links. */
1342 if (dump_hard_link (stat))
1343 return;
1344
1345 /* This is not a link to a previously dumped file, so dump it. */
1346
1347 if (S_ISREG (stat->stat.st_mode)
1348 || S_ISCTG (stat->stat.st_mode))
1349 {
1350 int fd;
1351 enum dump_status status;
1352
1353 if (file_dumpable_p (stat))
1354 {
1355 fd = open (stat->orig_file_name,
1356 O_RDONLY | O_BINARY);
1357 if (fd < 0)
1358 {
1359 if (!top_level && errno == ENOENT)
1360 WARN ((0, 0, _("%s: File removed before we read it"),
1361 quotearg_colon (stat->orig_file_name)));
1362 else
1363 open_diag (stat->orig_file_name);
1364 return;
1365 }
1366 }
1367 else
1368 fd = -1;
1369
1370 if (sparse_option && sparse_file_p (stat))
1371 {
1372 status = sparse_dump_file (fd, stat);
1373 if (status == dump_status_not_implemented)
1374 status = dump_regular_file (fd, stat);
1375 }
1376 else
1377 status = dump_regular_file (fd, stat);
1378
1379 switch (status)
1380 {
1381 case dump_status_ok:
1382 if (multi_volume_option)
1383 assign_string (&save_name, 0);
1384 dump_regular_finish (fd, stat, original_ctime);
1385 break;
1386
1387 case dump_status_short:
1388 if (multi_volume_option)
1389 assign_string (&save_name, 0);
1390 close (fd);
1391 break;
1392
1393 case dump_status_fail:
1394 close (fd);
1395 return;
1396
1397 case dump_status_not_implemented:
1398 abort ();
1399 }
1400
1401 if (atime_preserve_option)
1402 utime (stat->orig_file_name, &restore_times);
1403 file_count_links (stat);
1404 return;
1405 }
1406 #ifdef HAVE_READLINK
1407 else if (S_ISLNK (stat->stat.st_mode))
1408 {
1409 char *buffer;
1410 int size;
1411 size_t linklen = stat->stat.st_size;
1412 if (linklen != stat->stat.st_size || linklen + 1 == 0)
1413 xalloc_die ();
1414 buffer = (char *) alloca (linklen + 1);
1415 size = readlink (p, buffer, linklen + 1);
1416 if (size < 0)
1417 {
1418 readlink_diag (p);
1419 return;
1420 }
1421 buffer[size] = '\0';
1422 assign_string (&stat->link_name, buffer);
1423 if (size > NAME_FIELD_SIZE)
1424 write_long_link (stat);
1425
1426 block_ordinal = current_block_ordinal ();
1427 stat->stat.st_size = 0; /* force 0 size on symlink */
1428 header = start_header (stat);
1429 if (!header)
1430 return;
1431 tar_copy_str (header->header.linkname, buffer, NAME_FIELD_SIZE);
1432 header->header.typeflag = SYMTYPE;
1433 finish_header (stat, header, block_ordinal);
1434 /* nothing more to do to it */
1435
1436 if (remove_files_option)
1437 {
1438 if (unlink (p) == -1)
1439 unlink_error (p);
1440 }
1441 file_count_links (stat);
1442 return;
1443 }
1444 #endif
1445 else if (S_ISCHR (stat->stat.st_mode))
1446 type = CHRTYPE;
1447 else if (S_ISBLK (stat->stat.st_mode))
1448 type = BLKTYPE;
1449 else if (S_ISFIFO (stat->stat.st_mode))
1450 type = FIFOTYPE;
1451 else if (S_ISSOCK (stat->stat.st_mode))
1452 {
1453 WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
1454 return;
1455 }
1456 else if (S_ISDOOR (stat->stat.st_mode))
1457 {
1458 WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p)));
1459 return;
1460 }
1461 else
1462 {
1463 unknown_file_error (p);
1464 return;
1465 }
1466 }
1467
1468 if (archive_format == V7_FORMAT)
1469 {
1470 unknown_file_error (p);
1471 return;
1472 }
1473
1474 block_ordinal = current_block_ordinal ();
1475 stat->stat.st_size = 0; /* force 0 size */
1476 header = start_header (stat);
1477 if (!header)
1478 return;
1479 header->header.typeflag = type;
1480
1481 if (type != FIFOTYPE)
1482 {
1483 MAJOR_TO_CHARS (major (stat->stat.st_rdev),
1484 header->header.devmajor);
1485 MINOR_TO_CHARS (minor (stat->stat.st_rdev),
1486 header->header.devminor);
1487 }
1488
1489 finish_header (stat, header, block_ordinal);
1490 if (remove_files_option)
1491 {
1492 if (unlink (p) == -1)
1493 unlink_error (p);
1494 }
1495 }
1496
1497 void
1498 dump_file (char *p, int top_level, dev_t parent_device)
1499 {
1500 struct tar_stat_info stat;
1501 tar_stat_init (&stat);
1502 dump_file0 (&stat, p, top_level, parent_device);
1503 tar_stat_destroy (&stat);
1504 }
This page took 0.091498 seconds and 3 git commands to generate.