Logo Search packages:      
Sourcecode: faubackup version File versions  Download package

faubackup-scatter.c

/*
 *  FauBackup - Backup System, using a Filesystem for Storage
 *  Copyright (C) 2000-01 Dr. Volkmar Sieh, 2000-06 Martin Waitz
 *  $Id$
 *
 *  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 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#include <popt.h>

#include "faubackup.h"


#define MAXDATE 256

int error;
int verbose;
const char* progname;
static char lastdate[MAXDATE];


static int can_set_owner = 1;


static int
recv_buf(void *buf, size_t len, int mayfail)
{
      unsigned char *b;
      size_t ret;

      b = buf;
      while( len>0 ) {
            ret = fread(b, 1, len, stdin);
            if( ferror(stdin) ) {
                  fprintf(stderr, "%s: fread stdin: %s\n",
                              progname, strerror(errno));
                  break;
            }
            if( feof(stdin) ) break;
            b += ret;
            len -= ret;
      }

      if( len!=0 && ! mayfail ) {
            fprintf(stderr, "%s: protocol error (%d bytes missing)\n",
                        progname, len);
            exit(1);
      }

      return( b - (unsigned char*)buf );
}

static char*
recv_string()
{
      unsigned int tmp;
      char* str;

      recv_buf( &tmp, sizeof(tmp), 0 );
      tmp = ntohl(tmp);

      str = malloc( tmp+1 );

      recv_buf(str, tmp, 0);
      str[tmp] = '\0';

      return str;
}

static struct stat*
recv_inode()
{
      static struct stat st;
      unsigned int tmp;
      int size64=0;

      /*
       * recv contents of inode
       */
      if( recv_buf( &tmp, sizeof(tmp), 1 ) != sizeof(tmp) ) return (NULL);
      st.st_dev = ntohl(tmp);

      recv_buf( &tmp, sizeof(tmp), 0 );
      st.st_ino = ntohl(tmp);

      recv_buf( &tmp, sizeof(tmp), 0 );
      tmp = ntohl(tmp);

      if(tmp & B_IFSIZE64) {
            size64 = 1;
            tmp &= ~B_IFSIZE64;
      }
      st.st_mode = tmp & 07777;
      switch (tmp & ~07777) {
      case B_IFBLK:     st.st_mode |= S_IFBLK; break;
      case B_IFCHR:     st.st_mode |= S_IFCHR; break;
      case B_IFDIR:     st.st_mode |= S_IFDIR; break;
      case B_IFIFO:     st.st_mode |= S_IFIFO; break;
      case B_IFLNK:     st.st_mode |= S_IFLNK; break;
      case B_IFREG:     st.st_mode |= S_IFREG; break;
      case B_IFSOCK:    st.st_mode |= S_IFSOCK; break;
      default:
            fprintf( stderr, "%s: protocol error: mode 0%o\n",
                        progname, tmp );
            exit(1);
      }
      recv_buf( &tmp, sizeof(tmp), 0 ); st.st_uid = ntohl(tmp);
      recv_buf( &tmp, sizeof(tmp), 0 ); st.st_gid = ntohl(tmp);
      recv_buf( &tmp, sizeof(tmp), 0 ); st.st_rdev = ntohl(tmp);
      recv_buf( &tmp, sizeof(tmp), 0 ); st.st_size = ntohl(tmp);
      if(size64) {
            recv_buf( &tmp, sizeof(tmp), 0 );
            st.st_size += (off_t) ntohl(tmp)<<32;
      }
      /*recv_buf( &tmp, sizeof(tmp), 0 ); st.st_atime = ntohl(tmp);*/
      recv_buf( &tmp, sizeof(tmp), 0 ); st.st_mtime = ntohl(tmp);

      return( &st );
}


static int
make_path(char *path)
{
      char *dir;
      char *d,*p;
      int ret;

      dir = malloc( strlen(path)+1 );
      strcpy(dir, "");
      d=dir; p=path;

      while( 1 ) {
            /* copy one directory entry to dir */
            while( *p!='\0' && *p!='/' ) *d++ = *p++;
            if( *p=='\0' ) break;
            *d='\0';
            /* create directory */
            ret = mkdir(dir, 0700);
            if( ret<0 && errno!=EEXIST ) {
                  fprintf( stderr, "%s: mkdir \"%s\": %s\n",
                              progname, dir, strerror(errno) );
                  return( ret );
            }
            /* goto next part of path */
            *d++='/'; p++;
      }
      free( dir );

      return(0);
}

static void
dir_getlast()
{
      DIR *d;
      struct dirent *de;
      struct stat st;
      time_t lastt;
      int ret;

      d = opendir(".");
      if( d == (DIR *) 0 ) {
            char cwd[MAXLEN];
            fprintf(stderr, "%s: opendir \"%s\": %s\n",
                        progname, getcwd(cwd, MAXLEN),
                        strerror(errno));
            exit(1);
      }
      lastt = 0;
      while( (de = readdir(d)) != (struct dirent *) 0 ) {
            char *p;

            if( strcmp(de->d_name, ".") == 0
             || strcmp(de->d_name, "..") == 0 ) {
                  continue;
            }

            p = de->d_name;
            (void) strtol(p, &p, 10);
            if( *p != '-') continue;
            p++;
            (void) strtol(p, &p, 10);
            if( *p != '-') continue;
            p++;
            (void) strtol(p, &p, 10);
            if( *p != '@') continue;
            p++;
            (void) strtol(p, &p, 10);
            if( *p != ':') continue;
            p++;
            (void) strtol(p, &p, 10);
            if( *p != ':') continue;
            p++;
            (void) strtol(p, &p, 10);
            if( *p != '\0') continue;

            ret = lstat( de->d_name, &st );
            if( ret < 0 ) {
                  char cwd[MAXLEN];
                  fprintf(stderr, "%s: lstat %s/%s: %s\n",
                              progname, getcwd(cwd, MAXLEN),
                              de->d_name, strerror(errno));
                  exit(1);
            }

            if( ! S_ISDIR(st.st_mode) ) {
                  continue;
            }

            if( lastt < st.st_mtime ) {
                  lastt = st.st_mtime;
                  strncpy( lastdate, de->d_name, MAXDATE );
            }
      }
      closedir(d);

      if( lastt == 0 ) {
            strcpy( lastdate, "1970-01-01@00:00:00" );
      }

      if( verbose ) {
            printf( "date of last backup: %s\n", lastdate );
      }
}

/*
 * create a temporary directory for the backup and chdir into it
 * will return the directories name
 */
static char *
dir_create()
{
      static char workdir[MAXLEN];
      int ret;

      snprintf( workdir, MAXDATE, "working-%d", getpid() );
      ret = mkdir( workdir, 0700 );
      if( ret<0 ) {
            char cwd[MAXLEN];
            fprintf( stderr, "%s: mkdir \"%s/%s\": %s\n",
                        progname, getcwd(cwd, MAXLEN),
                        workdir, strerror(errno) );
            exit( 1 );
      }

      ret = chdir( workdir );
      if( ret<0 ) {
            char cwd[MAXLEN];
            fprintf( stderr, "%s: chdir \"%s/%s\": %s\n",
                        progname, getcwd(cwd, MAXLEN),
                        workdir, strerror(errno) );
            exit( 1 );
      }

      return( workdir );
}

/*
 * change name and time of the temporary directory to match the current time
 */
static int
dir_finalize(char *workdir)
{
      char newdir[MAXDATE];
      time_t thist;
      struct tm *tm;
      struct utimbuf u;
      int ret;

      time(&thist);
      tm = localtime(&thist);

      time(&thist);
      tm = localtime(&thist);
      u.actime = thist;
      u.modtime = thist;
      ret = utime( ".", &u );
      if( ret < 0 ) {
            char cwd[MAXLEN];
            fprintf( stderr, "%s: utime %s: %s\n",
                        progname, getcwd(cwd, MAXLEN),
                        strerror(errno) );
            error++;
      }

      ret = chdir("..");
      if( ret<0 ) {
            fprintf( stderr, "%s: chdir \"..\": %s\n",
                        progname, strerror(errno) );
            return( ret );
      }
      snprintf( newdir, MAXDATE, "%04d-%02d-%02d@%02d:%02d:%02d",
                  tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                  tm->tm_hour, tm->tm_min, tm->tm_sec );
      if( error>0 ) {
            strncat( newdir, "-broken", MAXDATE );
      }
      
      ret = rename( workdir, newdir );
      if( ret<0 ) {
            char cwd[MAXLEN];
            fprintf( stderr, "%s: rename \"%s\" to \"%s\" in %s: %s\n",
                        progname, workdir, newdir,
                        getcwd(cwd, MAXLEN), strerror(errno) );
            return( ret );
      }

      return( 0 );
}


static char *
devinopath(dev_t dev, ino_t ino)
{
      static char path[MAXLEN];

      snprintf( path, MAXLEN, "..inodes/%08x/%02x/%02x/%02x/%02x",
                  (int) dev,
                  ((int) ino >> 24) & 0xff,
                  ((int) ino >> 16) & 0xff,
                  ((int) ino >>  8) & 0xff,
                  ((int) ino >>  0) & 0xff );
      return( path );
}


static char *
olddevinopath(dev_t dev, ino_t ino)
{
      static char path[MAXLEN];

      snprintf( path, MAXLEN, "../%s/..inodes/%08x/%02x/%02x/%02x/%02x",
                  lastdate, (int) dev,
                  ((int) ino >> 24) & 0xff,
                  ((int) ino >> 16) & 0xff,
                  ((int) ino >>  8) & 0xff,
                  ((int) ino >>  0) & 0xff );
      return( path );
}

static void
cannot_set_owner(void)
{
      if( !can_set_owner ) {
            /* only print warning once */
            return;
      }
      fprintf( stderr, "%s: WARNING: cannot set ownership of files\n",
                  progname );
      can_set_owner = 0;
}

#define SET_OWNER (1)
#define SET_MODE  (2)
#define SET_TIME  (4)
#define SET_LINK_OWNER  (8)
#define SET_ALL         (SET_OWNER | SET_MODE | SET_TIME)

/*
 * Set ownership, access mode and timestamps
 */
static void
set_perms(char *path, struct stat *st, int flags)
{
      mode_t mode;
      struct utimbuf u;
      int ret;

      mode = st->st_mode & 07777;

      if( flags & SET_TIME ) {
            u.actime = st->st_atime;
            u.modtime = st->st_mtime;
            ret = utime( path, &u );
            if( ret < 0 ) {
                  fprintf( stderr, "%s: utime %s: %s\n",
                              progname, path, strerror(errno) );
                  error++;
            }
      }

      /* set owner */
      if( flags & SET_OWNER ) {
#ifdef HAVE_LCHOWN
            ret = lchown(path, st->st_uid, st->st_gid);
#else
            if( flags & SET_LINK_OWNER ) {
                  ret = 0; /* simply ignore if it's not supported */
            } else {
                  ret = chown(path, st->st_uid, st->st_gid);
            }
#endif
            /* unset suid/sgid bits if chown fails */
            if( ret < 0 && errno == EPERM ) {
                  cannot_set_owner();
                  mode &= ~06000;
            } else if( ret < 0 ) {
                  fprintf( stderr, "%s: lchown %s, %d:%d: %s\n",
                              progname, path, (int) st->st_uid,
                              (int) st->st_gid, strerror(errno) );
                  error++;
                  mode &= ~06000;
            }
      }

      if( flags & SET_MODE ) {
            ret = chmod(path, mode);
            if( ret < 0 ) {
                  fprintf( stderr, "%s: chmod %s, %03o: %s\n",
                              progname, path, (int) mode,
                              strerror(errno) );
                  error++;
            }
      }
}

static void
scatter_blk(char *path, struct stat *st)
{
      int ret;

      assert( S_ISBLK(st->st_mode) );

      /*
       * create node
       */
      ret = mknod( path, S_IFBLK, st->st_rdev );
      if( ret < 0 ) {
            fprintf( stderr, "%s: mknod %s, S_IFBLK, 0x%x: %s\n",
                        progname, path,
                        (int) st->st_rdev, strerror(errno) );
            if( errno==ENOSPC ) exit(1);
            error++;
            return;
      }

      set_perms( path, st, SET_ALL );
}


static void
scatter_chr(char *path, struct stat *st)
{
      int ret;

      assert( S_ISCHR(st->st_mode) );

      /*
       * create node
       */
      ret = mknod( path, S_IFCHR, st->st_rdev );
      if( ret < 0 ) {
            fprintf(stderr, "%s: mknod %s, S_IFCHR, 0x%x: %s\n",
                        progname, path,
                        (int) st->st_rdev, strerror(errno) );
            if( errno==ENOSPC ) exit(1);
            error++;
            return;
      }

      set_perms( path, st, SET_ALL );
}


static void
scatter_reg(char *path, struct stat *st)
{
      char *targetname;
      char *oldname;
      FILE *target, *old;
      struct stat oldst;
      int equal;
      off_t pos;
      int ret;


      assert( S_ISREG(st->st_mode) );

      oldname = olddevinopath( st->st_dev, st->st_ino );
      targetname = devinopath( st->st_dev, st->st_ino );
      make_path( targetname );

      equal = 1;

      /*
       * compare file with file saved last time
       */

      ret = lstat( oldname, &oldst );
      if( ret < 0 && errno == ENOENT ) {
            equal = 0;
      } else if( ret < 0 ) {
            fprintf( stderr, "%s: lstat \"%s\": %s\n",
                        progname, oldname, strerror(errno) );
            error++;
            equal = 0;
      }

      /* do _not_ check atime/ctime; will be changed by backup itself */
      if( st->st_mode != oldst.st_mode
       || st->st_size != oldst.st_size
       || st->st_mtime != oldst.st_mtime ) {
            /* different inode => use new one */
            equal = 0;
      }
      
      /* ================================================= */
      /* copy file to local disk and compare with old file */
      /* ================================================= */

      target = fopen( targetname, "w");
      if( target==NULL ) {
            if( errno!=EEXIST ) {
                  fprintf( stderr, "%s: fopen \"%s\": %s\n",
                              progname, targetname, strerror(errno) );
                  if( errno==ENOSPC ) exit(1);
                  error++;
            }
            equal = 0;
            /* can't write this file, but go on reading it,
             * to be able to handle the following files
             */
            target = fopen( "/dev/null", "w" );
            if( target==NULL ) {
                  fprintf( stderr, "%s: fopen \"/dev/null\": %s\n",
                              progname, strerror(errno) );
                  exit( 1 );
            }
      }
      if( equal ) {
            old = fopen( oldname, "r" );
            if( old==NULL && errno!=ENOENT ) {
                  /*
                   * problem with Solaris:
                   * Solaris will report "Permission denied"
                   * with "-rw--l---" files even with "no_root_squash"
                   */
                  fprintf( stderr, "%s: WARNING: fopen %s: %s\n",
                              progname, oldname, strerror(errno) );
                  error++;
            }
            if( old==NULL ) {
                  equal = 0;
            }
      } else {
            old=NULL;
      }

      for( pos = 0; pos < st->st_size; ) {
            const static char zero[4096] = { 0, 0, /* ... */ };
            char buf[4096];
            char obuf[4096];
            size_t len;

            if( st->st_size - pos < (off_t)sizeof(buf) ) {
                  len = st->st_size - pos;
            } else {
                  len = sizeof(buf);
            }
            len = recv_buf( buf, len, 0 );

            /* compare files */
            if( equal ) {
                  size_t n_read = fread( obuf, 1, len, old );
                  if( n_read!=len || ferror(old) ||
                                    memcmp(buf, obuf, len)!=0 ) {
                        equal = 0;
                  }
            }
            if( len==sizeof(zero) && pos + len != st->st_size
                        && memcmp(buf, zero, len) == 0 ) {

                  /* found an empty page, create hole in target file */
                  ret = fseek( target, len, SEEK_CUR );
                  if( ret < 0 ) {
                        fprintf( stderr, "%s: fseek \"%s\", %+zd: %s\n",
                                    progname, targetname,
                                    len, strerror(errno) );
                        error++;
                  }
            } else {
                  fwrite( buf, 1, len, target );
                  if( ferror(target) ) {
                        fprintf( stderr, "%s: fwrite \"%s\": %s\n",
                                    progname, targetname,
                                    strerror(errno) );
                        /* fatal error on 'no space left on device' */
                        if( errno==ENOSPC ) exit( 1 );
                        error++;
                  }
            }
            pos += len;
      }

      if( old ) {
            ret = fclose( old );
            if( ret < 0 ) {
                  fprintf( stderr, "%s: fclose \"%s\": %s\n",
                              progname, oldname, strerror(errno) );
                  error++;
            }
      }
      ret = fclose(target);
      if( ret < 0 ) {
            fprintf( stderr, "%s: fclose \"%s\": %s\n",
                        progname, targetname, strerror(errno) );
            error++;
      }

      set_perms( targetname, st, SET_ALL );

      /*
       * check difference in uid/gid only after we tried to set
       * them, as we must not check for them when we are unable
       * to change ownership, which may happen when we are not
       * called by root
       */
      if( st->st_uid != oldst.st_uid || st->st_gid != oldst.st_gid ) {
            if( can_set_owner ) equal = 0;
      }

      if( equal ) {
            ret = unlink(targetname);
            if( ret < 0 ) {
                  fprintf( stderr, "%s: unlink \"%s\": %s\n",
                              progname, targetname, strerror(errno) );
                  error++;
            } else {
                  ret = link( oldname, targetname );
                  if( ret < 0 ) {
                        fprintf( stderr, "%s: link \"%s\", \"%s\": %s\n",
                                    progname, oldname, targetname, strerror(errno) );
                        error++;
                  }
            }
      }

      /*
       * link file into directory
       */
      ret = link( targetname, path );
      if( ret < 0 ) {
            fprintf( stderr, "%s: link \"%s\", \"%s\": %s\n",
                        progname, targetname, path, strerror(errno) );
            error++;
      }
}


static void
scatter_lnk(char *path, struct stat *st)
{
      char *link;
      int ret;

      assert( S_ISLNK(st->st_mode) );

      /*
       * read link
       */
      link = recv_string();

      /*
       * create link
       */
      ret = symlink( link, path );
      if( ret < 0 ) {
            fprintf( stderr, "%s: symlink \"%s\", \"%s\": %s\n",
                        progname, link, path, strerror(errno) );
            if( errno==ENOSPC ) exit(1);
            error++;
            return;
      }

      free( link );

      set_perms( path, st, SET_OWNER | SET_LINK_OWNER);
}


static void
scatter_dir(char *path, struct stat *st)
{
      int ret;

      assert( S_ISDIR(st->st_mode) );

      /*
       * generate new directory
       */
      ret = mkdir( path, 0700 );
      if( ret < 0 && errno!=EEXIST ) {
            fprintf( stderr, "%s: mkdir(%s, 0700): %s\n",
                        progname, path, strerror(errno) );
            if( errno==ENOSPC ) exit(1);
            error++;
            return;
      }

      set_perms( path, st, SET_ALL );
}


static struct poptOption options[] = {
      {
            .longName="verbose", .shortName='v',
            .argInfo=POPT_ARG_NONE, .arg=&verbose,
            .descrip = "Print verbose messages"
      }, {
            .longName="version",
            .val = 'V',
            .descrip = "Print program version"
      },
      POPT_AUTOHELP
      {}
};

int
main(int argc, const char* argv[])
{
      char *path, *workdir;
      struct stat *st;
      int ret;
      poptContext popt;
      int rc;

      error=0;
      progname=argv[0];

      /* argument parsing */
      popt = poptGetContext("faubackup", argc, argv, options, 0);
      while( (rc = poptGetNextOpt(popt)) > 0 ) {
            switch (rc) {
            case 'V':
                  printf("%s\n", PACKAGE_STRING);
                  exit(0);
            }
      }
      if(rc<-1) {
            poptPrintUsage(popt, stderr, 0);
            fprintf(stderr, "%s: %s\n",
                        poptBadOption(popt, 0), poptStrerror(rc));
            exit(1);
      }

      if( poptPeekArg(popt) ) {
            poptPrintUsage(popt, stderr, 0);
            fprintf(stderr, "unknown parameter\n");
            exit(1);
      }

      /* get date of last backup */
      dir_getlast();

      /* restrict access to new files/dirs while they are created */
      ret = umask(0077);
      if( ret < 0 ) {
            fprintf( stderr, "%s: umask(0): %s\n",
                        progname, strerror(errno) );
            exit( 1 );
      }

      /* ========= */
      /* do backup */
      /* ========= */

      workdir = dir_create();

      for(st=recv_inode(); st; st=recv_inode()) {
            path=recv_string();
            if( verbose ) {
                  printf( "%s\n", path );
            }
            if( path[0]=='/' || strncmp(path, "../", 3)==0
                        || strstr( path, "/../" ) ) {
                  fprintf( stderr, "%s: %s: illegal path name, aborting\n",
                        progname, path );
                  exit( 1 );
            }
            make_path( path );
            if( S_ISBLK(st->st_mode) ) {
                  scatter_blk( path, st );
            } else if( S_ISCHR(st->st_mode) ) {
                  scatter_chr( path, st );
            } else if( S_ISREG(st->st_mode) ) {
                  scatter_reg( path, st );
            } else if( S_ISLNK(st->st_mode) ) {
                  scatter_lnk( path, st );
            } else if( S_ISDIR(st->st_mode) ) {
                  scatter_dir( path, st );
            } else if( S_ISFIFO(st->st_mode)
                  || S_ISSOCK(st->st_mode)) {
                  /* shouldn't be gathered */
                  assert(0);
                  abort();
            } else {
                  fprintf( stderr, "%s: %s: unknown mode 0%o\n",
                              progname, path, (int) st->st_mode );
            }
            
            ret = strcmp(path, ".");
            free( path );
      }

      /* check that backup was complete */
      if( ret!=0 ) {
            fprintf( stderr, "backup not complete! '.' should be last.\n");
            error++;
      }
      /* rename backup dir to commit our work */
      dir_finalize( workdir );

      return( error!=0 );
}

Generated by  Doxygen 1.6.0   Back to index