/************************************************************************ * * * Copyright (c) 1984, Fred Fish * * All Rights Reserved * * * * This software and/or documentation is protected by U.S. * * Copyright Law (Title 17 United States Code). Unauthorized * * reproduction and/or sales may result in imprisonment of up * * to 1 year and fines of up to $10,000 (17 USC 506). * * Copyright infringers may also be subject to civil liability. * * * ************************************************************************ */ /* * FILE * * extract.c contains routines specific to extracting files * * SCCS * * @(#)extract.c 9.11 5/11/88 * * DESCRIPTION * * Contains routines specific to extracting files from bru archives. * */ #include "autoconfig.h" #include #if (unix || xenix) # include # include #else # include "sys.h" #endif #include "typedefs.h" #include "dbug.h" #include "manifest.h" #include "config.h" #include "errors.h" #include "blocks.h" #include "macros.h" #include "trees.h" #include "finfo.h" #include "flags.h" #include "bruinfo.h" #define DIR_MAGIC 0777 /* Default permissions for directories */ /* This is modified by umask */ dev_t saved_st_dev = 0; /* * External bru functions. */ extern BOOLEAN decompfip (); /* Decompress a file, saving to destination */ extern BOOLEAN openzfile (); /* Open a temp file for compressed file */ extern int setinfo (); extern int s_mknod (); /* Make a directory, special, or other file */ extern BOOLEAN confirmed (); /* Confirm action */ extern union blk *ar_next (); /* Get pointer to next archive block */ extern VOID bru_error (); /* Report an error to user */ extern VOID ar_close (); /* Flush buffers and close the archive */ extern VOID ar_open (); /* Open the archive */ extern VOID ar_read (); /* Read archive block */ extern VOID reload (); /* Reload first volume for rescan */ extern VOID done (); /* Finish up and exit */ extern BOOLEAN newdir (); /* Make a directory */ extern BOOLEAN mklink (); /* Make a link to a file */ extern BOOLEAN mksymlink (); /* Make a symbolic link to a file */ extern VOID scan (); /* Scan archive */ extern VOID verbosity (); /* Issue verbosity message */ extern BOOLEAN selected (); /* Apply selection criteria */ extern VOID finfo_init (); /* Initialize a finfo structure */ extern BOOLEAN file_access (); /* Check file for access */ extern BOOLEAN dir_access (); /* Check parent for access */ extern BOOLEAN out_of_date (); /* Existing file out of date */ extern BOOLEAN unconditional(); /* Unconditionally overwrite */ extern char *s_strrchr (); /* Find last given character in string */ extern int s_close (); /* Invoke library file close function */ extern int s_umask (); /* Invoke umask library function */ extern int s_creat (); /* Create a file */ extern VOID file_chown (); /* Change file owner and group */ extern int s_utime (); /* Set file access/modification times */ extern int s_chmod (); /* Change mode of a file */ extern int s_write (); /* Write data to file */ extern int s_unlink (); /* Remove a directory entry */ extern long s_time (); /* Get current time */ extern VOID mark_archived (); /* Set the "has been archived" bit */ extern VOID clear_archived (); /* Clear the "has been archived" bit */ extern char *getlinkname(); extern char *add_link(); /* * External bru variables. */ extern struct bru_info info; /* Current invocation information */ extern struct cmd_flags flags; /* Command line flags */ extern char mode; /* Current mode (citdxh) */ /* * Local functions. */ static VOID do_extract (); /* Actually do the extraction */ static VOID xfile (); /* Extract the file */ static VOID makelink (); /* Make link to existing file */ static BOOLEAN makedir (); /* Make a directory */ static BOOLEAN makeparent (); /* Make a parent directory */ static VOID makestat (); /* Make default stat buffer */ static VOID makespecial (); /* Make a special file */ static VOID xregfile (); /* Do extraction of regular file */ static VOID makefile (); /* Make first copy of file */ static VOID attributes (); /* Set attributes to match */ /* * Stuff for signal handling. */ extern VOID sig_catch (); /* Set signal catch state */ extern VOID sig_push (); /* Push signal state */ extern VOID sig_pop (); /* Pop signal state */ extern BOOLEAN interrupt; /* Interrupt received */ /* * NAME * * extract main entry point for extraction of file from archive * * SYNOPSIS * * VOID extract () * * DESCRIPTION * * This is the main entry point for extracting files from a bru * archive. It is called once all the command line options * have been processed and common initialization has been * performed. * */ VOID extract () { DBUG_ENTER ("extract"); mode = 'x'; reload ("extraction"); ar_open (); scan (xfile); ar_close (); DBUG_VOID_RETURN; } /* * NAME * * xfile control extraction of a single file * * SYNOPSIS * * static VOID xfile (blkp, fip) * register union blk *blkp; * register struct finfo *fip; * * DESCRIPTION * * Controls extraction of a single file from the archive. * Blkp points to the file header block and fip points * to a file information structure. * * There was initially some question about how to report * files which are not extracted because there is an existing * file which is more recent. Since files that ARE extracted * are only reported if verbosity is enabled, by analogy, files * that are NOT extracted are reported only if verbosity is * enabled. The basic philosophy is to silently bring the existing * hierarchy up to date by replacing missing files and * superceding out of date files, without overwriting more * recent files. If every file in a given class (regular, * directory, block special, etc) is to be extracted regardless * of modification date, bru must be explicitly told this via * the -u (unconditional) flag. * */ /* * PSEUDO CODE * * Begin xfile * Determine whether or not file is a directory * If this file is to be extracted then * If unconditional extraction or file out of date then * If extraction confirmed then * Issue verbosity message * Do the extraction * End if * End if * Else * If file is not a directory and verbosity enabled * Warn user that it was not extracted * End if * End if * End if * End xfile * */ static VOID xfile (blkp, fip) register union blk *blkp; register struct finfo *fip; { register BOOLEAN is_directory; struct stat64 alpha; char tmp[256]; char *savelinkname; DBUG_ENTER ("xfile"); is_directory = IS_DIR (fip -> statp -> st_mode); if ((IS_STEM (fip) && is_directory) || IS_LEAF (fip) || IS_EXTENSION (fip)) { /* change absolute path to relative path if -j flag specified */ if( (flags.jflag) && (*fip->fname == '/') ) { if(strcmp(fip->fname, "/") == 0) DBUG_VOID_RETURN; strcpy(tmp, fip->fname); strcpy(fip->fname, &tmp[1]); } if(flags.mflag) { if(saved_st_dev != fip->statp->st_dev) { if(access(fip->fname, 0)) { DBUG_VOID_RETURN; } else { stat64(fip->fname, &alpha); if(alpha.st_dev == fip->statp->st_dev) saved_st_dev = fip->statp->st_dev; } } } /* symbolic links that are hard linked to other symbolic links we * have already seen are treated as just links. SGI seems to * be one of the few vendors that even allows this; the only way * make this work (and remain compatible with older versions of * bru both directions) on list and extract is to build the link * table, same as on create and estimate... */ savelinkname = fip->lname; if(!IS_FLNK(fip->statp->st_mode) || (fip->statp->st_nlink>1 && getlinkname(fip))) { fip->lname = NULL; } if(!is_directory && fip->statp -> st_nlink > 1) { fip->lname = add_link(fip); if(!fip->lname && IS_FLNK(fip->statp->st_mode)) fip->lname = savelinkname; } if (unconditional (fip) || out_of_date (fip)) { if (confirmed ("x %s", fip)) { verbosity (fip); do_extract (blkp, fip); } } else { if (!is_directory && flags.vflag > 1) { bru_error (ERR_SUPERSEDE, fip -> fname); } } } DBUG_VOID_RETURN; } /* * FUNCTION * * makelink link file being extracted to an existing file * * SYNOPSIS * * static VOID makelink (blkp, fip) * union blk *blkp; * register struct finfo *fip; * * DESCRIPTION * * Links the file being extracted (file1) to the existing file * (file2) that it is supposed to be linked to. * * Note that this will fail if file2 does not exist. * In this case there is nothing else to be done since the file * contents for file2 are not stored in the archived file1. * * Also note that any existing file1 is unlinked. If the * new link cannot be made for some reason, the effective * result is the deletion of any existing file1. This * could be fixed by first linking an existing file1 to some * temporary and then either unlinking or relinking it as * necessary once the link between the new file1 and file2 * is established or denied respectively. Another way is * to open the existing file1, unlink the existing file1, * then either close file1 or copy it back to a new * instance of file1 as necessary. * */ /* * PSEUDO CODE * * Begin makelink * If file to be linked to does not exist then * Tell user we can't make the link * Else * Unlink any existing file to be linked * If the link to the target file is successful then * Set the attributes of the link just made * End if * End if * End makelink * */ static VOID makelink (blkp, fip) union blk *blkp; register struct finfo *fip; { char tmp[256]; DBUG_ENTER ("makelink"); if(flags.jflag) { if(!fip->lname) { bru_error (ERR_MKLINK_J, fip->fname); return; } if(strcmp(fip->lname, "") && *fip->lname == '/') { strcpy(tmp, fip->lname); strcpy(fip->lname, &tmp[1]); } if (!file_access (fip->lname, A_EXISTS, FALSE)) { bru_error (ERR_MKLINK, fip->fname, fip->lname); } else { (VOID) s_unlink (fip->fname); if (mklink (fip->lname, fip->fname)) { attributes (fip); } } } else { if (!file_access (blkp -> FH.f_lname, A_EXISTS, FALSE)) { bru_error (ERR_MKLINK, blkp -> HD.h_name, blkp -> FH.f_lname); } else { (VOID) s_unlink (blkp -> HD.h_name); if (mklink (blkp -> FH.f_lname, blkp -> HD.h_name)) { attributes (fip); } } } DBUG_VOID_RETURN; } /* * FUNCTION * * makefile extract a regular file * * SYNOPSIS * * static VOID makefile (blkp, fip) * register union blk *blkp; * register struct finfo *fip; * * DESCRIPTION * * Controls extract of a regular file by testing for * for it's existance and/or writability. The file * will only be extracted if it does not currently exist * or if the user has write permission to overwrite an * existing version. * */ /* * PSEUOD CODE * * Begin makefile * If file does not exist then * Go ahead and extract it from archive * Else * If file is not writable then * Tell user he can't overwrite the file * Else * Go ahead and extract it from archive * End if * End if * End makefile * */ static VOID makefile (blkp, fip) register union blk *blkp; register struct finfo *fip; { DBUG_ENTER ("makefile"); if (!file_access (fip -> fname, A_EXISTS, FALSE)) { xregfile (blkp, fip); } else { if (!file_access (fip -> fname, A_WRITE, FALSE)) { bru_error (ERR_OVRWRT, fip -> fname); } else { xregfile (blkp, fip); } } DBUG_VOID_RETURN; } /* * FUNCTION * * xregfile extract a normal file from archive * * SYNOPSIS * * static VOID xregfile (blkp, fip) * register union blk *blkp; * register struct finfo *fip; * * DESCRIPTION * * This routine is responsible for extracting a normal file from * an archive. * * Returns TRUE if file was created, FALSE otherwise. * Note that return of TRUE does NOT indicate successful * extraction, only that a file was created. * */ /* * PSEUDO CODE * * Begin xregfile * Initialize the file truncated flag to FALSE * Create the file being extracted * If couldn't be created then * Inform user * Else * Get number of bytes to extract * If any bytes to extract then * For each block to be extracted * Seek to archive block * Read the archive block * If extracting a partial block then * Only use that many bytes * End if * Write the bytes to the new file * If write did not write requested number then * Remember that the file was truncated * End if * End for * If last write returned error then * Notify user about write error * End if * If file was truncated then * Warn user about truncation * End if * End if * If file close get an error then * Warn user about the close error * End if * Set the attributes of the extracted file * End if * End xregfile * */ static VOID xregfile (blkp, fip) register union blk *blkp; register struct finfo *fip; { register off_t bytes; register int iobytes; register UINT wbytes; register int fildes; register BOOLEAN truncated = FALSE; register BOOLEAN doextract = TRUE; register char *fname; DBUG_ENTER ("xregfile"); if (IS_COMPRESSED (fip)) { if (openzfile (fip)) { fname = fip -> zfname; fildes = fip -> zfildes; } else { bru_error (ERR_ZXFAIL, fip -> fname); doextract = FALSE; } } else { fname = fip -> fname; fildes = s_creat (fip -> fname, 0600); if (fildes == SYS_ERROR) { bru_error (ERR_CREAT, fip -> fname); doextract = FALSE; } } if (doextract) { bytes = ZSIZE (fip); if (bytes > 0) { for (wbytes = DATASIZE; bytes > 0; bytes -= wbytes) { blkp = ar_next (); ar_read (fip); if (bytes < DATASIZE) { wbytes = bytes; } iobytes = s_write (fildes, blkp -> FD, wbytes); if (iobytes != wbytes) { truncated = TRUE; } } if (iobytes == SYS_ERROR) { bru_error (ERR_WRITE, fname); } if (truncated) { bru_error (ERR_FTRUNC, fname); } } if (s_close (fildes) == SYS_ERROR) { bru_error (ERR_CLOSE, fname); } if (!IS_COMPRESSED (fip) || decompfip (fip)) { attributes (fip); } } DBUG_VOID_RETURN; } /* * FUNCTION * * makespecial make a special file node * * SYNOPSIS * * static VOID makespecial (fip) * register struct finfo *fip; * * DESCRIPTION * * Is reponsible for controlling creation of special files. * If the special file being made already exists, it is * unlinked first, then the new node is made. * * Adding code for 4.2 has complicated this routine, and made it * very ugly. It is even worse than it ought to be, since it is * currently impossible under 4.2 to chmod a symbolic link (although * a symlink with mode 000 can still be read!), or to reset the access * and modification times on a symbolic link. This is dealt with * in attributes(), below. * */ /* * PSEUDO CODE * * Begin makespecial * If the current file exists then * Unlink the file * End if * If under 4.2, not a pyramid, and file is a fifo, then * Just create a regular file * Set the attributes of the new node * Else if the file is a symbolic link then * If cannot make the symbolic link then * Inform the user * Else * Set the attributes of the new node * Endif * Else if the new node cannot be made then * Inform the user * Else * Set attributes of the new node * End if * End makespecial * */ static VOID makespecial (fip) register struct finfo *fip; { char *tmpname; DBUG_ENTER ("makespecial"); if (file_access (fip -> fname, A_EXISTS, FALSE)) { (VOID) s_unlink (fip -> fname); } #if !HAVE_FIFOS if (IS_FIFO (fip -> statp -> st_mode)) { int fd; fd = s_creat (fip -> fname, 0666); /* fudge it */ if (fd == SYS_ERROR) { bru_error (ERR_MKFIFO, fip -> fname); } else { attributes (fip); bru_error (ERR_FIFOTOREG, fip -> fname); if (s_close (fd) == SYS_ERROR) { bru_error (ERR_CLOSE, fip -> fname); } } } else #endif if (IS_FLNK (fip -> statp -> st_mode)) { if (! mksymlink (fip -> lname, fip -> fname) && ! flags.lflag) { bru_error (ERR_MKSYMLINK, fip -> fname); } else { attributes (fip); } } else if (s_mknod (fip -> fname, (int) fip -> statp -> st_mode, (int) fip -> statp -> st_rdev) == SYS_ERROR) { bru_error (ERR_MKNOD, fip -> fname); } else { attributes (fip); } DBUG_VOID_RETURN; } /* * FUNCTION * * attributes set attributes of newly created file * * SYNOPSIS * * static VOID attributes (fip) * register struct finfo *fip; * * DESCRIPTION * * Using stat buffer information recovered from archive * header block, sets the attributes of the file. * * Under 4.2 BSD, it is impossible to reset the access and * modification times of a symbolic link, or to change its * mode with chmod. Both chmod and utimes go through the * symbolic link to the real file, which is not what we want. * Therefore, we add code for checking symbolic links. * */ /* * PSEUDO CODE * * Begin attributes * If the mode cannot be set then * Inform user that mode cannot be changed * End if * Change owner and group of the file * If the access and modification times cannot be set then * Inform the user about time error * End if * End attributes * */ static VOID attributes (fip) register struct finfo *fip; { register char *path; register int s_mode; register int s_owner; register int s_group; struct filetimes { /* 4.2 changed the stat buffer; st_atime */ time_t atime; /* is not contiguous to st_mtime, so we */ time_t mtime; /* build the structure explicitly before */ } t; /* handing it off to s_utime() */ DBUG_ENTER ("attributes"); path = fip -> fname; s_mode = fip -> statp -> st_mode; s_owner = fip -> statp -> st_uid; s_group = fip -> statp -> st_gid; if (s_owner != info.bru_uid && (info.bru_uid != 0 || flags.Cflag)) { if (s_mode & S_ISUID) { s_mode &= ~S_ISUID; bru_error (ERR_SUID, path); } } if (s_group != info.bru_gid && (info.bru_uid != 0 || flags.Cflag)) { if (s_mode & S_ISGID) { s_mode &= ~S_ISGID; bru_error (ERR_SGID, path); } } DBUG_PRINT ("attributes", ("file %s, owner %d, group %d", fip -> fname, s_owner, s_group)); DBUG_PRINT ("attributes", ("file %s, mode %#o", fip -> fname, s_mode)); if(!IS_FLNK (fip -> statp -> st_mode)) { t.atime = fip -> statp -> st_atime; t.mtime = fip -> statp -> st_mtime; /* do in this order so when on sysV systems, the time * and mode can be set correctly even if we are chown'ing * the file to some other user. */ if(s_utime (fip -> fname, &t) == SYS_ERROR) bru_error (ERR_STIMES, fip -> fname); if(s_chmod (path, s_mode) == SYS_ERROR) bru_error (ERR_SMODE, path); file_chown (path, s_owner, s_group); } if (fip -> fi_flags & FI_AMIGA) { (VOID) setinfo (fip); if (flags.Asflag) { mark_archived (fip); } else if (flags.Acflag) { clear_archived (fip); } } DBUG_VOID_RETURN; } /* * FUNCTION * * do_extract do the extraction * * SYNOPSIS * * * static VOID do_extract (blkp, fip) * register union blk *blkp; * register struct finfo *fip; * * DESCRIPTION * * Once all tests have been met and it has been decided to extract * the file, this routine is called to do the actual extraction. * At this point, we are committed to doing the extraction and * cannot be interrupted until done. * * Any missing parent directories will be created and then the * file itself will be extracted. * * For directories, there is an explicit call to set the attributes * since the makedir function cannot do this if the directory already * exists. Thus directories which do not exist will have there * attributes set when they are made by makedir, and again here. * It is the case of existing directories that we only catch here. * */ /* * PSEUDO CODE * * Begin do_extract * Save current signal processing stat * Set up to catch signals * Check for directory in which to extract file * If no parent directory then * Make a default stat buffer * Make the parent directory * End if * If parent directory and parent accessible then * If file to extract is a directory then * Make the directory * Set the directory attributes * Else if file linked to another then * Make the linkage * Else if not a regular file then * Make a special file * Else * Make the file * End if * End if * Pop the saved signal processing state * If interrupt came in while doing extraction * Cleanup and exit * End if * End do_extract * */ static VOID do_extract (blkp, fip) register union blk *blkp; register struct finfo *fip; { auto struct stat64 sbuf; register BOOLEAN got_parent; char *tmpname; SIGTYPE prevINT; SIGTYPE prevQUIT; DBUG_ENTER ("do_extract"); sig_push (&prevINT, &prevQUIT); sig_catch (); got_parent = dir_access (fip -> fname, A_EXISTS, FALSE); if (!got_parent) { makestat (&sbuf); got_parent = makeparent (&sbuf, fip -> fname); } if (got_parent) { if (IS_DIR (fip -> statp -> st_mode)) { (VOID) makedir (fip); attributes (fip); } else if (LINKED (blkp) && (!IS_FLNK (fip -> statp -> st_mode)) || fip->statp->st_nlink>1 && (tmpname=getlinkname(fip)) && strcmp(fip->fname, tmpname)) { /* make hard link if not a symlink, or a symlink with st_nlink>1 * and this is the 2nd thru nth instance seen of the hardlink * to the symlink during the backup. */ makelink (blkp, fip); } else if (!IS_REG (fip -> statp -> st_mode)) { /* * blkp and fip are passed down from scan, which will already * set up the link name in fip, so makespecial can just pass * it on if a symbolic link has to be made. In other words, * we don't need to do any special checking for that here. */ makespecial (fip); } else { makefile (blkp, fip); } } sig_pop (&prevINT, &prevQUIT); if (interrupt) { done (); } DBUG_VOID_RETURN; } /* * FUNCTION * * makeparent make a parent directory that does not exist * * SYNOPSIS * * static BOOLEAN makeparent (statp, name) * struct stat64 *statp; * char *name; * * DESCRIPTION * * Given pointer to a directory pathname in "name" and * pointer to stat buffer in "statp", attempts to make * the parent directory if one does not already exist. * * Note that the installation is recursive. That is, * if the parent of the parent does not exist, it is * made also, with the same attributes. * */ /* * PSEUDO CODE * * Begin makeparent * If parent already exists then * Result is TRUE * Else * Find where parent and child names join. * If no parent name * Tell user about bug * Result is FALSE * Else * Split parent and child names * Attempt to make grandparent recursively * If attempt successful then * Build a file info struct for parent * Make the parent * End if * Rejoin parent and child names * End if * End if * Return result * End makeparent * */ static BOOLEAN makeparent (statp, name) struct stat64 *statp; char *name; { register char *slash; register BOOLEAN got_dir; auto struct finfo pfile; DBUG_ENTER ("makeparent"); if (dir_access (name, A_EXISTS, FALSE)) { got_dir = TRUE; } else { slash = s_strrchr (name, '/'); if (slash == NULL) { bru_error (ERR_BUG, "makeparent"); got_dir = FALSE; } else { if(slash != name) { *slash = EOS; if(got_dir = makeparent (statp, name)) { finfo_init (&pfile, name, statp); got_dir = makedir (&pfile); } *slash = '/'; } else /* makedir called directly later if it's a dir; if it isn't, we definitely do not want to mkdir! */ got_dir = 1; /* but makeparent still has to return true */ } } DBUG_RETURN (got_dir); } /* * FUNCTION * * makedir make a directory * * SYNOPSIS * * static BOOLEAN makedir (fip) * register struct finfo *fip; * * DESCRIPTION * * Attempts to make a directory if one does not already exist. * * Note that it is assumed that the parent has already * been made by a call to makeparent. If not, then * makedir will fail if no parent exists. Also, we need to * verify that the user running bru has appropriate permission * to write into the parent of the directory to be made, rather * than depending upon the newdir/mkdir calls to enforce * permissions. * * Also note that the attributes are set ONLY if the directory * is made. This is because makedir may be called to ensure * that parent directories exist in order to extract an archived * directory. If the attributes were unconditionally set, * parents would take on the attributes of the child directory * being extracted from the archive. The actual directory * being extracted has it attributes explicitly set elsewhere. * */ /* * PSEUDO CODE * * Begin makedir * If the directory already exists then * Result is TRUE * Else if we can write in parent to make directory then * Attempt to make the directory * If directory node successfully made then * Set attributes of directory * End if * Else * Doesn't exist and can't make it, result is FALSE * End if * Return result * End makedir * */ static BOOLEAN makedir (fip) register struct finfo *fip; { register BOOLEAN result; DBUG_ENTER ("makedir"); if (file_access (fip -> fname, A_EXISTS, FALSE)) { result = TRUE; } else { /* idiotic program; don't check permissions, just * try to make the directory. */ result = newdir (fip); if (result) { attributes (fip); } } DBUG_RETURN (result); } /* * FUNCTION * * makestat make a default stat buffer * * SYNOPSIS * * static VOID makestat (statp) * struct stat64 *statp; * * DESCRIPTION * * Makes a default stat buffer as appropriate for current user. * This is typically used to set attributes of directories which * must be made to install a file being extracted. * */ /* * PSEUDO CODE * * Begin makestat * Get current time * Get the mode word mask * Restore the mode word mask * Get the file mode and make it a directory * Initialize the stat buffer mode * Initialize the user id * Initialize the group id * Initialize the access time * Initialize the modification time * End makestat * */ static VOID makestat (statp) struct stat64 *statp; { register int mask; register time_t now; DBUG_ENTER ("makestat"); now = (time_t) s_time ((long *) 0); mask = s_umask (0); (VOID) s_umask (mask); statp -> st_mode = (ushort) (S_IFDIR | (DIR_MAGIC & ~mask)); statp -> st_uid = info.bru_uid; statp -> st_gid = info.bru_gid; statp -> st_atime = now; statp -> st_mtime = now; DBUG_VOID_RETURN; }