X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=3c35d80cec8630c14bd3299c6c4384fcb8f71830;hb=63db9071d21b749f6883346cdea820aa3426c422;hp=8cabb52cf9704b9df47b2abe5859aa0ce40010b1;hpb=22596ef5992e948ed407404a180347cc0d79d205;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 8cabb52..3c35d80 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,5 +1,8 @@ /* Extract files from a tar archive. - Copyright 1988, 92,93,94,96,97,98,99, 2000 Free Software Foundation, Inc. + + Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, + 2001 Free Software Foundation, Inc. + Written by John Gilmore, on 1985-11-19. This program is free software; you can redistribute it and/or modify it @@ -18,7 +21,6 @@ #include "system.h" #include -#include #if HAVE_UTIME_H # include @@ -51,9 +53,14 @@ enum permstatus }; /* List of directories whose statuses we need to extract after we've - finished extracting their subsidiary files. The head of the list - has the longest name; each non-head element in the list is an - ancestor (in the directory hierarchy) of the preceding element. */ + finished extracting their subsidiary files. If you consider each + contiguous subsequence of elements of the form [D]?[^D]*, where [D] + represents an element where AFTER_SYMLINKS is nonzero and [^D] + represents an element where AFTER_SYMLINKS is zero, then the head + of the subsequence has the longest name, and each non-head element + in the prefix is an ancestor (in the directory hierarchy) of the + preceding element. */ + struct delayed_set_stat { struct delayed_set_stat *next; @@ -61,6 +68,7 @@ struct delayed_set_stat size_t file_name_len; mode_t invert_permissions; enum permstatus permstatus; + bool after_symlinks; char file_name[1]; }; @@ -72,8 +80,7 @@ struct delayed_symlink /* The next delayed symbolic link in the list. */ struct delayed_symlink *next; - /* The device, inode number and last-modified time of the - placeholder symbolic link. */ + /* The device, inode number and last-modified time of the placeholder. */ dev_t dev; ino_t ino; time_t mtime; @@ -82,13 +89,22 @@ struct delayed_symlink uid_t uid; gid_t gid; - /* The location and desired target of the desired link, as two - adjacent character strings, both null-terminated. */ - char names[1]; + /* A list of sources for this symlink. The sources are all to be + hard-linked together. */ + struct string_list *sources; + + /* The desired target of the desired link. */ + char target[1]; }; static struct delayed_symlink *delayed_symlink_head; +struct string_list + { + struct string_list *next; + char string[1]; + }; + /* Set up to extract files. */ void extr_init (void) @@ -113,12 +129,14 @@ extr_init (void) } /* If restoring permissions, restore the mode for FILE_NAME from - information given in *STAT_INFO; otherwise invert the + information given in *STAT_INFO (where *CURRENT_STAT_INFO gives + the current status if CURRENT_STAT_INFO is nonzero); otherwise invert the INVERT_PERMISSIONS bits from the file's current permissions. PERMSTATUS specifies the status of the file's permissions. TYPEFLAG specifies the type of the file. */ static void set_mode (char const *file_name, struct stat const *stat_info, + struct stat const *current_stat_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -150,12 +168,16 @@ set_mode (char const *file_name, struct stat const *stat_info, that we created, so there's no point optimizing this code for other cases. */ struct stat st; - if (stat (file_name, &st) != 0) + if (! current_stat_info) { - stat_error (file_name); - return; + if (stat (file_name, &st) != 0) + { + stat_error (file_name); + return; + } + current_stat_info = &st; } - mode = st.st_mode ^ invert_permissions; + mode = current_stat_info->st_mode ^ invert_permissions; } if (chmod (file_name, mode) != 0) @@ -174,6 +196,8 @@ check_time (char const *file_name, time_t t) /* Restore stat attributes (owner, group, mode and times) for FILE_NAME, using information given in *STAT_INFO. + If CURRENT_STAT_INFO is nonzero, *CURRENT_STAT_INFO is the + file's currernt status. If not restoring permissions, invert the INVERT_PERMISSIONS bits from the file's current permissions. PERMSTATUS specifies the status of the file's permissions. @@ -186,6 +210,7 @@ check_time (char const *file_name, time_t t) static void set_stat (char const *file_name, struct stat const *stat_info, + struct stat const *current_stat_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -224,7 +249,7 @@ set_stat (char const *file_name, struct stat const *stat_info, done, it is not possible anymore to change file permissions, so we have to set permissions prior to possibly giving files away. */ - set_mode (file_name, stat_info, + set_mode (file_name, stat_info, current_stat_info, invert_permissions, permstatus, typeflag); } @@ -253,7 +278,7 @@ set_stat (char const *file_name, struct stat const *stat_info, away, changing the owner or group destroys the suid or sgid bits. So let's attempt setting these bits once more. */ if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - set_mode (file_name, stat_info, + set_mode (file_name, stat_info, 0, invert_permissions, permstatus, typeflag); } } @@ -270,11 +295,14 @@ delay_set_stat (char const *file_name, struct stat const *stat_info, mode_t invert_permissions, enum permstatus permstatus) { size_t file_name_len = strlen (file_name); - struct delayed_set_stat *data = xmalloc (sizeof *data + file_name_len); + struct delayed_set_stat *data = + xmalloc (offsetof (struct delayed_set_stat, file_name) + + file_name_len + 1); data->file_name_len = file_name_len; strcpy (data->file_name, file_name); data->invert_permissions = invert_permissions; data->permstatus = permstatus; + data->after_symlinks = 0; data->stat_info = *stat_info; data->next = delayed_set_stat_head; delayed_set_stat_head = data; @@ -282,9 +310,8 @@ delay_set_stat (char const *file_name, struct stat const *stat_info, /* Update the delayed_set_stat info for an intermediate directory created on the path to DIR_NAME. The intermediate directory turned - out to be the same as this directory, e.g. due trailing slash or - ".." or symbolic links. *DIR_STAT_INFO is the status of the - directory. */ + out to be the same as this directory, e.g. due to ".." or symbolic + links. *DIR_STAT_INFO is the status of the directory. */ static void repair_delayed_set_stat (char const *dir_name, struct stat const *dir_stat_info) @@ -321,27 +348,30 @@ repair_delayed_set_stat (char const *dir_name, static int make_directories (char *file_name) { + char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name); char *cursor; /* points into path */ int did_something = 0; /* did we do anything yet? */ int mode; int invert_permissions; int status; - for (cursor = strchr (file_name, '/'); - cursor; - cursor = strchr (cursor + 1, '/')) + + for (cursor = cursor0; *cursor; cursor++) { + if (! ISSLASH (*cursor)) + continue; + /* Avoid mkdir of empty string, if leading or double '/'. */ - if (cursor == file_name || cursor[-1] == '/') + if (cursor == cursor0 || ISSLASH (cursor[-1])) continue; /* Avoid mkdir where last part of path is "." or "..". */ if (cursor[-1] == '.' - && (cursor == file_name + 1 || cursor[-2] == '/' + && (cursor == cursor0 + 1 || ISSLASH (cursor[-2]) || (cursor[-2] == '.' - && (cursor == file_name + 2 || cursor[-3] == '/')))) + && (cursor == cursor0 + 2 || ISSLASH (cursor[-3]))))) continue; *cursor = '\0'; /* truncate the path there */ @@ -453,13 +483,13 @@ static void extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) { int sparse_ind = 0; - size_t written; - ssize_t count; /* assuming sizeleft is initially totalsize */ while (*sizeleft > 0) { + size_t written; + size_t count; union block *data_block = find_next_block (); if (! data_block) { @@ -475,10 +505,13 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) while (written > BLOCKSIZE) { count = full_write (fd, data_block->buffer, BLOCKSIZE); - if (count < 0) - write_error (name); written -= count; *sizeleft -= count; + if (count != BLOCKSIZE) + { + write_error_details (name, count, BLOCKSIZE); + return; + } set_next_block_after (data_block); data_block = find_next_block (); if (! data_block) @@ -489,42 +522,69 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) } count = full_write (fd, data_block->buffer, written); + *sizeleft -= count; - if (count < 0) - write_error (name); - else if (count != written) + if (count != written) { write_error_details (name, count, written); - skip_file (*sizeleft); + return; } - written -= count; - *sizeleft -= count; set_next_block_after (data_block); } - - free (sparsearray); } /* Fix the statuses of all directories whose statuses need fixing, and - which are not ancestors of FILE_NAME. */ + which are not ancestors of FILE_NAME. If AFTER_SYMLINKS is + nonzero, do this for all such directories; otherwise, stop at the + first directory that is marked to be fixed up only after delayed + symlinks are applied. */ static void -apply_nonancestor_delayed_set_stat (char const *file_name) +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) { size_t file_name_len = strlen (file_name); + bool check_for_renamed_directories = 0; while (delayed_set_stat_head) { struct delayed_set_stat *data = delayed_set_stat_head; - if (data->file_name_len < file_name_len - && file_name[data->file_name_len] - && (file_name[data->file_name_len] == '/' - || file_name[data->file_name_len - 1] == '/') - && memcmp (file_name, data->file_name, data->file_name_len) == 0) + bool skip_this_one = 0; + struct stat st; + struct stat const *current_stat_info = 0; + + check_for_renamed_directories |= data->after_symlinks; + + if (after_symlinks < data->after_symlinks + || (data->file_name_len < file_name_len + && file_name[data->file_name_len] + && (ISSLASH (file_name[data->file_name_len]) + || ISSLASH (file_name[data->file_name_len - 1])) + && memcmp (file_name, data->file_name, data->file_name_len) == 0)) break; + + if (check_for_renamed_directories) + { + current_stat_info = &st; + if (stat (data->file_name, &st) != 0) + { + stat_error (data->file_name); + skip_this_one = 1; + } + else if (! (st.st_dev == data->stat_info.st_dev + && (st.st_ino == data->stat_info.st_ino))) + { + ERROR ((0, 0, + _("%s: Directory renamed before its status could be extracted"), + quotearg_colon (data->file_name))); + skip_this_one = 1; + } + } + + if (! skip_this_one) + set_stat (data->file_name, &data->stat_info, current_stat_info, + data->invert_permissions, data->permstatus, DIRTYPE); + delayed_set_stat_head = data->next; - set_stat (data->file_name, &data->stat_info, - data->invert_permissions, data->permstatus, DIRTYPE); free (data); } } @@ -536,13 +596,13 @@ extract_archive (void) union block *data_block; int fd; int status; - ssize_t sstatus; + size_t count; size_t name_length; size_t written; int openflag; mode_t mode; off_t size; - int skipcrud; + size_t skipcrud; int counter; int interdir_made = 0; char typeflag; @@ -569,28 +629,32 @@ extract_archive (void) skipcrud = 0; if (! absolute_names_option) { - while (CURRENT_FILE_NAME[0] == '/') + if (contains_dot_dot (CURRENT_FILE_NAME)) + { + ERROR ((0, 0, _("%s: Member name contains `..'"), + quotearg_colon (CURRENT_FILE_NAME))); + skip_member (); + return; + } + + skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name); + while (ISSLASH (CURRENT_FILE_NAME[0])) + skipcrud++; + + if (skipcrud) { static int warned_once; if (!warned_once) { warned_once = 1; - WARN ((0, 0, _("Removing leading `/' from member names"))); + WARN ((0, 0, _("Removing leading `%.*s' from member names"), + (int) skipcrud, current_file_name)); } - skipcrud++; /* force relative path */ - } - - if (contains_dot_dot (CURRENT_FILE_NAME)) - { - ERROR ((0, 0, _("%s: Member name contains `..'"), - quotearg_colon (CURRENT_FILE_NAME))); - skip_member (); - return; } } - apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME); + apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME, 0); /* Take a safety backup of a previously existing file. */ @@ -693,7 +757,8 @@ extract_archive (void) suffix means a directory. */ name_length = strlen (CURRENT_FILE_NAME); - if (name_length && CURRENT_FILE_NAME[name_length - 1] == '/') + if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length + && CURRENT_FILE_NAME[name_length - 1] == '/') goto really_dir; /* FIXME: deal with protection issues. */ @@ -773,11 +838,10 @@ extract_archive (void) memcpy (name, CURRENT_FILE_NAME, name_length_bis); size = current_stat.st_size; extract_sparse_file (fd, &size, current_stat.st_size, name); + free (sparsearray); } else - for (size = current_stat.st_size; - size > 0; - size -= written) + for (size = current_stat.st_size; size > 0; ) { if (multi_volume_option) { @@ -802,21 +866,20 @@ extract_archive (void) if (written > size) written = size; errno = 0; - sstatus = full_write (fd, data_block->buffer, written); + count = full_write (fd, data_block->buffer, written); + size -= count; set_next_block_after ((union block *) (data_block->buffer + written - 1)); - if (sstatus == written) - continue; - - /* Error in writing to file. Print it, skip to next file in - archive. */ - - write_error_details (CURRENT_FILE_NAME, sstatus, written); - skip_file (size - written); - break; /* still do the close, mod time, chmod, etc */ + if (count != written) + { + write_error_details (CURRENT_FILE_NAME, count, written); + break; + } } + skip_file (size); + if (multi_volume_option) assign_string (&save_name, 0); @@ -834,7 +897,7 @@ extract_archive (void) undo_last_backup (); } - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, (old_files_option == OVERWRITE_OLD_FILES ? UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), @@ -847,8 +910,9 @@ extract_archive (void) break; if (absolute_names_option - || (current_link_name[0] != '/' - && ! contains_dot_dot (current_link_name))) + || ! (ISSLASH (current_link_name + [FILESYSTEM_PREFIX_LEN (current_link_name)]) + || contains_dot_dot (current_link_name))) { while (status = symlink (current_link_name, CURRENT_FILE_NAME), status != 0) @@ -856,7 +920,7 @@ extract_archive (void) break; if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, SYMTYPE); + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, 0, SYMTYPE); else symlink_error (current_link_name, CURRENT_FILE_NAME); } @@ -884,10 +948,10 @@ extract_archive (void) close_error (CURRENT_FILE_NAME); else { - size_t filelen = strlen (CURRENT_FILE_NAME); - size_t linklen = strlen (current_link_name); + struct delayed_set_stat *h; struct delayed_symlink *p = - xmalloc (sizeof *p + filelen + linklen + 1); + xmalloc (offsetof (struct delayed_symlink, target) + + strlen (current_link_name) + 1); p->next = delayed_symlink_head; delayed_symlink_head = p; p->dev = st.st_dev; @@ -895,8 +959,34 @@ extract_archive (void) p->mtime = st.st_mtime; p->uid = current_stat.st_uid; p->gid = current_stat.st_gid; - memcpy (p->names, CURRENT_FILE_NAME, filelen + 1); - memcpy (p->names + filelen + 1, current_link_name, linklen + 1); + p->sources = xmalloc (offsetof (struct string_list, string) + + strlen (CURRENT_FILE_NAME) + 1); + p->sources->next = 0; + strcpy (p->sources->string, CURRENT_FILE_NAME); + strcpy (p->target, current_link_name); + + h = delayed_set_stat_head; + if (h && ! h->after_symlinks + && strncmp (CURRENT_FILE_NAME, h->file_name, h->file_name_len) == 0 + && ISSLASH (CURRENT_FILE_NAME[h->file_name_len]) + && (base_name (CURRENT_FILE_NAME) + == CURRENT_FILE_NAME + h->file_name_len + 1)) + { + do + { + h->after_symlinks = 1; + + if (stat (h->file_name, &st) != 0) + stat_error (h->file_name); + else + { + h->stat_info.st_dev = st.st_dev; + h->stat_info.st_ino = st.st_ino; + } + } + while ((h = h->next) && ! h->after_symlinks); + } + status = 0; } } @@ -934,7 +1024,24 @@ extract_archive (void) status = link (current_link_name, CURRENT_FILE_NAME); if (status == 0) - break; + { + struct delayed_symlink *ds = delayed_symlink_head; + if (ds && stat (current_link_name, &st1) == 0) + for (; ds; ds = ds->next) + if (ds->dev == st1.st_dev + && ds->ino == st1.st_ino + && ds->mtime == st1.st_mtime) + { + struct string_list *p = + xmalloc (offsetof (struct string_list, string) + + strlen (CURRENT_FILE_NAME) + 1); + strcpy (p->string, CURRENT_FILE_NAME); + p->next = ds->sources; + ds->sources = p; + break; + } + break; + } if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_link; @@ -947,9 +1054,7 @@ extract_archive (void) && st1.st_ino == st2.st_ino) break; - ERROR ((0, e, _("%s: Cannot link to %s"), - quotearg_colon (CURRENT_FILE_NAME), - quote (current_link_name))); + link_error (current_link_name, CURRENT_FILE_NAME); if (backup_option) undo_last_backup (); } @@ -982,7 +1087,7 @@ extract_archive (void) undo_last_backup (); break; }; - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); break; #endif @@ -998,7 +1103,7 @@ extract_archive (void) break; if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); else { @@ -1014,6 +1119,12 @@ extract_archive (void) name_length = strlen (CURRENT_FILE_NAME); really_dir: + /* Remove any redundant trailing "/"s. */ + while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length + && CURRENT_FILE_NAME[name_length - 1] == '/') + name_length--; + CURRENT_FILE_NAME[name_length] = '\0'; + if (incremental_option) { /* Read the entry and delete files that aren't listed in the @@ -1033,15 +1144,27 @@ extract_archive (void) again_dir: status = mkdir (CURRENT_FILE_NAME, mode); + if (status != 0) { - if (errno == EEXIST && interdir_made) + if (errno == EEXIST + && (interdir_made + || old_files_option == OVERWRITE_OLD_DIRS + || old_files_option == OVERWRITE_OLD_FILES)) { struct stat st; if (stat (CURRENT_FILE_NAME, &st) == 0) { - repair_delayed_set_stat (CURRENT_FILE_NAME, &st); - break; + if (interdir_made) + { + repair_delayed_set_stat (CURRENT_FILE_NAME, &st); + break; + } + if (S_ISDIR (st.st_mode)) + { + mode = st.st_mode & ~ current_umask; + goto directory_exists; + } } errno = EEXIST; } @@ -1049,7 +1172,7 @@ extract_archive (void) if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_dir; - if (errno != EEXIST || old_files_option == KEEP_OLD_FILES) + if (errno != EEXIST) { mkdir_error (CURRENT_FILE_NAME); if (backup_option) @@ -1058,10 +1181,12 @@ extract_archive (void) } } + directory_exists: if (status == 0 + || old_files_option == OVERWRITE_OLD_DIRS || old_files_option == OVERWRITE_OLD_FILES) delay_set_stat (CURRENT_FILE_NAME, ¤t_stat, - mode & ~ current_stat.st_mode, + MODE_RWX & (mode ^ current_stat.st_mode), (status == 0 ? ARCHIVED_PERMSTATUS : UNKNOWN_PERMSTATUS)); @@ -1107,40 +1232,56 @@ extract_archive (void) static void apply_delayed_symlinks (void) { - struct delayed_symlink *p; - struct delayed_symlink *next; + struct delayed_symlink *ds; - for (p = delayed_symlink_head; p; p = next) + for (ds = delayed_symlink_head; ds; ) { - char const *file = p->names; - struct stat st; + struct string_list *sources = ds->sources; + char const *valid_source = 0; - /* Before doing anything, make sure the placeholder file is still - there. If the placeholder isn't there, don't worry about it, as - it may have been removed by a later extraction. */ - if (lstat (file, &st) == 0 - && st.st_dev == p->dev - && st.st_ino == p->ino - && st.st_mtime == p->mtime) + for (sources = ds->sources; sources; sources = sources->next) { - if (unlink (file) != 0) - unlink_error (file); - else + char const *source = sources->string; + struct stat st; + + /* Make sure the placeholder file is still there. If not, + don't create a symlink, as the placeholder was probably + removed by a later extraction. */ + if (lstat (source, &st) == 0 + && st.st_dev == ds->dev + && st.st_ino == ds->ino + && st.st_mtime == ds->mtime) { - char const *contents = file + strlen (file) + 1; - if (symlink (contents, file) != 0) - symlink_error (contents, file); + /* Unlink the placeholder, then create a hard link if possible, + a symbolic link otherwise. */ + if (unlink (source) != 0) + unlink_error (source); + else if (valid_source && link (valid_source, source) == 0) + ; + else if (symlink (ds->target, source) != 0) + symlink_error (ds->target, source); else { - st.st_uid = p->uid; - st.st_gid = p->gid; - set_stat (file, &st, 0, 0, SYMTYPE); + valid_source = source; + st.st_uid = ds->uid; + st.st_gid = ds->gid; + set_stat (source, &st, 0, 0, 0, SYMTYPE); } } } - next = p->next; - free (p); + for (sources = ds->sources; sources; ) + { + struct string_list *next = sources->next; + free (sources); + sources = next; + } + + { + struct delayed_symlink *next = ds->next; + free (ds); + ds = next; + } } delayed_symlink_head = 0; @@ -1150,10 +1291,16 @@ apply_delayed_symlinks (void) void extract_finish (void) { - /* Apply delayed symlinks last, so that they don't affect - delayed directory status-setting. */ - apply_nonancestor_delayed_set_stat (""); + /* First, fix the status of ordinary directories that need fixing. */ + apply_nonancestor_delayed_set_stat ("", 0); + + /* Then, apply delayed symlinks, so that they don't affect delayed + directory status-setting for ordinary directories. */ apply_delayed_symlinks (); + + /* Finally, fix the status of directories that are ancestors + of delayed symlinks. */ + apply_nonancestor_delayed_set_stat ("", 1); } void