/* Update a tar archive.
- Copyright (C) 1988, 1992 Free Software Foundation
-
-This file is part of GNU Tar.
-
-GNU Tar is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-GNU Tar is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Tar; see the file COPYING. If not, write to
-the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
-
-/* JF implement the 'r' 'u' and 'A' options for tar. */
-/* The 'A' option is my own invention: It means that the file-names are
- tar files, and they should simply be appended to the end of the archive.
- No attempt is made to block the reads from the args; if they're on raw
- tape or something like that, it'll probably lose. . . */
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <errno.h>
-#ifndef STDC_HEADERS
-extern int errno;
-#endif
-
-#ifndef NO_MTIO
-#include <sys/ioctl.h>
-#include <sys/mtio.h>
-#endif
-
-#ifdef BSD42
-#include <sys/file.h>
-#else
-#ifndef V7
-#include <fcntl.h>
-#endif
-#endif
-
-#ifndef __MSDOS__
-#include <pwd.h>
-#include <grp.h>
-#endif
-
-#define STDIN 0
-#define STDOUT 1
-
-#include "tar.h"
-#include "port.h"
-#include "rmt.h"
-
-int time_to_start_writing = 0; /* We've hit the end of the old stuff,
- and its time to start writing new stuff
- to the tape. This involves seeking
- back one block and re-writing the current
- block (which has been changed). */
-
-char *output_start; /* Pointer to where we started to write in
- the first block we write out. This is used
- if we can't backspace the output and have
- to null out the first part of the block */
-
-extern void skip_file();
-extern void skip_extended_headers();
-
-extern union record *head;
-extern struct stat hstat;
-
-void append_file();
-void close_archive();
-int confirm();
-void decode_header();
-void fl_read();
-void fl_write();
-void flush_archive();
-int move_arch();
-struct name *name_scan();
-char *name_from_list();
-void name_expand();
-void name_gather();
-void names_notfound();
-void open_archive();
-int read_header();
-void reset_eof();
-void write_block();
-void write_eot();
-
-/* Implement the 'r' (add files to end of archive), and 'u' (add files to
- end of archive if they arent there, or are more up to date than the
- version in the archive.) commands.*/
-void
-update_archive()
-{
- int found_end = 0;
- int status = 3;
- int prev_status;
- char *p;
- struct name *name;
- extern void dump_file();
-
- name_gather();
- if(cmd_mode==CMD_UPDATE)
- name_expand();
- open_archive(2); /* Open for updating */
-
- do {
- prev_status=status;
- status=read_header();
- switch(status) {
- case EOF:
- found_end=1;
- break;
-
- case 0: /* A bad record */
- userec(head);
- switch(prev_status) {
- case 3:
- msg("This doesn't look like a tar archive.");
- /* FALL THROUGH */
- case 2:
- case 1:
- msg("Skipping to next header");
- case 0:
- break;
- }
- break;
-
- /* A good record */
- case 1:
- /* printf("File %s\n",head->header.name); */
- /* head->header.name[NAMSIZ-1]='\0'; */
- if(cmd_mode==CMD_UPDATE && (name=name_scan(head->header.name))) {
- /* struct stat hstat; */
- struct stat nstat;
- int head_standard;
-
- decode_header(head,&hstat,&head_standard,0);
- if(stat(head->header.name,&nstat)<0) {
- msg_perror("can't stat %s:",head->header.name);
- } else {
- if(hstat.st_mtime>=nstat.st_mtime)
- name->found++;
- }
- }
- userec(head);
- if (head->header.isextended)
- skip_extended_headers();
- skip_file((long)hstat.st_size);
- break;
-
- case 2:
- ar_record=head;
- found_end = 1;
- break;
- }
- } while(!found_end);
-
- reset_eof();
- time_to_start_writing = 1;
- output_start=ar_record->charptr;
-
- while(p=name_from_list()) {
- if(f_confirm && !confirm("add", p))
- continue;
- if(cmd_mode==CMD_CAT)
- append_file(p);
- else
- dump_file(p,-1, 1);
- }
-
- write_eot();
- close_archive();
- names_notfound();
-}
-
-/* Catenate file p to the archive without creating a header for it. It had
- better be a tar file or the archive is screwed */
-
-void
-append_file(p)
-char *p;
-{
- int fd;
- struct stat statbuf;
- long bytes_left;
- union record *start;
- long bufsiz,count;
-
- if(0 != stat(p,&statbuf) || (fd=open(p,O_RDONLY|O_BINARY))<0) {
- msg_perror("can't open file %s",p);
- errors++;
- return;
- }
-
- bytes_left = statbuf.st_size;
-
- while(bytes_left>0) {
- start=findrec();
- bufsiz=endofrecs()->charptr - start->charptr;
- if(bytes_left < bufsiz) {
- bufsiz = bytes_left;
- count = bufsiz % RECORDSIZE;
- if(count)
- bzero(start->charptr + bytes_left,(int)(RECORDSIZE-count));
- }
- count=read(fd,start->charptr,bufsiz);
- if(count<0) {
- msg_perror("read error at byte %ld reading %d bytes in file %s",statbuf.st_size-bytes_left,bufsiz,p);
- exit(EX_ARGSBAD); /* FOO */
- }
- bytes_left-=count;
- userec(start+(count-1)/RECORDSIZE);
- if(count!=bufsiz) {
- msg("%s: file shrunk by %d bytes, yark!",p,bytes_left);
- abort();
- }
- }
- (void)close(fd);
-}
-
-#ifdef DONTDEF
-bprint(fp,buf,num)
-FILE *fp;
-char *buf;
-{
- int c;
-
- if(num==0 || num==-1)
- return;
- fputs(" '",fp);
- while(num--) {
- c= *buf++;
- if(c=='\\') fputs("\\\\",fp);
- else if(c>=' ' && c<='~')
- putc(c,fp);
- else switch(c) {
- case '\n':
- fputs("\\n",fp);
- break;
- case '\r':
- fputs("\\r",fp);
- break;
- case '\b':
- fputs("\\b",fp);
- break;
- case '\0':
- /* fputs("\\-",fp); */
- break;
- default:
- fprintf(fp,"\\%03o",c);
- break;
- }
- }
- fputs("'\n",fp);
-}
-#endif
-
-int number_of_blocks_read = 0;
-
-int number_of_new_records = 0;
-int number_of_records_needed = 0;
-union record *new_block = 0;
-union record *save_block = 0;
-
-void
-junk_archive()
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 1999, 2000, 2001, 2003,
+ 2004, 2005, 2007, 2010 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 the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Implement the 'r', 'u' and 'A' options for tar. 'A' means that the
+ file names are tar files, and they should simply be appended to the end
+ of the archive. No attempt is made to record the reads from the args; if
+ they're on raw tape or something like that, it'll probably lose... */
+
+#include <system.h>
+#include <quotearg.h>
+#include "common.h"
+
+/* FIXME: This module should not directly handle the following variable,
+ instead, this should be done in buffer.c only. */
+extern union block *current_block;
+
+/* We've hit the end of the old stuff, and its time to start writing new
+ stuff to the tape. This involves seeking back one record and
+ re-writing the current record (which has been changed).
+ FIXME: Either eliminate it or move it to common.h.
+*/
+bool time_to_start_writing;
+
+/* Pointer to where we started to write in the first record we write out.
+ This is used if we can't backspace the output and have to null out the
+ first part of the record. */
+char *output_start;
+
+/* Catenate file FILE_NAME to the archive without creating a header for it.
+ It had better be a tar file or the archive is screwed. */
+static void
+append_file (char *file_name)
{
- int found_stuff = 0;
- int status = 3;
- int prev_status;
- struct name *name;
-
- /* int dummy_head; */
- int number_of_records_to_skip = 0;
- int number_of_records_to_keep = 0;
- int number_of_kept_records_in_block;
- int sub_status;
- extern int write_archive_to_stdout;
-
-/* fprintf(stderr,"Junk files\n"); */
- name_gather();
- open_archive(2);
-
- while(!found_stuff) {
- prev_status=status;
- status=read_header();
- switch(status) {
- case EOF:
- found_stuff = 1;
- break;
-
- case 0:
- userec(head);
- switch(prev_status) {
- case 3:
- msg("This doesn't look like a tar archive.");
- /* FALL THROUGH */
- case 2:
- case 1:
- msg("Skipping to next header");
- /* FALL THROUGH */
- case 0:
- break;
- }
- break;
-
- case 1:
- /* head->header.name[NAMSIZ-1] = '\0'; */
- /* fprintf(stderr,"file %s\n",head->header.name); */
- if((name=name_scan(head->header.name))==(struct name *)0) {
- userec(head);
- /* fprintf(stderr,"Skip %ld\n",(long)(hstat.st_size)); */
- if (head->header.isextended)
- skip_extended_headers();
- skip_file((long)(hstat.st_size));
- break;
- }
- name->found = 1;
- found_stuff = 2;
- break;
-
- case 2:
- found_stuff = 1;
- break;
- }
- }
- /* fprintf(stderr,"Out of first loop\n"); */
-
- if(found_stuff!=2) {
- write_eot();
- close_archive();
- names_notfound();
- return;
- }
-
- if(write_archive_to_stdout)
- write_archive_to_stdout = 0;
- new_block = (union record *)malloc(blocksize);
- if(new_block==0) {
- msg("Can't allocate secondary block of %d bytes",blocksize);
- exit(EX_SYSTEM);
- }
-
- /* Save away records before this one in this block */
- number_of_new_records=ar_record-ar_block;
- number_of_records_needed = blocking - number_of_new_records;
- if(number_of_new_records)
- bcopy((void *)ar_block,(void *)new_block,(number_of_new_records)*RECORDSIZE);
-
- /* fprintf(stderr,"Saved %d recs, need %d more\n",number_of_new_records,number_of_records_needed); */
- userec(head);
- if (head->header.isextended)
- skip_extended_headers();
- skip_file((long)(hstat.st_size));
- found_stuff=0;
- /* goto flush_file; */
-
- for(;;) {
- /* Fill in a block */
- /* another_file: */
- if(ar_record==ar_last) {
- /* fprintf(stderr,"New block\n"); */
- flush_archive();
- number_of_blocks_read++;
- }
- sub_status = read_header();
- /* fprintf(stderr,"Header type %d\n",sub_status); */
-
- if(sub_status==2 && f_ignorez) {
- userec(head);
- continue;
- }
- if(sub_status==EOF || sub_status==2) {
- found_stuff = 1;
- bzero(new_block[number_of_new_records].charptr,RECORDSIZE*number_of_records_needed);
- number_of_new_records+=number_of_records_needed;
- number_of_records_needed = 0;
- write_block(0);
- break;
- }
-
- if(sub_status==0) {
- msg("Deleting non-header from archive.");
- userec(head);
- continue;
- }
-
- /* Found another header. Yipee! */
- /* head->header.name[NAMSIZ-1] = '\0'; */
- /* fprintf(stderr,"File %s ",head->header.name); */
- if(name=name_scan(head->header.name)) {
- name->found = 1;
- /* fprintf(stderr,"Flush it\n"); */
- /* flush_file: */
- /* decode_header(head,&hstat,&dummy_head,0); */
- userec(head);
- number_of_records_to_skip=(hstat.st_size+RECORDSIZE-1)/RECORDSIZE;
- /* fprintf(stderr,"Flushing %d recs from %s\n",number_of_records_to_skip,head->header.name); */
-
- while(ar_last-ar_record<=number_of_records_to_skip) {
-
- /* fprintf(stderr,"Block: %d <= %d ",ar_last-ar_record,number_of_records_to_skip); */
- number_of_records_to_skip -= (ar_last - ar_record);
- flush_archive();
- number_of_blocks_read++;
- /* fprintf(stderr,"Block %d left\n",number_of_records_to_skip); */
- }
- ar_record+=number_of_records_to_skip;
- /* fprintf(stderr,"Final %d\n",number_of_records_to_skip); */
- number_of_records_to_skip = 0;
- continue;
- }
-
- /* copy_header: */
- new_block[number_of_new_records]= *head;
- number_of_new_records++;
- number_of_records_needed--;
- number_of_records_to_keep=(hstat.st_size+RECORDSIZE-1)/RECORDSIZE;
- userec(head);
- if(number_of_records_needed==0)
- write_block(1);
- /* copy_data: */
- number_of_kept_records_in_block = ar_last - ar_record;
- if(number_of_kept_records_in_block > number_of_records_to_keep)
- number_of_kept_records_in_block = number_of_records_to_keep;
-
- /* fprintf(stderr,"Need %d kept_in %d keep %d\n",blocking,number_of_kept_records_in_block,number_of_records_to_keep); */
-
- while(number_of_records_to_keep) {
- int n;
-
- if(ar_record==ar_last) {
- /* fprintf(stderr,"Flush. . .\n"); */
- fl_read();
- number_of_blocks_read++;
- ar_record=ar_block;
- number_of_kept_records_in_block = blocking;
- if(number_of_kept_records_in_block > number_of_records_to_keep)
- number_of_kept_records_in_block = number_of_records_to_keep;
- }
- n = number_of_kept_records_in_block;
- if(n>number_of_records_needed)
- n = number_of_records_needed;
-
- /* fprintf(stderr,"Copying %d\n",n); */
- bcopy((void *)ar_record, (void *)(new_block+number_of_new_records), n*RECORDSIZE);
- number_of_new_records += n;
- number_of_records_needed -= n;
- ar_record += n;
- number_of_records_to_keep -= n;
- number_of_kept_records_in_block -= n;
- /* fprintf(stderr,"Now new %d need %d keep %d keep_in %d rec %d/%d\n",
- number_of_new_records,number_of_records_needed,number_of_records_to_keep,
- number_of_kept_records_in_block,ar_record-ar_block,ar_last-ar_block); */
-
- if(number_of_records_needed == 0) {
- write_block(1);
- }
- }
+ int handle = openat (chdir_fd, file_name, O_RDONLY | O_BINARY);
+ struct stat stat_data;
+
+ if (handle < 0)
+ {
+ open_error (file_name);
+ return;
+ }
+
+ if (fstat (handle, &stat_data) != 0)
+ stat_error (file_name);
+ else
+ {
+ off_t bytes_left = stat_data.st_size;
+
+ while (bytes_left > 0)
+ {
+ union block *start = find_next_block ();
+ size_t buffer_size = available_space_after (start);
+ size_t status;
+ char buf[UINTMAX_STRSIZE_BOUND];
+
+ if (bytes_left < buffer_size)
+ {
+ buffer_size = bytes_left;
+ status = buffer_size % BLOCKSIZE;
+ if (status)
+ memset (start->buffer + bytes_left, 0, BLOCKSIZE - status);
+ }
+
+ status = safe_read (handle, start->buffer, buffer_size);
+ if (status == SAFE_READ_ERROR)
+ read_fatal_details (file_name, stat_data.st_size - bytes_left,
+ buffer_size);
+ if (status == 0)
+ FATAL_ERROR ((0, 0,
+ ngettext ("%s: File shrank by %s byte",
+ "%s: File shrank by %s bytes",
+ bytes_left),
+ quotearg_colon (file_name),
+ STRINGIFY_BIGINT (bytes_left, buf)));
+
+ bytes_left -= status;
+
+ set_next_block_after (start + (status - 1) / BLOCKSIZE);
}
+ }
- write_eot();
- close_archive();
- names_notfound();
+ if (close (handle) != 0)
+ close_error (file_name);
}
+/* Implement the 'r' (add files to end of archive), and 'u' (add files
+ to end of archive if they aren't there, or are more up to date than
+ the version in the archive) commands. */
void
-write_block(f)
- int f;
+update_archive (void)
{
- /* fprintf(stderr,"Write block\n"); */
- /* We've filled out a block. Write it out. */
-
- /* Backspace back to where we started. . . */
- if(archive!=STDIN)
- (void)move_arch(-(number_of_blocks_read+1));
-
- save_block = ar_block;
- ar_block = new_block;
-
- if(archive==STDIN)
- archive=STDOUT;
- fl_write();
-
- if(archive==STDOUT)
- archive=STDIN;
- ar_block = save_block;
-
- if(f) {
- /* Move the tape head back to where we were */
- if(archive!=STDIN)
- (void)move_arch(number_of_blocks_read);
- number_of_blocks_read--;
+ enum read_header previous_status = HEADER_STILL_UNREAD;
+ bool found_end = false;
+
+ name_gather ();
+ open_archive (ACCESS_UPDATE);
+ buffer_write_global_xheader ();
+
+ while (!found_end)
+ {
+ enum read_header status = read_header (¤t_header,
+ ¤t_stat_info,
+ read_header_auto);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+
+ case HEADER_SUCCESS:
+ {
+ struct name *name;
+
+ decode_header (current_header, ¤t_stat_info,
+ ¤t_format, 0);
+ archive_format = current_format;
+
+ if (subcommand_option == UPDATE_SUBCOMMAND
+ && (name = name_scan (current_stat_info.file_name)) != NULL)
+ {
+ struct stat s;
+
+ chdir_do (name->change_dir);
+ if (deref_stat (current_stat_info.file_name, &s) == 0)
+ {
+ if (S_ISDIR (s.st_mode))
+ {
+ char *p, *dirp;
+ DIR *stream;
+ int fd = openat (chdir_fd, name->name,
+ open_read_flags | O_DIRECTORY);
+ if (fd < 0)
+ open_error (name->name);
+ else if (! ((stream = fdopendir (fd))
+ && (dirp = streamsavedir (stream))))
+ savedir_error (name->name);
+ else
+ {
+ namebuf_t nbuf = namebuf_create (name->name);
+
+ for (p = dirp; *p; p += strlen (p) + 1)
+ addname (namebuf_name (nbuf, p),
+ 0, false, NULL);
+
+ namebuf_free (nbuf);
+ free (dirp);
+
+ remname (name);
+ }
+
+ if (stream
+ ? closedir (stream) != 0
+ : 0 <= fd && close (fd) != 0)
+ savedir_error (name->name);
+ }
+ else if (tar_timespec_cmp (get_stat_mtime (&s),
+ current_stat_info.mtime)
+ <= 0)
+ remname (name);
+ }
+ }
+
+ skip_member ();
+ break;
+ }
+
+ case HEADER_ZERO_BLOCK:
+ current_block = current_header;
+ found_end = true;
+ break;
+
+ case HEADER_END_OF_FILE:
+ found_end = true;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+ }
+ break;
}
- number_of_records_needed = blocking;
- number_of_new_records = 0;
+ tar_stat_destroy (¤t_stat_info);
+ previous_status = status;
+ }
+
+ reset_eof ();
+ time_to_start_writing = true;
+ output_start = current_block->buffer;
+
+ {
+ struct name const *p;
+ while ((p = name_from_list ()) != NULL)
+ {
+ char *file_name = p->name;
+ if (excluded_name (file_name))
+ continue;
+ if (interactive_option && !confirm ("add", file_name))
+ continue;
+ if (subcommand_option == CAT_SUBCOMMAND)
+ append_file (file_name);
+ else
+ dump_file (0, file_name, file_name);
+ }
+ }
+
+ write_eot ();
+ close_archive ();
+ finish_deferred_unlinks ();
+ names_notfound ();
}
-
-/* Move archive descriptor by n blocks worth. If n is positive we move
- forward, else we move negative. If its a tape, MTIOCTOP had better
- work. If its something else, we try to seek on it. If we can't
- seek, we lose! */
-int
-move_arch(n)
- int n;
-{
- long cur;
-
-#ifdef MTIOCTOP
- struct mtop t;
- int er;
-
- if(n>0) {
- t.mt_op = MTFSR;
- t.mt_count = n;
- } else {
- t.mt_op = MTBSR;
- t.mt_count = -n;
- }
- if((er=rmtioctl(archive,MTIOCTOP,&t))>=0)
- return 1;
- if(errno==EIO && (er=rmtioctl(archive,MTIOCTOP,&t))>=0)
- return 1;
-#endif
-
- cur=rmtlseek(archive,0L,1);
- cur+=blocksize*n;
-
- /* fprintf(stderr,"Fore to %x\n",cur); */
- if(rmtlseek(archive,cur,0)!=cur) {
- /* Lseek failed. Try a different method */
- msg("Couldn't re-position archive file.");
- exit(EX_BADARCH);
- }
- return 3;
-}
-