summaryrefslogtreecommitdiff
path: root/rdiff-backup/src/setconnections.py
diff options
context:
space:
mode:
Diffstat (limited to 'rdiff-backup/src/setconnections.py')
-rw-r--r--rdiff-backup/src/setconnections.py205
1 files changed, 205 insertions, 0 deletions
diff --git a/rdiff-backup/src/setconnections.py b/rdiff-backup/src/setconnections.py
new file mode 100644
index 0000000..07c6893
--- /dev/null
+++ b/rdiff-backup/src/setconnections.py
@@ -0,0 +1,205 @@
+execfile("highlevel.py")
+
+#######################################################################
+#
+# setconnections - Parse initial arguments and establish connections
+#
+
+class SetConnectionsException(Exception): pass
+
+class SetConnections:
+ """Parse args and setup connections
+
+ The methods in this class are used once by Main to parse file
+ descriptions like bescoto@folly.stanford.edu:/usr/bin/ls and to
+ set up the related connections.
+
+ """
+ # This is the schema that determines how rdiff-backup will open a
+ # pipe to the remote system. If the file is given as A:B, %s will
+ # be substituted with A in the schema.
+ __cmd_schema = 'ssh %s rdiff-backup --server'
+
+ # This is a list of remote commands used to start the connections.
+ # The first is None because it is the local connection.
+ __conn_remote_cmds = [None]
+
+ def InitRPs(cls, arglist, remote_schema = None, remote_cmd = None):
+ """Map the given file descriptions into rpaths and return list"""
+ if remote_schema: cls.__cmd_schema = remote_schema
+ if not arglist: return []
+ desc_pairs = map(cls.parse_file_desc, arglist)
+
+ if filter(lambda x: x[0], desc_pairs): # True if any host_info found
+ if remote_cmd:
+ Log.FatalError("The --remote-cmd flag is not compatible "
+ "with remote file descriptions.")
+ elif remote_schema:
+ Log("Remote schema option ignored - no remote file "
+ "descriptions.", 2)
+
+ cmd_pairs = map(cls.desc2cmd_pairs, desc_pairs)
+ if remote_cmd: # last file description gets remote_cmd
+ cmd_pairs[-1] = (remote_cmd, cmd_pairs[-1][1])
+ return map(cls.cmdpair2rp, cmd_pairs)
+
+ def cmdpair2rp(cls, cmd_pair):
+ """Return RPath from cmd_pair (remote_cmd, filename)"""
+ cmd, filename = cmd_pair
+ if cmd: conn = cls.init_connection(cmd)
+ else: conn = Globals.local_connection
+ return RPath(conn, filename)
+
+ def desc2cmd_pairs(cls, desc_pair):
+ """Return pair (remote_cmd, filename) from desc_pair"""
+ host_info, filename = desc_pair
+ if not host_info: return (None, filename)
+ else: return (cls.fill_schema(host_info), filename)
+
+ def parse_file_desc(cls, file_desc):
+ """Parse file description returning pair (host_info, filename)
+
+ In other words, bescoto@folly.stanford.edu::/usr/bin/ls =>
+ ("bescoto@folly.stanford.edu", "/usr/bin/ls"). The
+ complication is to allow for quoting of : by a \. If the
+ string is not separated by :, then the host_info is None.
+
+ """
+ def check_len(i):
+ if i >= len(file_desc):
+ raise SetConnectionsException(
+ "Unexpected end to file description %s" % file_desc)
+
+ host_info_list, i, last_was_quoted = [], 0, None
+ while 1:
+ if i == len(file_desc):
+ return (None, file_desc)
+
+ if file_desc[i] == '\\':
+ i = i+1
+ check_len(i)
+ last_was_quoted = 1
+ elif (file_desc[i] == ":" and i > 0 and file_desc[i-1] == ":"
+ and not last_was_quoted):
+ host_info_list.pop() # Remove last colon from name
+ break
+ else: last_was_quoted = None
+ host_info_list.append(file_desc[i])
+ i = i+1
+
+ check_len(i+1)
+ return ("".join(host_info_list), file_desc[i+1:])
+
+ def fill_schema(cls, host_info):
+ """Fills host_info into the schema and returns remote command"""
+ return cls.__cmd_schema % host_info
+
+ def init_connection(cls, remote_cmd):
+ """Run remote_cmd, register connection, and then return it
+
+ If remote_cmd is None, then the local connection will be
+ returned. This also updates some settings on the remote side,
+ like global settings, its connection number, and verbosity.
+
+ """
+ if not remote_cmd: return Globals.local_connection
+
+ Log("Executing " + remote_cmd, 4)
+ stdin, stdout = os.popen2(remote_cmd)
+ conn_number = len(Globals.connections)
+ conn = PipeConnection(stdout, stdin, conn_number)
+
+ cls.check_connection_version(conn)
+ Log("Registering connection %d" % conn_number, 7)
+ cls.init_connection_routing(conn, conn_number, remote_cmd)
+ cls.init_connection_settings(conn)
+ return conn
+
+ def check_connection_version(cls, conn):
+ """Log warning if connection has different version"""
+ remote_version = conn.Globals.get('version')
+ if remote_version != Globals.version:
+ Log("Warning: Local version %s does not match remote version %s."
+ % (Globals.version, remote_version), 2)
+
+ def init_connection_routing(cls, conn, conn_number, remote_cmd):
+ """Called by init_connection, establish routing, conn dict"""
+ Globals.connection_dict[conn_number] = conn
+
+ conn.SetConnections.init_connection_remote(conn_number)
+ for other_remote_conn in Globals.connections[1:]:
+ conn.SetConnections.add_redirected_conn(
+ other_remote_conn.conn_number)
+ other_remote_conn.SetConnections.add_redirected_conn(conn_number)
+
+ Globals.connections.append(conn)
+ cls.__conn_remote_cmds.append(remote_cmd)
+
+ def init_connection_settings(cls, conn):
+ """Tell new conn about log settings and updated globals"""
+ conn.Log.setverbosity(Log.verbosity)
+ conn.Log.setterm_verbosity(Log.term_verbosity)
+ for setting_name in Globals.changed_settings:
+ conn.Globals.set(setting_name, Globals.get(setting_name))
+
+ def init_connection_remote(cls, conn_number):
+ """Run on server side to tell self that have given conn_number"""
+ Globals.connection_number = conn_number
+ Globals.local_connection.conn_number = conn_number
+ Globals.connection_dict[0] = Globals.connections[1]
+ Globals.connection_dict[conn_number] = Globals.local_connection
+
+ def add_redirected_conn(cls, conn_number):
+ """Run on server side - tell about redirected connection"""
+ Globals.connection_dict[conn_number] = \
+ RedirectedConnection(conn_number)
+
+ def UpdateGlobal(cls, setting_name, val):
+ """Update value of global variable across all connections"""
+ for conn in Globals.connections:
+ conn.Globals.set(setting_name, val)
+
+ def BackupInitConnections(cls, reading_conn, writing_conn):
+ """Backup specific connection initialization"""
+ reading_conn.Globals.set("isbackup_reader", 1)
+ writing_conn.Globals.set("isbackup_writer", 1)
+ cls.UpdateGlobal("backup_reader", reading_conn)
+ cls.UpdateGlobal("backup_writer", writing_conn)
+
+
+ def CloseConnections(cls):
+ """Close all connections. Run by client"""
+ assert not Globals.server
+ for conn in Globals.connections: conn.quit()
+ del Globals.connections[1:] # Only leave local connection
+ Globals.connection_dict = {0: Globals.local_connection}
+ Globals.backup_reader = Globals.isbackup_reader = \
+ Globals.backup_writer = Globals.isbackup_writer = None
+
+ def TestConnections(cls):
+ """Test connections, printing results"""
+ if len(Globals.connections) == 1:
+ print "No remote connections specified"
+ else:
+ for i in range(1, len(Globals.connections)):
+ cls.test_connection(i)
+
+ def test_connection(cls, conn_number):
+ """Test connection. conn_number 0 is the local connection"""
+ print "Testing server started by: ", \
+ cls.__conn_remote_cmds[conn_number]
+ conn = Globals.connections[conn_number]
+ try:
+ assert conn.pow(2,3) == 8
+ assert conn.os.path.join("a", "b") == "a/b"
+ version = conn.reval("lambda: Globals.version")
+ except:
+ sys.stderr.write("Server tests failed\n")
+ raise
+ if not version == Globals.version:
+ print """Server may work, but there is a version mismatch:
+Local version: %s
+Remote version: %s""" % (Globals.version, version)
+ else: print "Server OK"
+
+MakeClass(SetConnections)