summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup/robust.py
blob: 46b690d5a940ad7c1cf451191addd0172ba3a78b (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
# 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
import librsync, C, static, rpath, Globals, log, statistics

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, 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
		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):
		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] == 'invalid mode: rb' or
		 errno.errorcode.has_key(exc[0]) and
		 errno.errorcode[exc[0]] in ('EPERM', 'ENOENT', 'EACCES', 'EBUSY',
									 'EEXIST', 'ENOTDIR', 'ENAMETOOLONG',
									 'EINTR', 'ENOTEMPTY', 'EIO', 'ETXTBSY',
									 'ESRCH', 'EINVAL', 'EDEADLOCK'))):
		return 1
	return 0

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"""
	for signum in [signal.SIGQUIT, signal.SIGHUP, signal.SIGTERM]:
		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)