Example #1
0
 def execute_user_command(self, cmd_type, command):
   """Run the after user command"""
   # Before or after command?
   if cmd_type == 1:
     cmd_type_str = 'Before'
   elif cmd_type == 2:
     cmd_type_str = 'After'
   else:
     raise ValueError('Unknown command type value "%s"' % cmd_type)
   # Execute the command
   self.logger.logmsg('INFO', _("Executing '%s' command") % cmd_type_str)
   sub = fwbackups.executeSub(command, env=self.environment, shell=True)
   self.pids.append(sub.pid)
   self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid)
   # track stdout
   errors = []
   # use nonblocking I/O
   fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL)
   fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK)
   while sub.poll() in ["", None]:
     time.sleep(0.01)
     try:
       errors += sub.stderr.readline()
     except IOError, description:
       pass
Example #2
0
def write(crontabEntries=[]):
  """Write a crontab-formatted file from the list of fstabLines. Return values
  of 0 or 1 to indicate a failure writing to the crontab or a success
  respectively."""
  if LINUX:
    environ = {'EDITOR': 'python %s/cronwriter.py' % INSTALL_DIR,
               'VISUAL': 'python %s/cronwriter.py' % INSTALL_DIR}
  elif DARWIN:
    environ = {'EDITOR': '/fwbackups-cronwriter.py',
               'VISUAL': '/fwbackups-cronwriter.py'}
  else:
    environ = {}
  remove()
  try:
    if MSWINDOWS:
      crontab = getPyCrontab()
      fh = open(crontab, 'w')
    else:
      if DARWIN:
        if os.path.islink('/fwbackups-cronwriter.py'):
          os.remove('/fwbackups-cronwriter.py')
        os.symlink(os.path.join(encode(INSTALL_DIR), 'cronwriter.py'), '/fwbackups-cronwriter.py')
      sub = executeSub(['crontab', '-e'], environ, stdoutfd=subprocess.PIPE)
      fh = sub.stdin
    # Write the content to the crontab
    for crontabEntry in crontabEntries:
      if isinstance(crontabEntry, crontabLine): # generate the entry text
        fh.write(encode(crontabEntry.generate_entry_text()))
      else:
        fh.write(encode(crontabEntry.get_raw_entry_text()))
    time.sleep(1)
    fh.close()
    if not MSWINDOWS:
      counter = 0.0
      while sub.poll() in [None, ""] and counter < 5.0: # After waiting for 5 seconds, assume that the crontab could not be installed
        time.sleep(0.1)
        counter += 0.1
      if sub.poll() in [None, ""]:
        # Soft-terminate the process if it still hasn't finished
        kill(sub.pid, 15)
        sub.wait()
        raise ValidationError(_("The crontab could not be saved"))
      if DARWIN:
        os.remove('/fwbackups-cronwriter.py')
      if sub.poll() != os.EX_OK:
        stdout = ' '.join(sub.stdout.readlines())
        stderr = ' '.join(sub.stderr.readlines())
        raise CronError(_('Could not write new crontab:\n%(a)s%(b)s') % {'a': stdout, 'b': stderr})
    fh.close()
  except IOError: # can't open crontab file
    return 0
  return 1
Example #3
0
 def backupPaths(self, paths, command):
   """Does the actual copying dirty work"""
   # this is in common
   self._current = 0
   if len(paths) == 0:
     return True
   self._total = len(paths)
   self._status = STATUS_BACKING_UP
   wasAnError = False
   if self.options['Engine'] == 'tar':
     if MSWINDOWS:
       self.logger.logmsg('INFO', _('Using %s on Windows: Cancel function will only take effect after a path has been completed.' % self.options['Engine']))
       import tarfile
       fh = tarfile.open(self.dest, 'w')
       for i in paths:
         self.ifCancel()
         self._current += 1
         self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i}))
         fh.add(i, recursive=self.options['Recursive'])
       fh.close()
     else: # not MSWINDOWS
       for i in paths:
         i = fwbackups.escapeQuotes(i, 1)
         self.ifCancel()
         self._current += 1
         self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s '%(c)s'" % {'a': self.options['Nice'], 'b': command, 'c': i}))
         sub = fwbackups.executeSub("nice -n %i %s '%s'" % (self.options['Nice'], command, i), env=self.environment, shell=True)
         self.pids.append(sub.pid)
         self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid)
         # track stdout
         errors = []
         # use nonblocking I/O
         fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL)
         fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK)
         while sub.poll() in ["", None]:
           time.sleep(0.01)
           try:
             errors += sub.stderr.readline()
           except IOError, description:
             pass
         self.pids.remove(sub.pid)
         retval = sub.poll()
         self.logger.logmsg('DEBUG', _('Subprocess with PID %(a)s exited with status %(b)s' % {'a': sub.pid, 'b': retval}))
         # Something wrong?
         if retval != EXIT_STATUS_OK and retval != 2:
           wasAnError = True
           self.logger.logmsg('ERROR', 'An error occurred while backing up path \'%s\'.\nPlease check the error output below to determine if any files are incomplete or missing.' % str(i))
           self.logger.logmsg('ERROR', _('Process exited with status %(a)s. Errors: %(b)s' % {'a': retval, 'b': ''.join(errors)}))
Example #4
0
def read():
  """Read in crontab entires from a crontab-formatted file. Returns a list of
  rawCrontabLine objects, one for each line."""
  if MSWINDOWS:
    # Read from pycron's crontab file
    crontabLoc = getPyCrontab()
    fh = open(crontabLoc, 'r')
  else:
    # Read from the user's crontab
    sub = executeSub(['crontab', '-l'], stdoutfd=subprocess.PIPE)
    retval = sub.wait()
    if retval != os.EX_OK and retval != 1:
      raise CronError('stderr:\n%sstdout:\n%s' % (sub.stderr.readlines(), sub.stdout.readlines()))
    fh = sub.stdout
  # Parse the lines
  lines = [rawCrontabLine(line) for line in fh.readlines()]
  if MSWINDOWS:
    fh.close()
  return lines
Example #5
0
def read():
    """Read in crontab entires from a crontab-formatted file. Returns a list of
  rawCrontabLine objects, one for each line."""
    if MSWINDOWS:
        # Read from pycron's crontab file
        crontabLoc = getPyCrontab()
        fh = open(crontabLoc, 'r')
    else:
        # Read from the user's crontab
        sub = executeSub(['crontab', '-l'], stdoutfd=subprocess.PIPE)
        retval = sub.wait()
        if retval != os.EX_OK and retval != 1:
            raise CronError('stderr:\n%sstdout:\n%s' %
                            (sub.stderr.readlines(), sub.stdout.readlines()))
        fh = sub.stdout
    # Parse the lines
    lines = [rawCrontabLine(line) for line in fh.readlines()]
    if MSWINDOWS:
        fh.close()
    return lines
Example #6
0
class BackupOperation(operations.Common):
  """A parent class for all backups operations."""
  def __init__(self, logger=None):
    """Initalizes the class. If no logger is specified, a new one will be
    created."""
    operations.Common.__init__(self, logger)

  def getOptions(self, conf):
    """Loads all the configuration options from a restore configuration file and
    returns them in a dictionary"""
    def _bool(value):
      return value in [1, '1', True, 'True']
    options = conf.getOptions()
    if not options['RemotePort']:
      options['RemotePort'] = 22
    else:
      options['RemotePort'] = int(options['RemotePort'])
    options['Nice'] = int(options['Nice'])
    options['RemotePassword'] = options['RemotePassword'].decode('base64')
    for option in ['Recursive', 'PkgListsToFile', 'DiskInfoToFile',
                   'BackupHidden', 'FollowLinks', 'Sparse']:
      options[option] = _bool(options[option])
    return options

  def parsePaths(self, config):
    """Get the list of paths in the configuration file. Returns a list of paths to backup"""
    paths = []
    for path in config.getPaths():
      paths.append(path)
    return paths

  def createPkgLists(self):
    """Create the pkg lists in tempdir"""
    # Start as a dictionary so we can keep track of which items we have already
    # processed. See comment at retur for more info.
    pkgListFiles = {}
    for path in os.environ['PATH'].split(':'):
      if os.path.exists(os.path.join(path, 'rpm')) and not pkgListFiles.has_key('rpm'):
        fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('rpm - Package list'))
        fh = os.fdopen(fd, "w")
        # try with rpm-python, but if not just execute it like the rest
        try:
          import rpm
          pyrpm = True
        except ImportError:
          pyrpm = False
        if pyrpm:
          ts=rpm.ts()
          # Equivalent to rpm -qa
          mi=ts.dbMatch()
          for hdr in mi:
            fh.write("%s-%s-%s.%s\n" % (hdr['name'], hdr['version'], hdr['release'], hdr['arch']))
        else:
          retval, stdout, stderr = fwbackups.execute('rpm -qa', env=self.environment, shell=True, stdoutfd=fh)
        pkgListFiles['rpm'] = path
        fh.close()
      if os.path.exists(os.path.join(path, 'pacman')) and not pkgListFiles.has_key('pacman'):
        fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('Pacman - Package list'))
        fh = os.fdopen(fd, 'w')
        retval, stdout, stderr = fwbackups.execute('pacman -Qq', env=self.environment, shell=True, stdoutfd=fh)
        pkgListFiles['pacman'] = path
        fh.close()
      if os.path.exists(os.path.join(path, 'dpkg')) and not pkgListFiles.has_key('dpkg'):
        fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('dpkg - Package list'))
        fh = os.fdopen(fd, 'w')
        retval, stdout, stderr = fwbackups.execute('dpkg -l', env=self.environment, shell=True, stdoutfd=fh)
        pkgListFiles['dpkg'] = path
        fh.close()
    # We want to return a list of only the filenames
    return pkgListFiles.values()

  def createDiskInfo(self):
    """Print disk info to a file in tempdir"""
    fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('Disk Information'))
    fh = os.fdopen(fd, 'w')
    retval, stdout, stderr = fwbackups.execute('fdisk -l', env=self.environment, shell=True, stdoutfd=fh)
    fh.close()
    return path

  def parseCommand(self):
    """Parse options to retrieve the correct command"""
    self.options = self.getOptions(self.config)
    if self.options['DestinationType'] == 'remote (ssh)':
      prefs = config.PrefsConf(create=True)
      tempDir = prefs.get('Preferences', 'TempDir') or tempfile.gettempdir()
      self.dest = os.path.join(tempDir, os.path.split(fwbackups.escapeQuotes(self.dest, 1))[1])
    if self.options['Engine'] == 'rsync':
      command = 'rsync -g -o -p -t -R'
      if self.options['Incremental']:
        command += ' -u --del'
      if self.options['Recursive']:
        command += ' -r'
      if not self.options['BackupHidden']:
        command += ' --exclude=.*'
      if self.options['Sparse']:
        command += ' -S'
      if self.options['FollowLinks']:
        command += ' -L'
      else:
        command += ' -l'
      if self.options['Excludes'] != None and self.options['Excludes'] != "":
        for i in self.options['Excludes'].split('\n'):
          command += ' --exclude="%s"' % i
    elif self.options['Engine'] == 'tar':
      command = "tar rf '%s'" % fwbackups.escapeQuotes(self.dest, 1)
    elif self.options['Engine'] == 'tar.gz':
      # DON'T rfz - Can't use r (append) and z (gzip) together
      command = "tar cfz '%s'" % fwbackups.escapeQuotes(self.dest, 1)
    elif self.options['Engine'] == 'tar.bz2':
      # DON'T rfz - Can't use r (append) and j (bzip2) together
      command = "tar cfj '%s'" % fwbackups.escapeQuotes(self.dest, 1)
    # --
    if self.options['Engine'] in ['tar', 'tar.gz', 'tar.bz2']: # they share command options
      if not self.options['Recursive']:
        command += ' --no-recursion'
      if not self.options['BackupHidden']:
        command += ' --exclude=.*'
      if self.options['Sparse']:
        command += ' -S'
      if self.options['FollowLinks']:
        command += ' -h'
    if self.options['Excludes']:
      for i in self.options['Excludes'].split('\n'):
        command += ' --exclude="%s"' % i
    # Finally...
    return command

  def addListFilesToBackup(self, pkgListfiles, command, engine, paths):
    """Adds the pkglist and diskinfo to the backup"""
    self.ifCancel()
    for file in pkgListfiles:
      paths.append(file)

  def deleteListFiles(self, pkgListfiles):
    """Delete the list files in the tempdir"""
    self.ifCancel()
    for file in pkgListfiles:
      os.remove(file)

  def checkRemoteServer(self):
    """Checks if a connection to the remote server can be established"""
    self.logger.logmsg('DEBUG', _('Attempting to connect to server %s...') % self.options['RemoteHost'])
    thread = fwbackups.runFuncAsThread(sftp.testConnection,
                                       self.options['RemoteHost'], self.options['RemoteUsername'],
                                       self.options['RemotePassword'], self.options['RemotePort'],
                                       self.options['RemoteFolder'])
    while thread.retval == None:
      time.sleep(0.1)
    # Check for errors, if any
    import paramiko
    import socket
    if thread.retval == True:
      self.logger.logmsg('DEBUG', _('Attempt to connect succeeded.'))
      return True
    elif type(thread.exception) == IOError:
      self.logger.logmsg('ERROR', _('The backup destination was either not ' + \
                     'found or it cannot be written to due to insufficient permissions.'))
      return False
    elif type(thread.exception) == paramiko.AuthenticationException:
      self.logger.logmsg('ERROR', _('A connection was established, but authentication ' + \
                      'failed. Please verify the username and password ' + \
                      'and try again.'))
      return False
    elif type(thread.exception) == socket.gaierror or type(thread.exception) == socket.error:
      self.logger.logmsg('ERROR', _('A connection to the server could not be established.\n' + \
                      'Error %(a)s: %(b)s' % {'a': type(thread.exception), 'b': str(thread.exception)} + \
                      '\nPlease verify your settings and try again.'))
      return False
    elif type(thread.exception) == socket.timeout:
      self.logger.logmsg('ERROR', _('A connection to the server has timed out. ' + \
                      'Please verify your settings and try again.'))
      return False
    elif type(thread.exception) == paramiko.SSHException:
      self.logger.logmsg('ERROR', _('A connection to the server could not be established ' + \
                      'because an error occurred: %s' % str(thread.exception) + \
                      '\nPlease verify your settings and try again.'))
      return False
    else: # not remote, just pass
      return True

  def backupPaths(self, paths, command):
    """Does the actual copying dirty work"""
    # this is in common
    self._current = 0
    if len(paths) == 0:
      return True
    self._total = len(paths)
    self._status = STATUS_BACKING_UP
    wasAnError = False
    if self.options['Engine'] == 'tar':
      if MSWINDOWS:
        self.logger.logmsg('INFO', _('Using %s on Windows: Cancel function will only take effect after a path has been completed.' % self.options['Engine']))
        import tarfile
        fh = tarfile.open(self.dest, 'w')
        for i in paths:
          self.ifCancel()
          self._current += 1
          self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i}))
          fh.add(i, recursive=self.options['Recursive'])
        fh.close()
      else: # not MSWINDOWS
        for i in paths:
          i = fwbackups.escapeQuotes(i, 1)
          self.ifCancel()
          self._current += 1
          self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s '%(c)s'" % {'a': self.options['Nice'], 'b': command, 'c': i}))
          sub = fwbackups.executeSub("nice -n %i %s '%s'" % (self.options['Nice'], command, i), env=self.environment, shell=True)
          self.pids.append(sub.pid)
          self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid)
          # track stdout
          errors = []
          # use nonblocking I/O
          fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL)
          fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK)
          while sub.poll() in ["", None]:
            time.sleep(0.01)
            try:
              errors += sub.stderr.readline()
            except IOError, description:
              pass
          self.pids.remove(sub.pid)
          retval = sub.poll()
          self.logger.logmsg('DEBUG', _('Subprocess with PID %(a)s exited with status %(b)s' % {'a': sub.pid, 'b': retval}))
          # Something wrong?
          if retval != EXIT_STATUS_OK and retval != 2:
            wasAnError = True
            self.logger.logmsg('ERROR', 'An error occurred while backing up path \'%s\'.\nPlease check the error output below to determine if any files are incomplete or missing.' % str(i))
            self.logger.logmsg('ERROR', _('Process exited with status %(a)s. Errors: %(b)s' % {'a': retval, 'b': ''.join(errors)}))

    elif self.options['Engine'] == 'tar.gz':
      self._total = 1
      if MSWINDOWS:
        self.logger.logmsg('INFO', _('Using %s on Windows: Cancel function will only take effect after a path has been completed.' % self.options['Engine']))
        import tarfile
        fh = tarfile.open(self.dest, 'w:gz')
        for i in paths:
          self._current += 1
          self.ifCancel()
          self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i}))
          fh.add(i, recursive=self.options['Recursive'])
          self.logger.logmsg('DEBUG', _('Adding path `%s\' to the archive' % i))
        fh.close()
      else: # not MSWINDOWS
        self._current = 1
        escapedPaths = [fwbackups.escapeQuotes(i, 1) for i in paths]
        # This is a fancy way for getting i = "'one' 'two' 'three'"
        i = "'%s'" % "' '".join(escapedPaths)
        self.logger.logmsg('INFO', _('Using %s: Must backup all paths at once - Progress notification will be disabled.' % self.options['Engine']))
        self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s') % {'a': self._current, 'b': self._total, 'c': i.replace("'", '')})
        self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s %(c)s" % {'a': self.options['Nice'], 'b': command, 'c': i}))
        # Don't wrap i in quotes; we did this above already when mering the paths
        sub = fwbackups.executeSub("nice -n %i %s %s" % (self.options['Nice'], command, i), env=self.environment, shell=True)
        self.pids.append(sub.pid)
        self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid)
        # track stdout
        errors = []
        # use nonblocking I/O
        fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL)
        fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        while sub.poll() in ["", None]:
          time.sleep(0.01)
          try:
            errors += sub.stderr.readline()
          except IOError, description:
            pass
        self.pids.remove(sub.pid)
        retval = sub.poll()
        self.logger.logmsg('DEBUG', _('Subprocess with PID %(a)s exited with status %(b)s' % {'a': sub.pid, 'b': retval}))
        # Something wrong?
        if retval != EXIT_STATUS_OK and retval != 2:
          wasAnError = True
          self.logger.logmsg('ERROR', 'An error occurred while backing up path \'%s\'.\nPlease check the error output below to determine if any files are incomplete or missing.' % str(i))
          self.logger.logmsg('ERROR', _('Process exited with status %(a)s. Errors: %(b)s' % {'a': retval, 'b': ''.join(errors)}))
Example #7
0
     self._current += 1
     self.ifCancel()
     self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i}))
     fh.add(i, recursive=self.options['Recursive'])
     self.logger.logmsg('DEBUG', _('Adding path `%s\' to the archive' % i))
   fh.close()
 else: # not MSWINDOWS
   self._current = 1
   escapedPaths = [fwbackups.escapeQuotes(i, 1) for i in paths]
   # This is a fancy way for getting i = "'one' 'two' 'three'"
   i = "'%s'" % "' '".join(escapedPaths)
   self.logger.logmsg('INFO', _('Using %s: Must backup all paths at once - Progress notification will be disabled.' % self.options['Engine']))
   self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s') % {'a': self._current, 'b': self._total, 'c': i})
   self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s %(c)s" % {'a': self.options['Nice'], 'b': command, 'c': i}))
   # Don't wrap i in quotes; we did this above already when mering the paths
   sub = fwbackups.executeSub("nice -n %i %s %s" % (self.options['Nice'], command, i), env=self.environment, shell=True)
   self.pids.append(sub.pid)
   self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid)
   # track stdout
   errors = []
   # use nonblocking I/O
   fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL)
   fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK)
   while sub.poll() in ["", None]:
     time.sleep(0.01)
     try:
       errors += sub.stderr.readline()
     except IOError, description:
       pass
   self.pids.remove(sub.pid)
   retval = sub.poll()