summaryrefslogblamecommitdiffstats
path: root/lib/backup-files.c
blob: 43357f0bac843ba4864304dd67b33e8b5a2f3afe (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                   
 


                                                                            


  



                                                                       
 

                   



                    

                      
                  


                   
                   

                   
                
 



                                                  

                     
                                                           
 
                                                     
                                   
                           



                        


           
                                                                                                    



                                                                        
                                      



                                                                         
                                                                    

                                                                                         
                                                                                   
 
                                                                                            


                         










                                                          
    
                                    

                       
                    
                                                           

                             







                               
 

                            
                          

                                                       
                 

                                     
         
                 


    
                                    
 
                                                           
 
                             
 










                                                 

 
          
                            

                          

                   











                                                                   











                                                                         

                                                                       



                               
                        
                                          


                                      

                                                                             











                           


















                                                                         
 







                                                              
                                                 
                         
 




                                         




                                          














                                              
   
                              
 

                                                                            
 
                                                                

                                      
                               
                                                                              

                               
                                       
                                   

                               

                                                              

                                                                       
                                               
                                          

                                  
                        

                                                             
                                                            
                                          

                                                    

                                                         
                                                  
                         
                 

                                              
 
                                     

                                             
                                  




                                                                      

                                             
                                          

                         

                                                               
                                     
                                                            
                                          

                                                  

                                                         
                                                  
                         
                 

                                       


                                             





                                                                              
                                          
                 
              







                          
 
 
   
                                                         



                                                                            
                











                                                                            
                                                             

                                                                    


                              


   

                            
                          
 

                           
                                                                   
                             



                                                       










                                                        
 











                                                    



                                                



                                              






                                         

                                                           




                               

                                       
 




                                                                    

                                         
                 
 

                                                         
 


                                                       
                                         
 

                                                               
                 
 




                                         





                                                                               
                                                   




                                                                      



                                                            
                                      



                      
/*
  File: backup-files.c

  Copyright (C) 2003 Andreas Gruenbacher <agruen@suse.de>
  SuSE Labs, SuSE Linux AG

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

/*
 * Create backup files of a list of files similar to GNU patch. A path
 * name prefix and suffix for the backup file can be specified with the
 * -B and -Z options.
 */

#define _GNU_SOURCE

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ftw.h>

#if !defined(HAVE_MKSTEMP) && defined(HAVE_MKTEMP)
# define mkstemp(x) creat(mktemp(x), 0600)
#endif

const char *progname;

enum { what_noop, what_backup, what_restore, what_remove };

const char *opt_prefix="", *opt_suffix="", *opt_file;
int opt_silent, opt_what=what_noop;
int opt_nolinks, opt_touch;

#define LINE_LENGTH 1024


void
usage(void)
{
	printf("Usage: %s [-B prefix] [-z suffix] [-f {file|-}] [-s] [-b|-r|-x] [-L] {file|-} ...\n"
	       "\n"
	       "\tCreate hard linked backup copies of a list of files\n"
	       "\tread from standard input.\n"
	       "\n"
	       "\t-b\tCreate backup\n"
	       "\t-r\tRestore the backup\n"
	       "\t-x\tRemove backup files and empty parent directories\n"
	       "\t-B\tPath name prefix for backup files\n"
	       "\t-z\tPath name suffix for backup files\n"
	       "\t-s\tSilent operation; only print error messages\n"
	       "\t-L\tEnsure that when finished, the source file has a link count of 1\n"
	       "\t-f\tRead the filenames to process from file (- = standard input)\n"
	       "\t-t\tTouch original files after restore (update their mtimes)\n\n"

	       "\t-L\tEnsure that when finished, the source file has a link count of 1\n\n",
	       progname);
}

static void *
malloc_nofail(size_t size)
{
	void *p = malloc(size);
	if (!p) {
		fprintf(stderr, "%s\n", strerror(ENOMEM));
		exit(1);
	}
	return p;
}

void
create_parents(const char *filename)
{
	struct stat st;
	int rv = -1;
	char *fn = malloc_nofail(strlen(filename) + 1), *f;

	strcpy(fn, filename);
	
	f = strrchr(fn, '/');
	if (f == NULL)
		return;
	*f = '\0';
	if (stat(fn, &st) == 0)
		return;
	*f = '/';

	f = strchr(fn, '/');
	while (f != NULL) {
		*f = '\0';
		if (!rv || (rv = stat(fn, &st)) != 0) {
			mkdir(fn, 0777);
		}
		*f = '/';
		f = strchr(f+1, '/');
	}
	free(fn);
}

void
remove_parents(const char *filename)
{
	char *fn = malloc_nofail(strlen(filename) + 1), *f;

	strcpy(fn, filename);

	f = strrchr(fn, '/');
	if (f == NULL)
		return;
	do {
		*f = '\0';
		if (rmdir(fn) == -1)
			goto out;
	} while ((f = strrchr(fn, '/')) != NULL);
	rmdir(fn);
out:
	free(fn);
}

static int
copy(int from_fd, int to_fd)
{
	char buffer[4096];
	size_t len;

	while ((len = read(from_fd, buffer, sizeof(buffer))) > 0) {
		if ((write(to_fd, buffer, len)) == -1)
			return 1;
	}
	return (len != 0);
}

static int
link_or_copy(const char *from, struct stat *st, const char *to)
{
	int from_fd, to_fd, error = 1;

	if (link(from, to) == 0)
		return 0;
	if (errno != EXDEV && errno != EPERM && errno != EMLINK) {
		fprintf(stderr, "Could not link file `%s' to `%s': %s\n",
		       from, to, strerror(errno));
		return 1;
	}

	if ((from_fd = open(from, O_RDONLY)) == -1) {
		perror(from);
		return 1;
	}
	unlink(to);  /* make sure we don't inherit this file's mode. */
	if ((to_fd = creat(to, st->st_mode))) {
		perror(to);
		close(from_fd);
		return 1;
	}
#if defined(HAVE_FCHMOD)
	(void) fchmod(to_fd, st->st_mode);
#elif defined(HAVE_CHMOD)
	(void) chmod(to, st->st_mode);
#endif
	if (copy(from_fd, to_fd)) {
		fprintf(stderr, "%s -> %s: %s\n", from, to, strerror(errno));
		unlink(to);
		goto out;
	}

	error = 0;
out:
	close(from_fd);
	close(to_fd);

	return error;
}

static int
ensure_nolinks(const char *filename)
{
	struct stat st;

	if (stat(filename, &st) != 0) {
		perror(filename);
		return 1;
	}
	if (st.st_nlink > 1) {
		char *tmpname = malloc(1 + strlen(filename) + 7 + 1), *c;
		int from_fd = -1, to_fd = -1;
		int error = 1;

		if (!tmpname)
			goto fail;
		from_fd = open(filename, O_RDONLY);
		if (from_fd == -1)
			goto fail;

		/* Temp file name is "path/to/.file.XXXXXX" */
		strcpy(tmpname, filename);
		strcat(tmpname, ".XXXXXX");
		c = strrchr(tmpname, '/');
		if (c == NULL)
			c = tmpname;
		else
			c++;
		memmove(c + 1, c, strlen(c) + 1);
		*c = '.';

		to_fd = mkstemp(tmpname);
		if (to_fd == -1)
			goto fail;
		if (copy(from_fd, to_fd))
			goto fail;
#if defined(HAVE_FCHMOD)
	(void) fchmod(to_fd, st.st_mode);
#elif defined(HAVE_CHMOD)
	(void) chmod(tmpname, st.st_mode);
#endif
		if (rename(tmpname, filename))
			goto fail;

		error = 0;
	fail:
		if (error)
			perror(filename);
		free(tmpname);
		close(from_fd);
		close(to_fd);
		return error;
	} else
		return 0;
}

int
process_file(const char *file)
{
	char *backup = malloc_nofail(
		strlen(opt_prefix) + strlen(file) + strlen(opt_suffix) + 1);

	sprintf(backup, "%s%s%s", opt_prefix, file, opt_suffix);

	if (opt_what == what_backup) {
		struct stat st;
		int missing_file = (stat(file, &st) == -1 && errno == ENOENT);

		unlink(backup);
		create_parents(backup);
		if (missing_file) {
			int fd;

			if (!opt_silent)
				printf("New file %s\n", file);
			/* GNU patch creates new files with mode==0. */
			if ((fd = creat(backup, 0)) == -1) {
				perror(backup);
				goto fail;
			}
			close(fd);
		} else {
			if (!opt_silent)
				printf("Copying %s\n", file);
			if (link_or_copy(file, &st, backup))
				goto fail;
			if (opt_touch)
				utime(backup, NULL);
			if (opt_nolinks) {
				if (ensure_nolinks(file))
					goto fail;
			}
		}
	} else if (opt_what == what_restore) {
		struct stat st;

		create_parents(file);
		if (stat(backup, &st) != 0) {
			perror(backup);
			goto fail;
		}
		if (st.st_size == 0) {
			if (unlink(file) == 0 || errno == ENOENT) {
				if (!opt_silent)
					printf("Removing %s\n", file);
			} else {
				perror(file);
				goto fail;
			}
		} else {
			if (!opt_silent)
				printf("Restoring %s\n", file);
			unlink(file);
			if (link_or_copy(backup, &st, file))
				goto fail;
			if (opt_touch)
				utime(file, NULL);
			if (opt_nolinks) {
				if (ensure_nolinks(file))
					goto fail;
			}
		}
		unlink(backup);
		remove_parents(backup);
	} else if (opt_what == what_remove) {
		unlink(backup);
		remove_parents(backup);
	} else if (opt_what == what_noop) {
		struct stat st;
		int missing_file = (stat(file, &st) == -1 && errno == ENOENT);

		if (!missing_file && opt_nolinks) {
			if (ensure_nolinks(file))
				goto fail;
		}
	} else
		goto fail;

	free(backup);
	return 0;

fail:
	free(backup);
	return 1;
}

int
walk(const char *path, const struct stat *stat, int flag)
{
	size_t prefix_len=strlen(opt_prefix), suffix_len=strlen(opt_suffix);
	size_t len = strlen(path);
	char *p;
	int ret;

	if (flag == FTW_DNR) {
		perror(path);
		return 1;
	}
	if (!S_ISREG(stat->st_mode))
		return 0;
	if (strncmp(opt_prefix, path, prefix_len))
		return 0;  /* prefix does not match */
	if (len < suffix_len || strcmp(opt_suffix, path + len - suffix_len))
		return 0;  /* suffix does not match */

	p = malloc_nofail(len - prefix_len - suffix_len + 1);
	memcpy(p, path + prefix_len, len - prefix_len - suffix_len);
	p[len - prefix_len - suffix_len] = '\0';
	ret = process_file(p);
	free(p);
	return ret;
}

int
main(int argc, char *argv[])
{
	int opt, status=0;

	progname = argv[0];

	while ((opt = getopt(argc, argv, "brxB:z:f:shLt")) != -1) {
		switch(opt) {
			case 'b':
				opt_what = what_backup;
				break;

			case 'r':
				opt_what = what_restore;
				break;

			case 'x':
				opt_what = what_remove;
				break;

			case 'B':
				opt_prefix = optarg;
				break;

			case 'f':
				opt_file = optarg;
				break;

			case 'z':
				opt_suffix = optarg;
				break;

			case 's':
				opt_silent = 1;
				break;

			case 'L':
				opt_nolinks = 1;
				break;

			case 't':
				opt_touch = 1;
				break;

			case 'h':
			default:
				usage();
				return 0;
		}
	}

	if ((*opt_prefix == '\0' && *opt_suffix == '\0') ||
	    (opt_file == NULL && optind == argc)) {
		usage();
		return 1;
	}

	if (opt_file != NULL) {
		FILE *file;
		char line[LINE_LENGTH];

		if (!strcmp(opt_file, "-")) {
			file = stdin;
		} else {
			if ((file = fopen(opt_file, "r")) == NULL) {
				perror(opt_file);
				return 1;
			}
		}

		while (fgets(line, sizeof(line), file)) {
			char *l = strchr(line, '\0');

			if (l > line && *(l-1) == '\n')
				*(l-1) = '\0';
			if (*line == '\0')
				continue;

			if ((status = process_file(line)) != 0)
				return status;
		}

		if (file != stdin) {
			fclose(file);
		}
	}
	for (; optind < argc; optind++) {
		if (strcmp(argv[optind], "-") == 0) {
			char *dir = strdup(opt_prefix), *d = strrchr(dir, '/');
			if (d)
				*(d+1) = '\0';
			else
				d = ".";
			status = ftw(dir, walk, 0);
			/* An error here indicates a problem somewhere
			 *  during the walk */
			if (status == -1)
				perror("ftw");

			free(dir);
		} else
			status = process_file(argv[optind]);
		if (status)
			return status;
	}

	return status;
}