diff --git a/ChangeLog b/ChangeLog index bd3d4def..14e01a7a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,105 @@ +2005-06-21 Paul Eggert + + Further improvements inspired by Jim Meyering's fixes. + + * NEWS: Better support for full-resolution time stamps. + The -v option now prints time stamps only to 1-minute resolution. + * gnulib.modules: Add utimens. + * lib/.cvsignore: Add imaxtostr.c, inttostr.c, inttostr.h, + offtostr.c, umaxtostr.c, utimens.c, utimens.h. Remove paxconvert.c. + * lib/Makefile.tmpl (libtar_a_SOURCES): Remove paxconvert.c. + * lib/paxconvert.c: Remove; superseded by umaxtostr.c. + * po/POTFILES.in: Remove lib/paxconvert.c. Add lib/xalloc-die.c, + lib/obstack.c. + * src/buffer.c (set_start_time, compute_duration, start_time): + Use gettime rather than rolling our own code. + * src/common.h (OLDGNU_NAME_FIELD_SIZE, MAXOCTAL11, MAXOCTAL7): Remove. + (newer_ctime_option): Remove. + (timespec_lt): New function. + (OLDER_STAT_TIME): Use it. + (string_to_chars): First arg is char const *, not char *. + (tartime): Time arg is now struct timespec. New bool arg. + All callers changed. + (code_ns_fraction): New decl. + (sys_stat_nanoseconds): Remove decl. + (get_stat_atime, get_stat_ctime, get_stat_mtime): New functions. + (set_stat_atime, set_stat_ctime, set_stat_mtime): New functions. + * src/compare.c: Include utimens.h rather than rolling our own. + (diff_dir, diff_file, diff_link, diff_symlink, diff_special): + Prototype. + (diff_dumpdir, diff_multivol): Prototype. + (diff_file): Support higher-resolution time stamps. + * src/create.c: Include utimens.h rather than rolling our own. + (MAX_OCTAL_VAL): New macro. + (tar_copy_str, string_to_chars): Don't bother to zero-fill; + the destination is already zeroed. + (string_to_chars): First arg is char const *. + (start_private_header): Use MINOR_TO_CHARS, not MAJOR_TO_CHARS, + for minor device number. + (write_header_name, dump_hard_link, dump_file0): + Simplify test for old GNU format. + (start_header): Put in placeholders for uid, etc., even when + using extended headers, for benefit of older "tar" implementations. + Don't assume uintmax_t is wider than 32 bits. + Output extended header for mtime if needed. + (dump_regular_finish, dump_file0): + Support extended time stamp resolution. + * src/extract.c: Include utimens.h rather than rolling our own. + (check_time): Support extended time stamp resolution. + * src/list.c: Include . + (tartime): Use umaxtostr rather than stringify_uintmax_t_backwards. + * src/xheader.c: Include . + Do not include . + (strtoimax) [!HAVE_DECL_STRTOIMAX && !defined strtoimax]: New decl. + (strtoumax) [!HAVE_DECL_STRTOUMAX && !defined strtoumax]: New decl. + (BILLION, LOG10_BILLION): New constants. + (to_decimal): Remove; superseded by inttostr. All callers changed + to use umaxtostr. + (xheader_format_name): Don't assume pids and uintmax_t values + fit in 63 bytes (!) when printed. + (decode_record): Don't bother to check for ERANGE; an out of range + value must be treater than len_max anyway. + If the length is out of range, output it in the diagnostic. + (format_uintmax): Remove; all callers changed to use umaxtostr. + (xheader_print): Don't assume sizes can be printed in 99 bytes (!). + (out_of_range_header): New function. + (decode_time): Use it. + (code_time): Accept struct timespec, not time_t and unsigned long. + All callers changed. Size sbuf properly, and remove unnecessary check. + Don't assume time stamps can fit in 199 bytes. + Handle negative time stamps. Handle fractional time stamps + more consistently. Don't output unnecessary trailing zeros. + (decode_time): Yield struct timespec, not time_t and unsigned long. + All callers changed. + Handle negative time stamps. Truncate towards minus infinity + consistently. Improve overflow checks, and output a better + diagnostic on overflow. + (code_num): Don't assume uintmax_t can be printed in 99 bytes (!). + (decode_num): New function, for better diagnostics. + (atime_coder, atime_decoder, gid_decoder, ctime_coder): + (ctime_decoder, mtime_coder, mtime_decoder, size_decoder): + (uid_decoder, sparse_size_decoder, sparse_numblocks_decoder): + (sparse_offset_decoder, sparse_numbytes_decoder): + Use decode_num, etc., instead of xstrtoumax, etc. + +2005-06-21 Jim Meyering + + Carefully crafted invalid headers can cause buffer overrun. + Invalid header fields go undiagnosed. + Some valid time strings are ignored. + + * src/xheader.c (sparse_numblocks_decoder): Remove unchecked use + of `calloc'. Use xcalloc instead. + (decode_time, gid_decoder, size_decoder, uid_decoder): + (sparse_size_decoder, sparse_offset_decoder, sparse_numblocks_decoder): + Ensure that the result of calling xstrtoumax is no larger than + the maximum value for the target type. Upon any failure, exit with + a diagnostic. + (sparse_numblocks_decoder): Avoid buffer overrun/heap corruption: + use x2nrealloc, rather than `n *= 2' and xrealloc(p, n,.... + (decode_time): Rewrite to accept time strings like + 1119018481.000000000. Before, such strings were always ignored. + 2005-06-13 Sergey Poznyakoff * src/create.c (dump_file0): Check for is_avoided_name() @@ -6,7 +108,7 @@ * tests/update.at: New file * tests/Makefile.am (TESTSUITE_AT): Add update.at * tests/testsuite.at: Likewise - + 2005-06-13 Sergey Poznyakoff * configure.ac (AC_STRUCT_ST_BLKSIZE) diff --git a/NEWS b/NEWS index e13e44ae..ab057ecc 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,15 @@ automatically. It is not necessary to give --null option. is useful e.g. for processing output from `find dir -print0'. An orthogonal option --unquote is provided as well. +* Better support for full-resolution time stamps. Tar cannot restore +time stamps to full nanosecond resolution, though, until the kernel +guys get their act together and give us a system call to set file time +stamps to nanosecond resolution. + +* The -v option now prints time stamps only to 1-minute resolution, +not full resolution, to avoid using up too many output columns. +Nanosecond resolution is now supported, but that would be too much. + * Bugfixes ** Allow non-option arguments to be interspersed with options. diff --git a/gnulib.modules b/gnulib.modules index 27db4dbc..c1caace1 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -40,6 +40,7 @@ timespec unlinkdir unlocked-io utime +utimens xalloc xalloc-die xgetcwd diff --git a/lib/.cvsignore b/lib/.cvsignore index 7cc00222..529dd0bd 100644 --- a/lib/.cvsignore +++ b/lib/.cvsignore @@ -73,7 +73,10 @@ hash.c hash.h human.c human.h +imaxtostr.c intprops.h +inttostr.c +inttostr.h lchown.c lchown.h localcharset.c @@ -90,10 +93,10 @@ modechange.c modechange.h obstack.c obstack.h +offtostr.c openat.c openat.h pathmax.h -paxconvert.c paxerror.c paxexit.c paxlib.h @@ -149,12 +152,15 @@ system.h time_r.c time_r.h timespec.h +umaxtostr.c unistd-safer.h unlinkdir.c unlinkdir.h unlocked-io.h unsetenv.c utime.c +utimens.c +utimens.h vasnprintf.c vasnprintf.h vsnprintf.c diff --git a/lib/Makefile.tmpl b/lib/Makefile.tmpl index 52394495..c20e3c4e 100644 --- a/lib/Makefile.tmpl +++ b/lib/Makefile.tmpl @@ -20,7 +20,7 @@ noinst_LIBRARIES = libtar.a noinst_HEADERS = system.h localedir.h rmt.h paxlib.h -libtar_a_SOURCES = prepargs.c prepargs.h rtapelib.c paxerror.c paxexit.c paxconvert.c paxnames.c +libtar_a_SOURCES = prepargs.c prepargs.h rtapelib.c paxerror.c paxexit.c paxnames.c localedir = $(datadir)/locale diff --git a/po/POTFILES.in b/po/POTFILES.in index bd2a577c..dc7320c0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,6 +1,7 @@ # List of files which contain translatable strings. -# Copyright (C) 1996, 1999, 2000, 2003 Free Software Foundation, Inc. +# Copyright (C) 1996, 1999, 2000, 2003, 2004, 2005 Free Software +# Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,17 +20,19 @@ # Library files lib/argmatch.c +lib/argp-help.c +lib/argp-parse.c lib/error.c lib/getopt.c lib/human.c -lib/quotearg.c -lib/xmalloc.c -lib/rtapelib.c -lib/argp-help.c -lib/paxconvert.c +lib/obstack.c lib/paxerror.c lib/paxexit.c lib/paxnames.c +lib/quotearg.c +lib/rtapelib.c +lib/xalloc-die.c +lib/xmalloc.c rmt/rmt.c @@ -51,4 +54,3 @@ src/xheader.c # Testsuite tests/genfile.c - diff --git a/src/buffer.c b/src/buffer.c index 6fc3f431..5977f5a5 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -125,23 +125,16 @@ double duration; void set_start_time () { -#if HAVE_CLOCK_GETTIME - if (clock_gettime (CLOCK_REALTIME, &start_timespec) != 0) -#endif - start_time = time (0); + gettime (&start_time); } void compute_duration () { -#if HAVE_CLOCK_GETTIME struct timespec now; - if (clock_gettime (CLOCK_REALTIME, &now) == 0) - duration += ((now.tv_sec - start_timespec.tv_sec) - + (now.tv_nsec - start_timespec.tv_nsec) / 1e9); - else -#endif - duration += time (NULL) - start_time; + gettime (&now); + duration += ((now.tv_sec - start_time.tv_sec) + + (now.tv_nsec - start_time.tv_nsec) / 1e9); set_start_time (); } @@ -542,7 +535,7 @@ open_archive (enum access_mode wanted_access) strip_trailing_slashes (current_stat_info.file_name); record_start->header.typeflag = GNUTYPE_VOLHDR; - TIME_TO_CHARS (start_time, record_start->header.mtime); + TIME_TO_CHARS (start_time.tv_sec, record_start->header.mtime); finish_header (¤t_stat_info, record_start, -1); } break; @@ -587,8 +580,8 @@ flush_write (void) { if (save_name) { - assign_string (&real_s_name, - safer_name_suffix (save_name, false, + assign_string (&real_s_name, + safer_name_suffix (save_name, false, absolute_names_option)); real_s_totsize = save_totsize; real_s_sizeleft = save_sizeleft; @@ -636,7 +629,7 @@ flush_write (void) memset (record_start, 0, BLOCKSIZE); sprintf (record_start->header.name, "%s Volume %d", volume_label_option, volno); - TIME_TO_CHARS (start_time, record_start->header.mtime); + TIME_TO_CHARS (start_time.tv_sec, record_start->header.mtime); record_start->header.typeflag = GNUTYPE_VOLHDR; finish_header (¤t_stat_info, record_start, -1); } @@ -695,7 +688,7 @@ flush_write (void) assign_string (&real_s_name, 0); else { - assign_string (&real_s_name, + assign_string (&real_s_name, safer_name_suffix (save_name, false, absolute_names_option)); real_s_sizeleft = save_sizeleft; @@ -825,7 +818,7 @@ flush_read (void) { if (save_name) { - assign_string (&real_s_name, + assign_string (&real_s_name, safer_name_suffix (save_name, false, absolute_names_option)); real_s_sizeleft = save_sizeleft; diff --git a/src/common.h b/src/common.h index e6860a10..70a925c9 100644 --- a/src/common.h +++ b/src/common.h @@ -23,19 +23,12 @@ /* The checksum field is filled with this while the checksum is computed. */ #define CHKBLANKS " " /* 8 blanks, no null */ -/* Old GNU stores zero-terminated file name */ -#define OLDGNU_NAME_FIELD_SIZE 99 - /* Some constants from POSIX are given names. */ #define NAME_FIELD_SIZE 100 #define PREFIX_FIELD_SIZE 155 #define UNAME_FIELD_SIZE 32 #define GNAME_FIELD_SIZE 32 -/* FIXME */ -#define MAXOCTAL11 017777777777L -#define MAXOCTAL7 07777777 - /* Some various global definitions. */ @@ -185,10 +178,6 @@ GLOBAL mode_t initial_umask; GLOBAL bool multi_volume_option; -/* The same variable holds the time, whether mtime or ctime. Just fake a - non-existing option, for making the code clearer, elsewhere. */ -#define newer_ctime_option newer_mtime_option - /* Specified threshold date and time. Files having an older time stamp do not get archived (also see after_date_option above). */ GLOBAL struct timespec newer_mtime_option; @@ -199,9 +188,14 @@ GLOBAL struct timespec newer_mtime_option; /* Return true if the struct stat ST's M time is less than newer_mtime_option. */ #define OLDER_STAT_TIME(st, m) \ - ((st).st_##m##time < newer_mtime_option.tv_sec \ - || ((st).st_##m##time == newer_mtime_option.tv_sec \ - && TIMESPEC_NS ((st).st_##m##tim) < newer_mtime_option.tv_nsec)) + timespec_lt (get_stat_##m##time (&st), newer_mtime_option) + +/* Return true if A < B. */ +static inline +timespec_lt (struct timespec a, struct timespec b) +{ + return a.tv_sec < b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_nsec < b.tv_nsec); +} /* Zero if there is no recursion, otherwise FNM_LEADING_DIR. */ GLOBAL int recursion_option; @@ -281,12 +275,7 @@ GLOBAL int archive; GLOBAL bool dev_null_output; /* Timestamp for when we started execution. */ -#if HAVE_CLOCK_GETTIME - GLOBAL struct timespec start_timespec; -# define start_time (start_timespec.tv_sec) -#else - GLOBAL time_t start_time; -#endif +GLOBAL struct timespec start_time; GLOBAL struct tar_stat_info current_stat_info; @@ -410,7 +399,7 @@ void size_to_chars (size_t, char *, size_t); void time_to_chars (time_t, char *, size_t); void uid_to_chars (uid_t, char *, size_t); void uintmax_to_chars (uintmax_t, char *, size_t); -void string_to_chars (char *, char *, size_t); +void string_to_chars (char const *, char *, size_t); /* Module diffarch.c. */ @@ -464,7 +453,7 @@ extern size_t recent_long_link_blocks; void decode_header (union block *, struct tar_stat_info *, enum archive_format *, int); -char const *tartime (time_t); +char const *tartime (struct timespec, bool); #define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where)) #define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where)) @@ -505,6 +494,8 @@ void assign_string (char **, const char *); char *quote_copy_string (const char *); int unquote_string (char *); +void code_ns_fraction (int, char *); + size_t dot_dot_prefix_len (char const *); enum remove_option @@ -621,7 +612,6 @@ void xheader_set_option (char *string); /* Module system.c */ -void sys_stat_nanoseconds (struct tar_stat_info *); void sys_detect_dev_null_output (void); void sys_save_archive_dev_ino (void); void sys_drain_input_pipe (void); @@ -654,3 +644,120 @@ bool sparse_diff_file (int, struct tar_stat_info *); /* Module utf8.c */ bool string_ascii_p (const char *str); bool utf8_convert (bool to_utf, char const *input, char **output); + + +/* FIXME: The following should get moved into gnulib. */ + +static inline struct timespec +get_stat_atime (struct stat const *st) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + return st->st_atim; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + return st->st_atimespec; +#else + struct timespec t; + t.tv_sec = st->st_atime; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + t.tv_nsec = st->stat.st_atimensec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + t.tv_nsec = st->stat.st_spare1 * 1000; +# else + t.tv_nsec = 0; +# endif + return t; +#endif +} + +static inline struct timespec +get_stat_ctime (struct stat const *st) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + return st->st_ctim; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + return st->st_ctimespec; +#else + struct timespec t; + t.tv_sec = st->st_ctime; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + t.tv_nsec = st->stat.st_ctimensec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + t.tv_nsec = st->stat.st_spare1 * 1000; +# else + t.tv_nsec = 0; +# endif + return t; +#endif +} + +static inline struct timespec +get_stat_mtime (struct stat const *st) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + return st->st_mtim; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + return st->st_mtimespec; +#else + struct timespec t; + t.tv_sec = st->st_mtime; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + t.tv_nsec = st->stat.st_mtimensec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + t.tv_nsec = st->stat.st_spare1 * 1000; +# else + t.tv_nsec = 0; +# endif + return t; +#endif +} + +static inline void +set_stat_atime (struct stat *st, struct timespec t) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + st->st_atim = t; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + st->st_atimespec = t; +#else + st->st_atime = t.tv_sec; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + st->stat.st_atimensec = t.tv_nsec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + st->stat.st_spare1 = t.tv_nsec / 1000; +# endif +#endif +} + +static inline void +set_stat_ctime (struct stat *st, struct timespec t) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + st->st_ctim = t; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + st->st_ctimespec = t; +#else + st->st_ctime = t.tv_sec; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + st->stat.st_ctimensec = t.tv_nsec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + st->stat.st_spare1 = t.tv_nsec / 1000; +# endif +#endif +} + +static inline void +set_stat_mtime (struct stat *st, struct timespec t) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + st->st_mtim = t; +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC + st->st_mtimespec = t; +#else + st->st_mtime = t.tv_sec; +# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC + st->stat.st_mtimensec = t.tv_nsec; +# elif defined HAVE_STRUCT_STAT_ST_SPARE1 + st->stat.st_spare1 = t.tv_nsec / 1000; +# endif +#endif +} diff --git a/src/compare.c b/src/compare.c index 0ad12233..98a953bf 100644 --- a/src/compare.c +++ b/src/compare.c @@ -1,7 +1,7 @@ /* Diff files from a tar archive. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, - 2003, 2004 Free Software Foundation, Inc. + 2003, 2004, 2005 Free Software Foundation, Inc. Written by John Gilmore, on 1987-04-30. @@ -21,21 +21,12 @@ #include -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif - #if HAVE_LINUX_FD_H # include #endif #include +#include #include "common.h" #include @@ -196,7 +187,7 @@ get_stat_data (char const *file_name, struct stat *stat_data) static void -diff_dir () +diff_dir (void) { struct stat stat_data; @@ -211,7 +202,7 @@ diff_dir () } static void -diff_file () +diff_file (void) { struct stat stat_data; @@ -254,10 +245,6 @@ diff_file () else { int status; - struct utimbuf restore_times; - - restore_times.actime = stat_data.st_atime; - restore_times.modtime = stat_data.st_mtime; if (current_stat_info.is_sparse) sparse_diff_file (diff_handle, ¤t_stat_info); @@ -265,7 +252,7 @@ diff_file () { if (multi_volume_option) { - assign_string (&save_name, + assign_string (&save_name, current_stat_info.orig_file_name); save_totsize = current_stat_info.stat.st_size; /* save_sizeleft is set in read_and_process. */ @@ -283,14 +270,20 @@ diff_file () close_error (current_stat_info.file_name); if (atime_preserve_option) - utime (current_stat_info.file_name, &restore_times); + { + struct timespec ts[2]; + ts[0] = get_stat_atime (&stat_data); + ts[1] = get_stat_mtime (&stat_data); + if (utimens (current_stat_info.file_name, ts) != 0) + utime_error (current_stat_info.file_name); + } } } } } static void -diff_link () +diff_link (void) { struct stat file_data; struct stat link_data; @@ -305,7 +298,7 @@ diff_link () #ifdef HAVE_READLINK static void -diff_symlink () +diff_symlink (void) { size_t len = strlen (current_stat_info.link_name); char *linkbuf = alloca (len + 1); @@ -327,7 +320,7 @@ diff_symlink () #endif static void -diff_special () +diff_special (void) { struct stat stat_data; @@ -361,7 +354,7 @@ diff_special () } static void -diff_dumpdir () +diff_dumpdir (void) { char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0); @@ -387,7 +380,7 @@ diff_dumpdir () } static void -diff_multivol () +diff_multivol (void) { struct stat stat_data; int fd, status; @@ -398,7 +391,7 @@ diff_multivol () diff_dir (); return; } - + if (!get_stat_data (current_stat_info.file_name, &stat_data)) return; @@ -418,7 +411,7 @@ diff_multivol () } fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY); - + if (fd < 0) { open_error (current_stat_info.file_name); @@ -445,7 +438,7 @@ diff_multivol () if (multi_volume_option) assign_string (&save_name, 0); - + status = close (fd); if (status != 0) close_error (current_stat_info.file_name); @@ -498,7 +491,7 @@ diff_archive (void) diff_symlink (); break; #endif - + case CHRTYPE: case BLKTYPE: case FIFOTYPE: @@ -508,7 +501,7 @@ diff_archive (void) case GNUTYPE_DUMPDIR: diff_dumpdir (); /* Fall through. */ - + case DIRTYPE: diff_dir (); break; diff --git a/src/create.c b/src/create.c index c9be71cf..6381f9fe 100644 --- a/src/create.c +++ b/src/create.c @@ -21,17 +21,8 @@ #include -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif - #include +#include #include "common.h" #include @@ -51,6 +42,10 @@ struct link ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \ : (uintmax_t) -1) +/* The maximum uintmax_t value that can be represented with octal + digits and a trailing NUL in BUFFER. */ +#define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, LG_8) + /* Convert VALUE to an octal representation suitable for tar headers. Output to buffer WHERE with size SIZE. The result is undefined if SIZE is 0 or if VALUE is too large to fit. */ @@ -69,6 +64,29 @@ to_octal (uintmax_t value, char *where, size_t size) while (i); } +/* Copy at most LEN bytes from the string SRC to DST. Terminate with + NUL unless SRC is LEN or more bytes long. */ + +static void +tar_copy_str (char *dst, const char *src, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) + if (! (dst[i] = src[i])) + break; +} + +/* Same as tar_copy_str, but always terminate with NUL if using + is OLDGNU format */ + +static void +tar_name_copy_str (char *dst, const char *src, size_t len) +{ + tar_copy_str (dst, src, len); + if (archive_format == OLDGNU_FORMAT) + dst[len-1] = 0; +} + /* Convert NEGATIVE VALUE to a base-256 representation suitable for tar headers. NEGATIVE is 1 if VALUE was negative before being cast to uintmax_t, 0 otherwise. Output to buffer WHERE with size SIZE. @@ -325,10 +343,10 @@ uintmax_to_chars (uintmax_t v, char *p, size_t s) } void -string_to_chars (char *str, char *p, size_t s) +string_to_chars (char const *str, char *p, size_t s) { - strncpy (p, str, s); - p[s-1] = 0; + tar_copy_str (p, str, s); + p[s - 1] = '\0'; } @@ -361,25 +379,6 @@ write_eot (void) set_next_block_after (pointer); } -/* Copy at most LEN bytes from SRC to DST. Terminate with NUL unless - SRC is LEN characters long */ -static void -tar_copy_str (char *dst, const char *src, size_t len) -{ - dst[len-1] = 0; - strncpy (dst, src, len); -} - -/* Same as tar_copy_str, but always terminate with NUL if using - is OLDGNU format */ -static void -tar_name_copy_str (char *dst, const char *src, size_t len) -{ - tar_copy_str (dst, src, len); - if (archive_format == OLDGNU_FORMAT) - dst[len-1] = 0; -} - /* Write a "private" header */ union block * start_private_header (const char *name, size_t size) @@ -398,7 +397,7 @@ start_private_header (const char *name, size_t size) UID_TO_CHARS (getuid (), header->header.uid); GID_TO_CHARS (getgid (), header->header.gid); MAJOR_TO_CHARS (0, header->header.devmajor); - MAJOR_TO_CHARS (0, header->header.devminor); + MINOR_TO_CHARS (0, header->header.devminor); strncpy (header->header.magic, TMAGIC, TMAGLEN); strncpy (header->header.version, TVERSION, TVERSLEN); return header; @@ -600,9 +599,8 @@ write_header_name (struct tar_stat_info *st) xheader_store ("path", st, NULL); return write_short_name (st); } - else if ((archive_format == OLDGNU_FORMAT - && OLDGNU_NAME_FIELD_SIZE < strlen (st->file_name)) - || NAME_FIELD_SIZE < strlen (st->file_name)) + else if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) + < strlen (st->file_name)) return write_long_name (st); else return write_short_name (st); @@ -662,39 +660,74 @@ start_header (struct tar_stat_info *st) else MODE_TO_CHARS (st->stat.st_mode, header->header.mode); - if (st->stat.st_uid > MAXOCTAL7 && archive_format == POSIX_FORMAT) - xheader_store ("uid", st, NULL); - else - UID_TO_CHARS (st->stat.st_uid, header->header.uid); + { + uid_t uid = st->stat.st_uid; + if (archive_format == POSIX_FORMAT + && MAX_OCTAL_VAL (header->header.uid) < uid) + { + xheader_store ("uid", st, NULL); + uid = 0; + } + UID_TO_CHARS (uid, header->header.uid); + } - if (st->stat.st_gid > MAXOCTAL7 && archive_format == POSIX_FORMAT) - xheader_store ("gid", st, NULL); - else - GID_TO_CHARS (st->stat.st_gid, header->header.gid); + { + gid_t gid = st->stat.st_gid; + if (archive_format == POSIX_FORMAT + && MAX_OCTAL_VAL (header->header.gid) < gid) + { + xheader_store ("gid", st, NULL); + gid = 0; + } + GID_TO_CHARS (gid, header->header.gid); + } - if (st->stat.st_size > MAXOCTAL11 && archive_format == POSIX_FORMAT) - xheader_store ("size", st, NULL); - else - OFF_TO_CHARS (st->stat.st_size, header->header.size); + { + off_t size = st->stat.st_size; + if (archive_format == POSIX_FORMAT + && MAX_OCTAL_VAL (header->header.size) < size) + { + xheader_store ("size", st, NULL); + size = 0; + } + OFF_TO_CHARS (size, header->header.size); + } - TIME_TO_CHARS (st->stat.st_mtime, header->header.mtime); + { + struct timespec mtime = get_stat_mtime (&st->stat); + if (archive_format == POSIX_FORMAT) + { + if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec + || mtime.tv_nsec != 0) + xheader_store ("mtime", st, NULL); + if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec) + mtime.tv_sec = 0; + } + TIME_TO_CHARS (mtime.tv_sec, header->header.mtime); + } /* FIXME */ if (S_ISCHR (st->stat.st_mode) || S_ISBLK (st->stat.st_mode)) { - st->devmajor = major (st->stat.st_rdev); - st->devminor = minor (st->stat.st_rdev); + major_t devmajor = major (st->stat.st_rdev); + minor_t devminor = minor (st->stat.st_rdev); - if (st->devmajor > MAXOCTAL7 && archive_format == POSIX_FORMAT) - xheader_store ("devmajor", st, NULL); - else - MAJOR_TO_CHARS (st->devmajor, header->header.devmajor); + if (archive_format == POSIX_FORMAT + && MAX_OCTAL_VAL (header->header.devmajor) < devmajor) + { + xheader_store ("devmajor", st, NULL); + devmajor = 0; + } + MAJOR_TO_CHARS (devmajor, header->header.devmajor); - if (st->devminor > MAXOCTAL7 && archive_format == POSIX_FORMAT) - xheader_store ("devminor", st, NULL); - else - MAJOR_TO_CHARS (st->devminor, header->header.devminor); + if (archive_format == POSIX_FORMAT + && MAX_OCTAL_VAL (header->header.devminor) < devminor) + { + xheader_store ("devminor", st, NULL); + devminor = 0; + } + MINOR_TO_CHARS (devminor, header->header.devminor); } else if (archive_format != GNU_FORMAT && archive_format != OLDGNU_FORMAT) { @@ -750,15 +783,13 @@ start_header (struct tar_stat_info *st) && (strlen (st->uname) > UNAME_FIELD_SIZE || !string_ascii_p (st->uname))) xheader_store ("uname", st, NULL); - else - UNAME_TO_CHARS (st->uname, header->header.uname); + UNAME_TO_CHARS (st->uname, header->header.uname); if (archive_format == POSIX_FORMAT && (strlen (st->gname) > GNAME_FIELD_SIZE || !string_ascii_p (st->gname))) xheader_store ("gname", st, NULL); - else - GNAME_TO_CHARS (st->gname, header->header.gname); + GNAME_TO_CHARS (st->gname, header->header.gname); } return header; @@ -888,7 +919,7 @@ dump_regular_file (int fd, struct tar_stat_info *st) size_left -= count; if (count) set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE); - + if (count != bufsize) { char buf[UINTMAX_STRSIZE_BOUND]; @@ -909,7 +940,8 @@ dump_regular_file (int fd, struct tar_stat_info *st) } static void -dump_regular_finish (int fd, struct tar_stat_info *st, time_t original_ctime) +dump_regular_finish (int fd, struct tar_stat_info *st, + struct timespec original_ctime) { if (fd >= 0) { @@ -918,7 +950,9 @@ dump_regular_finish (int fd, struct tar_stat_info *st, time_t original_ctime) { stat_diag (st->orig_file_name); } - else if (final_stat.st_ctime != original_ctime) + else if (final_stat.st_ctime != original_ctime.tv_sec + || (get_stat_ctime (&final_stat).tv_nsec + != original_ctime.tv_nsec)) { WARN ((0, 0, _("%s: file changed as we read it"), quotearg_colon (st->orig_file_name))); @@ -1270,9 +1304,8 @@ dump_hard_link (struct tar_stat_info *st) block_ordinal = current_block_ordinal (); assign_string (&st->link_name, link_name); - if ((archive_format == OLDGNU_FORMAT - && OLDGNU_NAME_FIELD_SIZE < strlen (link_name)) - || NAME_FIELD_SIZE < strlen (link_name)) + if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) + < strlen (link_name)) write_long_link (st); st->stat.st_size = 0; @@ -1354,15 +1387,15 @@ dump_file0 (struct tar_stat_info *st, char *p, { union block *header; char type; - time_t original_ctime; - struct utimbuf restore_times; + struct timespec original_ctime; + struct timespec restore_times[2]; off_t block_ordinal = -1; if (interactive_option && !confirm ("add", p)) return; assign_string (&st->orig_file_name, p); - assign_string (&st->file_name, + assign_string (&st->file_name, safer_name_suffix (p, false, absolute_names_option)); if (deref_stat (dereference_option, p, &st->stat) != 0) @@ -1371,10 +1404,9 @@ dump_file0 (struct tar_stat_info *st, char *p, return; } st->archive_file_size = st->stat.st_size; - sys_stat_nanoseconds (st); - original_ctime = st->stat.st_ctime; - restore_times.actime = st->stat.st_atime; - restore_times.modtime = st->stat.st_mtime; + original_ctime = get_stat_ctime (&st->stat); + restore_times[0] = get_stat_atime (&st->stat); + restore_times[1] = get_stat_mtime (&st->stat); #ifdef S_ISHIDDEN if (S_ISHIDDEN (st->stat.st_mode)) @@ -1420,7 +1452,7 @@ dump_file0 (struct tar_stat_info *st, char *p, { dump_dir (st, top_level, parent_device); if (atime_preserve_option) - utime (p, &restore_times); + utimens (p, restore_times); return; } else @@ -1486,7 +1518,7 @@ dump_file0 (struct tar_stat_info *st, char *p, } if (atime_preserve_option) - utime (st->orig_file_name, &restore_times); + utimens (st->orig_file_name, restore_times); file_count_links (st); return; } @@ -1507,8 +1539,7 @@ dump_file0 (struct tar_stat_info *st, char *p, } buffer[size] = '\0'; assign_string (&st->link_name, buffer); - if ((archive_format == OLDGNU_FORMAT && size > OLDGNU_NAME_FIELD_SIZE) - || size > NAME_FIELD_SIZE) + if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size) write_long_link (st); block_ordinal = current_block_ordinal (); diff --git a/src/extract.c b/src/extract.c index 0e758a2c..9be0a8b1 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,7 +1,7 @@ /* Extract files from a tar archive. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, - 2001, 2003, 2004 Free Software Foundation, Inc. + 2001, 2003, 2004, 2005 Free Software Foundation, Inc. Written by John Gilmore, on 1985-11-19. @@ -21,19 +21,10 @@ #include #include +#include #include #include -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif - #include "common.h" static bool we_are_root; /* true if our effective uid == 0 */ @@ -201,15 +192,30 @@ set_mode (char const *file_name, /* Check time after successfully setting FILE_NAME's time stamp to T. */ static void -check_time (char const *file_name, time_t t) +check_time (char const *file_name, struct timespec t) { - time_t now; - if (t <= 0) + if (t.tv_sec <= 0) WARN ((0, 0, _("%s: implausibly old time stamp %s"), - file_name, tartime (t))); - else if (start_time < t && (now = time (0)) < t) - WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"), - file_name, tartime (t), (unsigned long) (t - now))); + file_name, tartime (t, true))); + else if (timespec_lt (start_time, t)) + { + struct timespec now; + gettime (&now); + if (timespec_lt (now, t)) + { + unsigned long int ds = t.tv_sec - now.tv_sec; + int dns = t.tv_nsec - now.tv_nsec; + char dnsbuf[sizeof ".FFFFFFFFF"]; + if (dns < 0) + { + dns += 1000000000; + ds--; + } + code_ns_fraction (dns, dnsbuf); + WARN ((0, 0, _("%s: time stamp %s is %lu%s s in the future"), + file_name, tartime (t, true), ds, dnsbuf)); + } + } } /* Restore stat attributes (owner, group, mode and times) for @@ -233,8 +239,6 @@ set_stat (char const *file_name, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { - struct utimbuf utimbuf; - if (typeflag != SYMTYPE) { /* We do the utime before the chmod because some versions of utime are @@ -248,19 +252,16 @@ set_stat (char const *file_name, /* FIXME: incremental_option should set ctime too, but how? */ - if (incremental_option) - utimbuf.actime = stat_info->st_atime; - else - utimbuf.actime = start_time; + struct timespec ts[2]; + ts[0] = incremental_option ? get_stat_atime (stat_info) : start_time; + ts[1] = get_stat_mtime (stat_info); - utimbuf.modtime = stat_info->st_mtime; - - if (utime (file_name, &utimbuf) < 0) + if (utimens (file_name, ts) != 0) utime_error (file_name); else { - check_time (file_name, utimbuf.actime); - check_time (file_name, utimbuf.modtime); + check_time (file_name, ts[0]); + check_time (file_name, ts[1]); } } @@ -778,7 +779,7 @@ extract_file (char *file_name, int typeflag) static int extract_link (char *file_name, int typeflag) { - char const *link_name = safer_name_suffix (current_stat_info.link_name, + char const *link_name = safer_name_suffix (current_stat_info.link_name, true, absolute_names_option); int interdir_made = 0; @@ -1134,7 +1135,7 @@ extract_archive (void) if (verbose_option) print_header (¤t_stat_info, -1); - file_name = safer_name_suffix (current_stat_info.file_name, + file_name = safer_name_suffix (current_stat_info.file_name, false, absolute_names_option); if (strip_name_components) { diff --git a/src/incremen.c b/src/incremen.c index 9bd09ffb..69805a7f 100644 --- a/src/incremen.c +++ b/src/incremen.c @@ -354,6 +354,8 @@ read_directory_file (void) _("Time stamp out of range"))); else { + /* FIXME: This should also input nanoseconds, but that will be a + change in file format. */ newer_mtime_option.tv_sec = t; newer_mtime_option.tv_nsec = 0; } @@ -444,7 +446,9 @@ write_directory_file (void) if (sys_truncate (fileno (fp)) != 0) truncate_error (listed_incremental_option); - fprintf (fp, "%lu\n", (unsigned long) start_time); + /* FIXME: This should also output nanoseconds, but that will be a + change in file format. */ + fprintf (fp, "%lu\n", (unsigned long int) start_time.tv_sec); if (! ferror (fp) && directory_table) hash_do_for_each (directory_table, write_directory_file_entry, fp); if (ferror (fp)) diff --git a/src/list.c b/src/list.c index e56e7dce..ddf044df 100644 --- a/src/list.c +++ b/src/list.c @@ -19,10 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* Define to non-zero for forcing old ctime format instead of ISO format. */ -#undef USE_OLD_CTIME - #include +#include #include #include "common.h" @@ -68,6 +66,7 @@ read_and (void (*do_something) (void)) { enum read_header status = HEADER_STILL_UNREAD; enum read_header prev_status; + struct timespec mtime; base64_init (); name_gather (); @@ -95,13 +94,12 @@ read_and (void (*do_something) (void)) || (NEWER_OPTION_INITIALIZED (newer_mtime_option) /* FIXME: We get mtime now, and again later; this causes duplicate diagnostics if header.mtime is bogus. */ - && ((current_stat_info.stat.st_mtime + && ((mtime.tv_sec = TIME_FROM_HEADER (current_header->header.mtime)), -#ifdef ST_MTIM_NSEC /* FIXME: Grab fractional time stamps from extended header. */ - current_stat_info.stat.st_mtim.ST_MTIM_NSEC = 0, -#endif + mtime.tv_nsec = 0, + set_stat_mtime (¤t_stat_info.stat, mtime), OLDER_STAT_TIME (current_stat_info.stat, m))) || excluded_name (current_stat_info.file_name)) { @@ -522,6 +520,9 @@ decode_header (union block *header, struct tar_stat_info *stat_info, enum archive_format *format_pointer, int do_user_group) { enum archive_format format; + struct timespec atime; + struct timespec ctime; + struct timespec mtime; if (strcmp (header->header.magic, TMAGIC) == 0) { @@ -543,22 +544,31 @@ decode_header (union block *header, struct tar_stat_info *stat_info, *format_pointer = format; stat_info->stat.st_mode = MODE_FROM_HEADER (header->header.mode); - stat_info->stat.st_mtime = TIME_FROM_HEADER (header->header.mtime); + mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime); + mtime.tv_nsec = 0; + set_stat_mtime (&stat_info->stat, mtime); assign_string (&stat_info->uname, header->header.uname[0] ? header->header.uname : NULL); assign_string (&stat_info->gname, header->header.gname[0] ? header->header.gname : NULL); - stat_info->devmajor = MAJOR_FROM_HEADER (header->header.devmajor); - stat_info->devminor = MINOR_FROM_HEADER (header->header.devminor); - - stat_info->stat.st_atime = start_time; - stat_info->stat.st_ctime = start_time; if (format == OLDGNU_FORMAT && incremental_option) { - stat_info->stat.st_atime = TIME_FROM_HEADER (header->oldgnu_header.atime); - stat_info->stat.st_ctime = TIME_FROM_HEADER (header->oldgnu_header.ctime); + atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime); + ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime); + atime.tv_nsec = ctime.tv_nsec = 0; } + else if (format == STAR_FORMAT) + { + atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime); + ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime); + atime.tv_nsec = ctime.tv_nsec = 0; + } + else + atime = ctime = start_time; + + set_stat_atime (&stat_info->stat, atime); + set_stat_ctime (&stat_info->stat, ctime); if (format == V7_FORMAT) { @@ -568,13 +578,6 @@ decode_header (union block *header, struct tar_stat_info *stat_info, } else { - - if (format == STAR_FORMAT) - { - stat_info->stat.st_atime = TIME_FROM_HEADER (header->star_header.atime); - stat_info->stat.st_ctime = TIME_FROM_HEADER (header->star_header.ctime); - } - if (do_user_group) { /* FIXME: Decide if this should somewhat depend on -p. */ @@ -594,8 +597,9 @@ decode_header (union block *header, struct tar_stat_info *stat_info, { case BLKTYPE: case CHRTYPE: - stat_info->stat.st_rdev = makedev (stat_info->devmajor, - stat_info->devminor); + stat_info->stat.st_rdev = + makedev (MAJOR_FROM_HEADER (header->header.devmajor), + MINOR_FROM_HEADER (header->header.devminor)); break; default: @@ -922,47 +926,59 @@ uintmax_from_header (const char *p, size_t s) /* Return a printable representation of T. The result points to static storage that can be reused in the next call to this - function, to ctime, or to asctime. */ + function, to ctime, or to asctime. If FULL_TIME, then output the + time stamp to its full resolution; otherwise, just output it to + 1-minute resolution. */ char const * -tartime (time_t t) +tartime (struct timespec t, bool full_time) { + enum { fraclen = sizeof ".FFFFFFFFF" - 1 }; static char buffer[max (UINTMAX_STRSIZE_BOUND + 1, - INT_STRLEN_BOUND (int) + 16)]; + INT_STRLEN_BOUND (int) + 16) + + fraclen]; + struct tm *tm; + time_t s = t.tv_sec; + int ns = t.tv_nsec; + bool negative = s < 0; char *p; -#if USE_OLD_CTIME - p = ctime (&t); - if (p) + if (negative && ns != 0) { - char const *time_stamp = p + 4; - for (p += 16; p[3] != '\n'; p++) - p[0] = p[3]; - p[0] = '\0'; - return time_stamp; + s++; + ns = 1000000000 - ns; } -#else - /* Use ISO 8610 format. See: - http://www.cl.cam.ac.uk/~mgk25/iso-time.html */ - struct tm *tm = utc_option ? gmtime (&t) : localtime (&t); + + tm = utc_option ? gmtime (&s) : localtime (&s); if (tm) { - sprintf (buffer, "%04ld-%02d-%02d %02d:%02d:%02d", - tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); + if (full_time) + { + sprintf (buffer, "%04ld-%02d-%02d %02d:%02d:%02d", + tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + code_ns_fraction (ns, buffer + strlen (buffer)); + } + else + sprintf (buffer, "%04ld-%02d-%02d %02d:%02d", + tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min); return buffer; } -#endif /* The time stamp cannot be broken down, most likely because it is out of range. Convert it as an integer, right-adjusted in a field with the same width as the usual - 19-byte 4-year ISO time format. */ - p = stringify_uintmax_t_backwards (t < 0 ? - (uintmax_t) t : (uintmax_t) t, - buffer + sizeof buffer); - if (t < 0) + 4-year ISO time format. */ + p = umaxtostr (negative ? - (uintmax_t) s : s, + buffer + sizeof buffer - UINTMAX_STRSIZE_BOUND - fraclen); + if (negative) *--p = '-'; - while (buffer + sizeof buffer - 19 - 1 < p) + while ((buffer + sizeof buffer - sizeof "YYYY-MM-DD HH:MM" + + (full_time ? sizeof ":SS.FFFFFFFFF" - 1 : 0)) + < p) *--p = ' '; + if (full_time) + code_ns_fraction (ns, buffer + sizeof buffer - 1 - fraclen); return p; } @@ -979,23 +995,24 @@ tartime (time_t t) /* FIXME: Note that print_header uses the globals HEAD, HSTAT, and HEAD_STANDARD, which must be set up in advance. Not very clean.. */ -/* UGSWIDTH starts with 18, so with user and group names <= 8 chars, the - columns never shift during the listing. */ -#define UGSWIDTH 18 -static int ugswidth = UGSWIDTH; /* maximum width encountered so far */ +/* Width of "user/group size", with initial value chosen + heuristically. This grows as needed, though this may cause some + stairstepping in the output. Make it too small and the output will + almost always look ragged. Make it too large and the output will + be spaced out too far. */ +static int ugswidth = 19; -/* DATEWIDTH is the number of columns taken by the date and time fields. */ -#if USE_OLD_CDATE -# define DATEWIDTH 19 -#else -# define DATEWIDTH 18 -#endif +/* Width of printed time stamps. It grows if longer time stamps are + found (typically, those with nanosecond resolution). Like + USGWIDTH, some stairstepping may occur. */ +static int datewidth = sizeof "YYYY-MM-DD HH:MM" - 1; void print_header (struct tar_stat_info *st, off_t block_ordinal) { char modes[11]; char const *time_stamp; + int time_stamp_len; char *temp_name = st->orig_file_name ? st->orig_file_name : st->file_name; /* These hold formatted ints. */ @@ -1005,6 +1022,7 @@ print_header (struct tar_stat_info *st, off_t block_ordinal) /* holds formatted size or major,minor */ char uintbuf[UINTMAX_STRSIZE_BOUND]; int pad; + int sizelen; if (block_number_option) { @@ -1084,7 +1102,10 @@ print_header (struct tar_stat_info *st, off_t block_ordinal) /* Time stamp. */ - time_stamp = tartime (st->stat.st_mtime); + time_stamp = tartime (get_stat_mtime (&st->stat), false); + time_stamp_len = strlen (time_stamp); + if (datewidth < time_stamp_len) + datewidth = time_stamp_len; /* User and group names. */ @@ -1159,12 +1180,14 @@ print_header (struct tar_stat_info *st, off_t block_ordinal) /* Figure out padding and print the whole line. */ - pad = strlen (user) + strlen (group) + strlen (size) + 1; + sizelen = strlen (size); + pad = strlen (user) + 1 + strlen (group) + 1 + sizelen; if (pad > ugswidth) ugswidth = pad; - fprintf (stdlis, "%s %s/%s %*s%s %s", - modes, user, group, ugswidth - pad, "", size, time_stamp); + fprintf (stdlis, "%s %s/%s %*s %-*s", + modes, user, group, ugswidth - pad + sizelen, size, + datewidth, time_stamp); fprintf (stdlis, " %s", quotearg (temp_name)); @@ -1248,7 +1271,7 @@ print_for_mkdir (char *dirname, int length, mode_t mode) STRINGIFY_BIGINT (current_block_ordinal (), buf)); } - fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH, + fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + 1 + datewidth, _("Creating directory:"), length, quotearg (dirname)); } } diff --git a/src/misc.c b/src/misc.c index 2ba52a38..55bedd2b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -206,6 +206,40 @@ unquote_string (char *string) return result; } +/* Handling numbers. */ + +/* Output fraction and trailing digits appropriate for a nanoseconds + count equal to NS, but don't output unnecessary '.' or trailing + zeros. */ + +void +code_ns_fraction (int ns, char *p) +{ + if (ns == 0) + *p = '\0'; + else + { + int i = 9; + *p++ = '.'; + + while (ns % 10 == 0) + { + ns /= 10; + i--; + } + + p[i] = '\0'; + + for (;;) + { + p[--i] = '0' + ns % 10; + if (i == 0) + break; + ns /= 10; + } + } +} + /* File handling. */ /* Saved names in case backup needs to be undone. */ diff --git a/src/system.c b/src/system.c index dee82b22..b1e4eef7 100644 --- a/src/system.c +++ b/src/system.c @@ -22,30 +22,6 @@ #include #include -void -sys_stat_nanoseconds (struct tar_stat_info *st) -{ -#if defined(HAVE_STRUCT_STAT_ST_SPARE1) - st->atime_nsec = st->stat.st_spare1 * 1000; - st->mtime_nsec = st->stat.st_spare2 * 1000; - st->ctime_nsec = st->stat.st_spare3 * 1000; -#elif defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) - st->atime_nsec = st->stat.st_atim.tv_nsec; - st->mtime_nsec = st->stat.st_mtim.tv_nsec; - st->ctime_nsec = st->stat.st_ctim.tv_nsec; -#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC) - st->atime_nsec = st->stat.st_atimespec.tv_nsec; - st->mtime_nsec = st->stat.st_mtimespec.tv_nsec; - st->ctime_nsec = st->stat.st_ctimespec.tv_nsec; -#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) - st->atime_nsec = st->stat.st_atimensec; - st->mtime_nsec = st->stat.st_mtimensec; - st->ctime_nsec = st->stat.st_ctimensec; -#else - st->atime_nsec = st->mtime_nsec = st->ctime_nsec = 0; -#endif -} - #if MSDOS bool @@ -642,7 +618,7 @@ static void oct_to_env (char *envar, unsigned long num) { char buf[1+1+(sizeof(unsigned long)*CHAR_BIT+2)/3]; - + snprintf (buf, sizeof buf, "0%lo", num); setenv (envar, buf, 1); } @@ -679,7 +655,7 @@ stat_to_env (char *name, char type, struct tar_stat_info *st) dec_to_env ("TAR_CTIME", st->stat.st_ctime); dec_to_env ("TAR_SIZE", st->stat.st_size); dec_to_env ("TAR_UID", st->stat.st_uid); - dec_to_env ("TAR_GID", st->stat.st_gid); + dec_to_env ("TAR_GID", st->stat.st_gid); switch (type) { @@ -713,7 +689,7 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st) { int p[2]; char *argv[4]; - + xpipe (p); pipe_handler = signal (SIGPIPE, SIG_IGN); pid = xfork (); @@ -727,7 +703,7 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st) /* Child */ xdup2 (p[PREAD], STDIN_FILENO); xclose (p[PWRITE]); - + stat_to_env (file_name, typechar, st); argv[0] = "/bin/sh"; @@ -744,7 +720,7 @@ void sys_wait_command (void) { int status; - + if (pid < 0) return; @@ -776,4 +752,3 @@ sys_wait_command (void) } #endif /* not MSDOS */ - diff --git a/src/tar.c b/src/tar.c index 692120e0..b0e039db 100644 --- a/src/tar.c +++ b/src/tar.c @@ -656,7 +656,7 @@ read_name_from_file (FILE *fp, struct obstack *stk) { int c; size_t counter = 0; - + for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp)) { if (c == 0) @@ -721,7 +721,7 @@ update_argv (const char *filename, struct argp_state *state) size_t new_argc; bool is_stdin = false; enum read_file_list_state read_state; - + if (!strcmp (filename, "-")) { is_stdin = true; @@ -741,7 +741,7 @@ update_argv (const char *filename, struct argp_state *state) if (read_state == file_list_zero) { size_t size; - + WARN ((0, 0, N_("%s: file name read contains nul character"), quotearg_colon (filename))); @@ -751,7 +751,7 @@ update_argv (const char *filename, struct argp_state *state) for (; size > 0; size--, p++) if (*p) obstack_1grow (&argv_stk, *p); - else + else obstack_1grow (&argv_stk, '\n'); obstack_1grow (&argv_stk, 0); count = 1; @@ -973,15 +973,14 @@ parse_opt (int key, char *arg, struct argp_state *state) stat_error (arg); USAGE_ERROR ((0, 0, _("Date sample file not found"))); } - newer_mtime_option.tv_sec = st.st_mtime; - newer_mtime_option.tv_nsec = TIMESPEC_NS (st.st_mtim); + newer_mtime_option = get_stat_mtime (&st); } else { if (! get_date (&newer_mtime_option, arg, NULL)) { WARN ((0, 0, _("Substituting %s for unknown date format %s"), - tartime (newer_mtime_option.tv_sec), quote (arg))); + tartime (newer_mtime_option, false), quote (arg))); newer_mtime_option.tv_nsec = 0; } else @@ -1819,16 +1818,10 @@ decode_options (int argc, char **argv) if (verbose_option && args.textual_date_option) { - /* FIXME: tartime should support nanoseconds, too, so that this - comparison doesn't complain about lost nanoseconds. */ - char const *treated_as = tartime (newer_mtime_option.tv_sec); + char const *treated_as = tartime (newer_mtime_option, true); if (strcmp (args.textual_date_option, treated_as) != 0) - WARN ((0, 0, - ngettext ("Treating date `%s' as %s + %ld nanosecond", - "Treating date `%s' as %s + %ld nanoseconds", - newer_mtime_option.tv_nsec), - args.textual_date_option, treated_as, - newer_mtime_option.tv_nsec)); + WARN ((0, 0, _("Treating date `%s' as %s"), + args.textual_date_option, treated_as)); } } diff --git a/src/tar.h b/src/tar.h index cc937bbc..3e958ec8 100644 --- a/src/tar.h +++ b/src/tar.h @@ -277,17 +277,10 @@ struct tar_stat_info trailing slash before it was normalized. */ char *link_name; /* name of link for the current archive entry. */ - unsigned int devminor; /* device minor number */ - unsigned int devmajor; /* device major number */ char *uname; /* user name of owner */ char *gname; /* group name of owner */ struct stat stat; /* regular filesystem stat */ - /* Nanosecond parts of file timestamps (if available) */ - unsigned long atime_nsec; - unsigned long mtime_nsec; - unsigned long ctime_nsec; - off_t archive_file_size; /* Size of file as stored in the archive. Equals stat.st_size for non-sparse files */ diff --git a/src/xheader.c b/src/xheader.c index a45d24a9..e88934e8 100644 --- a/src/xheader.c +++ b/src/xheader.c @@ -20,14 +20,21 @@ #include #include +#include #include #include -#include #include "common.h" #include +#if !HAVE_DECL_STRTOIMAX && !defined strtoimax +intmax_t strtoimax (); +#endif +#if !HAVE_DECL_STRTOUMAX && !defined strtoumax +uintmax_t strtoumax (); +#endif + static bool xheader_protected_pattern_p (char const *pattern); static bool xheader_protected_keyword_p (char const *keyword); static void xheader_set_single_keyword (char *) __attribute__ ((noreturn)); @@ -50,6 +57,8 @@ static size_t global_header_count; However it should wait until buffer.c is finally rewritten */ +enum { BILLION = 1000000000, LOG10_BILLION = 9 }; + /* Keyword options */ @@ -191,26 +200,6 @@ xheader_set_option (char *string) } } -static void -to_decimal (uintmax_t value, char *where, size_t size) -{ - size_t i = 0, j; - - where[i++] = 0; - do - { - where[i++] = '0' + value % 10; - value /= 10; - } - while (i < size && value); - for (j = 0, i--; j < i; j++, i--) - { - char c = where[j]; - where[j] = where[i]; - where[i] = c; - } -} - /* string Includes: Replaced By: %d The directory name of the file, @@ -232,8 +221,10 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n) const char *p; char *dir = NULL; char *base = NULL; - char pidbuf[64]; - char nbuf[64]; + char pidbuf[UINTMAX_STRSIZE_BOUND]; + char const *pptr; + char nbuf[UINTMAX_STRSIZE_BOUND]; + char const *nptr = NULL; for (p = fmt; *p && (p = strchr (p, '%')); ) { @@ -246,7 +237,7 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n) case 'd': if (st) { - dir = safer_name_suffix (dir_name (st->orig_file_name), + dir = safer_name_suffix (dir_name (st->orig_file_name), false, absolute_names_option); len += strlen (dir) - 1; } @@ -261,15 +252,15 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n) break; case 'p': - to_decimal (getpid (), pidbuf, sizeof pidbuf); - len += strlen (pidbuf) - 1; + pptr = umaxtostr (getpid (), pidbuf); + len += pidbuf + sizeof pidbuf - 1 - pptr - 1; break; case 'n': if (allow_n) { - to_decimal (global_header_count + 1, pidbuf, sizeof pidbuf); - len += strlen (nbuf) - 1; + nptr = umaxtostr (global_header_count + 1, nbuf); + len += nbuf + sizeof nbuf - 1 - nptr - 1; } break; } @@ -301,14 +292,14 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n) break; case 'p': - q = stpcpy (q, pidbuf); + q = stpcpy (q, pptr); p += 2; break; case 'n': - if (allow_n) + if (nptr) { - q = stpcpy (q, nbuf); + q = stpcpy (q, nptr); p += 2; } /* else fall through */ @@ -492,9 +483,11 @@ decode_record (char **ptr, errno = 0; len = strtoul (p, &len_lim, 10); - if (len_max < len || (len == ULONG_MAX && errno == ERANGE)) + if (len_max < len) { - ERROR ((0, 0, _("Malformed extended header: length out of range"))); + int len_len = len_lim - p; + ERROR ((0, 0, _("Extended header length %*s is out of range"), + len_len, p)); return false; } @@ -650,48 +643,24 @@ xheader_read (union block *p, size_t size) while (size > 0); } -static size_t -format_uintmax (uintmax_t val, char *buf, size_t s) -{ - if (!buf) - { - s = 0; - do - s++; - while ((val /= 10) != 0); - } - else - { - char *p = buf + s - 1; - - do - { - *p-- = val % 10 + '0'; - } - while ((val /= 10) != 0); - - while (p >= buf) - *p-- = '0'; - } - return s; -} - static void xheader_print (struct xheader *xhdr, char const *keyword, char const *value) { size_t len = strlen (keyword) + strlen (value) + 3; /* ' ' + '=' + '\n' */ - size_t p, n = 0; - char nbuf[100]; + size_t p; + size_t n = 0; + char nbuf[UINTMAX_STRSIZE_BOUND]; + char const *np; do { p = n; - n = format_uintmax (len + p, NULL, 0); + np = umaxtostr (len + p, nbuf); + n = nbuf + sizeof nbuf - 1 - np; } while (n != p); - format_uintmax (len + n, nbuf, n); - obstack_grow (xhdr->stk, nbuf, n); + obstack_grow (xhdr->stk, np, n); obstack_1grow (xhdr->stk, ' '); obstack_grow (xhdr->stk, keyword, strlen (keyword)); obstack_1grow (xhdr->stk, '='); @@ -729,6 +698,24 @@ xheader_destroy (struct xheader *xhdr) /* Implementations */ + +static void +out_of_range_header (char const *keyword, char const *value, + uintmax_t minus_minval, uintmax_t maxval) +{ + char minval_buf[UINTMAX_STRSIZE_BOUND + 1]; + char maxval_buf[UINTMAX_STRSIZE_BOUND]; + char *minval_string = umaxtostr (minus_minval, minval_buf + 1); + char *maxval_string = umaxtostr (maxval, maxval_buf); + if (minus_minval) + *--minval_string = '-'; + + /* TRANSLATORS: The first %s is the pax extended header keyword + (atime, gid, etc.). */ + ERROR ((0, 0, _("Extended header %s=%s is out of range %s..%s"), + keyword, value, minval_string, maxval_string)); +} + static void code_string (char const *string, char const *keyword, struct xheader *xhdr) { @@ -758,41 +745,141 @@ decode_string (char **string, char const *arg) } static void -code_time (time_t t, unsigned long nano, - char const *keyword, struct xheader *xhdr) +code_time (struct timespec t, char const *keyword, struct xheader *xhdr) { - char sbuf[200]; - size_t s = format_uintmax (t, NULL, 0); - if (s + 11 >= sizeof sbuf) - return; - format_uintmax (t, sbuf, s); - sbuf[s++] = '.'; - s += format_uintmax (nano, sbuf + s, 9); - sbuf[s] = 0; - xheader_print (xhdr, keyword, sbuf); + time_t s = t.tv_sec; + int ns = t.tv_nsec; + char sbuf[1/*"-"*/ + UINTMAX_STRSIZE_BOUND + 1/*"."*/ + LOG10_BILLION]; + char *np; + bool negative = s < 0; + + if (negative && ns != 0) + { + s++; + ns = BILLION - ns; + } + + np = umaxtostr (negative ? - (uintmax_t) s : (uintmax_t) s, sbuf + 1); + if (negative) + *--np = '-'; + code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND); + xheader_print (xhdr, keyword, np); } -static void -decode_time (char const *arg, time_t *secs, unsigned long *nsecs) +static bool +decode_time (struct timespec *ts, char const *arg, char const *keyword) { - uintmax_t u; + time_t s; + unsigned long int ns = 0; char *p; - if (xstrtoumax (arg, &p, 10, &u, "") == LONGINT_OK) + char *arg_lim; + bool negative = *arg == '-'; + + errno = 0; + + if (ISDIGIT (arg[negative])) { - *secs = u; - if (*p == '.' && xstrtoumax (p+1, NULL, 10, &u, "") == LONGINT_OK) - *nsecs = u; + if (negative) + { + intmax_t i = strtoimax (arg, &arg_lim, 10); + if (TYPE_SIGNED (time_t) ? i < TYPE_MINIMUM (time_t) : i < 0) + goto out_of_range; + s = i; + } + else + { + uintmax_t i = strtoumax (arg, &arg_lim, 10); + if (TYPE_MAXIMUM (time_t) < i) + goto out_of_range; + s = i; + } + + p = arg_lim; + + if (errno == ERANGE) + goto out_of_range; + + if (*p == '.') + { + int digits = 0; + bool trailing_nonzero = false; + + while (ISDIGIT (*++p)) + if (digits < LOG10_BILLION) + { + ns = 10 * ns + (*p - '0'); + digits++; + } + else + trailing_nonzero |= *p != '0'; + + while (digits++ < LOG10_BILLION) + ns *= 10; + + if (negative) + { + /* Convert "-1.10000000000001" to s == -2, ns == 89999999. + I.e., truncate time stamps towards minus infinity while + converting them to internal form. */ + ns += trailing_nonzero; + if (ns != 0) + { + if (s == TYPE_MINIMUM (time_t)) + goto out_of_range; + s--; + ns = BILLION - ns; + } + } + } + + if (! *p) + { + ts->tv_sec = s; + ts->tv_nsec = ns; + return true; + } } + + ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), + keyword, arg)); + return false; + + out_of_range: + out_of_range_header (keyword, arg, - (uintmax_t) TYPE_MINIMUM (time_t), + TYPE_MAXIMUM (time_t)); + return false; } static void code_num (uintmax_t value, char const *keyword, struct xheader *xhdr) { - char sbuf[100]; - size_t s = format_uintmax (value, NULL, 0); - format_uintmax (value, sbuf, s); - sbuf[s] = 0; - xheader_print (xhdr, keyword, sbuf); + char sbuf[UINTMAX_STRSIZE_BOUND]; + xheader_print (xhdr, keyword, umaxtostr (value, sbuf)); +} + +static bool +decode_num (uintmax_t *num, char const *arg, uintmax_t maxval, + char const *keyword) +{ + uintmax_t u; + char *arg_lim; + + if (! (ISDIGIT (*arg) + && (errno = 0, u = strtoumax (arg, &arg_lim, 10), !*arg_lim))) + { + ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), + keyword, arg)); + return false; + } + + if (! (u <= maxval && errno != ERANGE)) + { + out_of_range_header (keyword, arg, 0, maxval); + return false; + } + + *num = u; + return true; } static void @@ -813,13 +900,15 @@ static void atime_coder (struct tar_stat_info const *st, char const *keyword, struct xheader *xhdr, void *data __attribute__ ((unused))) { - code_time (st->stat.st_atime, st->atime_nsec, keyword, xhdr); + code_time (get_stat_atime (&st->stat), keyword, xhdr); } static void atime_decoder (struct tar_stat_info *st, char const *arg) { - decode_time (arg, &st->stat.st_atime, &st->atime_nsec); + struct timespec ts; + if (decode_time (&ts, arg, "atime")) + set_stat_atime (&st->stat, ts); } static void @@ -833,7 +922,7 @@ static void gid_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, TYPE_MAXIMUM (gid_t), "gid")) st->stat.st_gid = u; } @@ -867,26 +956,30 @@ static void ctime_coder (struct tar_stat_info const *st, char const *keyword, struct xheader *xhdr, void *data __attribute__ ((unused))) { - code_time (st->stat.st_ctime, st->ctime_nsec, keyword, xhdr); + code_time (get_stat_ctime (&st->stat), keyword, xhdr); } static void ctime_decoder (struct tar_stat_info *st, char const *arg) { - decode_time (arg, &st->stat.st_ctime, &st->ctime_nsec); + struct timespec ts; + if (decode_time (&ts, arg, "ctime")) + set_stat_ctime (&st->stat, ts); } static void mtime_coder (struct tar_stat_info const *st, char const *keyword, struct xheader *xhdr, void *data __attribute__ ((unused))) { - code_time (st->stat.st_mtime, st->mtime_nsec, keyword, xhdr); + code_time (get_stat_mtime (&st->stat), keyword, xhdr); } static void mtime_decoder (struct tar_stat_info *st, char const *arg) { - decode_time (arg, &st->stat.st_mtime, &st->mtime_nsec); + struct timespec ts; + if (decode_time (&ts, arg, "mtime")) + set_stat_mtime (&st->stat, ts); } static void @@ -915,7 +1008,7 @@ static void size_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "size")) st->archive_file_size = st->stat.st_size = u; } @@ -930,7 +1023,7 @@ static void uid_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, TYPE_MAXIMUM (uid_t), "uid")) st->stat.st_uid = u; } @@ -958,7 +1051,7 @@ static void sparse_size_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "GNU.sparse.size")) st->stat.st_size = u; } @@ -974,10 +1067,10 @@ static void sparse_numblocks_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, SIZE_MAX, "GNU.sparse.numblocks")) { st->sparse_map_size = u; - st->sparse_map = calloc(st->sparse_map_size, sizeof(st->sparse_map[0])); + st->sparse_map = xcalloc (u, sizeof st->sparse_map[0]); st->sparse_map_avail = 0; } } @@ -994,7 +1087,7 @@ static void sparse_offset_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "GNU.sparse.offset")) st->sparse_map[st->sparse_map_avail].offset = u; } @@ -1010,15 +1103,12 @@ static void sparse_numbytes_decoder (struct tar_stat_info *st, char const *arg) { uintmax_t u; - if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK) + if (decode_num (&u, arg, SIZE_MAX, "GNU.sparse.numbytes")) { if (st->sparse_map_avail == st->sparse_map_size) - { - st->sparse_map_size *= 2; - st->sparse_map = xrealloc (st->sparse_map, - st->sparse_map_size - * sizeof st->sparse_map[0]); - } + st->sparse_map = x2nrealloc (st->sparse_map, + &st->sparse_map_size, + sizeof st->sparse_map[0]); st->sparse_map[st->sparse_map_avail++].numbytes = u; } }