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
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
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)}))
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
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)}))
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()