summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup/robust.py
blob: e33f4212b1edf00548f064ac2ac22887fa9b65f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Copyright 2002 Ben Escoto
#
# This file is part of rdiff-backup.
#
# rdiff-backup is free software; you can redistribute it and/or modify
# 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.
#
# rdiff-backup 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 rdiff-backup; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

"""Catch various exceptions given system call"""

import errno, signal, exceptions, zlib
import librsync, C, static, rpath, Globals, log, statistics, connection

def check_common_error(error_handler, function, args = []):
	"""Apply function to args, if error, run error_handler on exception

	This uses the catch_error predicate below to only catch
	certain exceptions which seems innocent enough.

	"""
	try: return function(*args)
	except (Exception, KeyboardInterrupt, SystemExit), exc:
		TracebackArchive.add([function] + list(args))
		if catch_error(exc):
			log.Log.exception()
			conn = Globals.backup_writer
			if conn is not None: conn.statistics.record_error()
			if error_handler: return error_handler(exc, *args)
			else: return None
		if is_routine_fatal(exc): log.Log.exception(1, 6)
		else: log.Log.exception(1, 2)
		raise

def catch_error(exc):
	"""Return true if exception exc should be caught"""
	for exception_class in (rpath.SkipFileException, rpath.RPathException,
							librsync.librsyncError, C.UnknownFileTypeError,
							zlib.error):
		if isinstance(exc, exception_class): return 1
	if (isinstance(exc, EnvironmentError) and
		# the invalid mode shows up in backups of /proc for some reason
		(exc[0] in ('invalid mode: rb', 'Not a gzipped file') or
		 errno.errorcode.has_key(exc[0]) and
		 errno.errorcode[exc[0]] in ('EPERM', 'ENOENT', 'EACCES', 'EBUSY',
									 'EEXIST', 'ENOTDIR', 'EILSEQ',
									 'ENAMETOOLONG', 'EINTR', 'ESTALE',
									 'ENOTEMPTY', 'EIO', 'ETXTBSY',
									 'ESRCH', 'EINVAL', 'EDEADLOCK',
									 'EDEADLK', 'EOPNOTSUPP', 'ETIMEDOUT'))):
		return 1
	return 0

def is_routine_fatal(exc):
	"""Return string if exception is non-error unrecoverable, None otherwise

	Used to suppress a stack trace for exceptions like keyboard
	interrupts or connection drops.  Return value is string to use as
	an exit message.

	"""
	if isinstance(exc, exceptions.KeyboardInterrupt):
		return "User abort"
	elif isinstance(exc, connection.ConnectionError):
		return "Lost connection to the remote system"
	elif isinstance(exc, SignalException):
		return "Killed with signal %s" % (exc,)
	elif isinstance(exc, EnvironmentError) and exc.errno == errno.ENOTCONN:
		return ("Filesystem reports connection failure:\n%s" % exc)
	return None

def get_error_handler(error_type):
	"""Return error handler function that can be used above

	Function will just log error to the error_log and then return
	None.  First two arguments must be the exception and then an rp
	(from which the filename will be extracted).

	"""
	def error_handler(exc, rp, *args):
		log.ErrorLog.write_if_open(error_type, rp, exc)
		return 0
	return error_handler

def listrp(rp):
	"""Like rp.listdir() but return [] if error, and sort results"""
	def error_handler(exc):
		log.Log("Error listing directory %s" % rp.path, 2)
		return []
	dir_listing = check_common_error(error_handler, rp.listdir)
	dir_listing.sort()
	return dir_listing

def signal_handler(signum, frame):
	"""This is called when signal signum is caught"""
	raise SignalException(signum)

def install_signal_handlers():
	"""Install signal handlers on current connection"""
	signals = [signal.SIGTERM, signal.SIGINT]
	try:
		signals.extend([signal.SIGHUP, signal.SIGQUIT])
	except AttributeError:
	    pass
	for signum in signals:
		signal.signal(signum, signal_handler)


class SignalException(Exception):
	"""SignalException(signum) means signal signum has been received"""
	pass


class TracebackArchive:
	"""Save last 10 caught exceptions, so they can be printed if fatal"""
	_traceback_strings = []
	def add(cls, extra_args = []):
		"""Add most recent exception to archived list

		If extra_args are present, convert to strings and add them as
		extra information to same traceback archive.

		"""
		cls._traceback_strings.append(log.Log.exception_to_string(extra_args))
		if len(cls._traceback_strings) > 10:
			cls._traceback_strings = cls._traceback_strings[:10]

	def log(cls):
		"""Print all exception information to log file"""
		if cls._traceback_strings:
			log.Log("------------ Old traceback info -----------\n%s\n"
					"-------------------------------------------" %
					("\n".join(cls._traceback_strings),), 3)

static.MakeClass(TracebackArchive)