-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackrest
executable file
·141 lines (111 loc) · 5.96 KB
/
backrest
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
#!/usr/bin/python3
import argparse
import filecmp
import json
import os
import re
import shutil
import subprocess
import sys
def list_append(listref, element):
if listref:
listref.append(element)
return listref
return [element]
argumentParser = argparse.ArgumentParser(description="Show the differences between a set of files and a backup copy")
argumentParser.add_argument("-H", "--hostname", help="name of the backup host; if no MAC specified, look up in ~/.hosttomac")
argumentParser.add_argument("-M", "--mac", help="ethernet MAC address; if specified write to ~/.hosttomac")
argumentParser.add_argument("-m", "--move", action='store_true', help="move files in the backup to match the source tree")
argumentParser.add_argument("-n", "--noaction", action='store_true', help="take no actual action, only tell what would be done")
argumentParser.add_argument("-r", "--rename", action='store_true', help="rename files in the backup to match the source tree")
argumentParser.add_argument("-u", "--update", action='store_true', help="update files in the backup to match the source tree")
argumentParser.add_argument("-d", "--debug", action='store_true', help="print out a lot of information helpful in debugging")
argumentParser.add_argument("source", nargs="?", default=".", help="the source directory")
argumentParser.add_argument("backup", help="the backup directory")
arguments = argumentParser.parse_args()
hostToMac = {}
try:
with open(os.getenv("HOME") + "/.hosttomac") as hostToMacFP:
hostToMac = json.load(hostToMacFP)
except IOError as error:
if os.path.exists(os.path.realpath(os.getenv("HOME") + "/.hosttomac")):
raise error
if arguments.hostname:
if arguments.mac:
if hostToMac.get(arguments.hostname) and arguments.mac != hostToMac[arguments.hostname]:
sys.exit("Host " + arguments.hostname + " is already in ~/.hosttomac with MAC "
+ hostToMac[arguments.hostname] + " but you specified " + arguments.mac)
elif hostToMac.get(arguments.hostname):
arguments.mac = hostToMac[arguments.hostname]
else:
sys.exit("Host " + arguments.hostname + " specified without a MAC and not found in ~/.hosttomac")
output = subprocess.check_output(["arp", "-n"], text=True)
linePat = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+ether\s+'
+ r'([\da-f][\da-f]:[\da-f][\da-f]:[\da-f][\da-f]:[\da-f][\da-f]:[\da-f][\da-f]:[\da-f][\da-f])')
hostIp = None
for line in output.split('\n'):
match = linePat.match(line)
if match:
if match.group(2) == arguments.mac:
hostIp = match.group(1)
if not hostIp:
sys.stderr.write(output)
sys.exit("MAC address " + arguments.mac + " not found in arp -n output; try running: sudo arp-scan -l")
if not hostToMac.get(arguments.hostname):
hostToMac[arguments.hostname] = arguments.mac
with open(os.getenv("HOME") + "/.hosttomac", "w") as hostToMacFP:
json.dump(hostToMac, hostToMacFP)
print(hostIp)
print(arguments)
source_path_len = len(os.path.abspath(arguments.source)) + 1 # roots from os.walk include absolute paths
backup_path_len = len(os.path.abspath(arguments.backup)) + 1 # roots from os.walk include absolute paths
# Build a model of the backup
files_by_size = {}
files_by_name = {}
for root, dirs, files in os.walk(arguments.backup):
for name in files:
size = os.path.getsize(os.path.join(root, name))
file = {"name": name, "dir": root[backup_path_len:] if root[backup_path_len:] else ".", "size": size};
files_by_size[size] = list_append(files_by_size.get(size), file)
files_by_name[name] = list_append(files_by_name.get(name), file)
for root, dirs, files in os.walk(arguments.source):
dir = root if not root.startswith("./") else root[2:]
for name in files:
path = os.path.join(root, name)
size = os.path.getsize(path)
back_files = files_by_name.get(name)
if arguments.debug:
print("File {} ({} bytes)".format(path, size))
if back_files:
if len(back_files) == 1:
if size == back_files[0]['size']:
if arguments.move and dir != back_files[0]['dir']:
if arguments.noaction:
print("Would move {} from {} to {}".format(name, back_files[0]['dir'], dir))
else:
if not os.path.isdir(dir):
os.makedirs(dir)
shutil.move(path, os.path.join(arguments.backup, dir, name))
elif arguments.update:
if dir == back_files[0]['dir']:
if arguments.noaction:
print("Would update {} in {}".format(name, back_files[0]['dir'] if back_files[0]['dir'] else "."))
else:
shutil.copyfile(path, os.path.join(arguments.backup, dir, name))
elif arguments.move:
# if arguments.noaction:
print("Would update and move {} from {} to {}".format(name, back_files[0]['dir'], dir))
continue
back_files = files_by_size.get(size)
if back_files:
if len(back_files) == 1:
if filecmp.cmp(path, os.path.join(arguments.backup, back_files[0]["dir"], back_files[0]["name"])):
if arguments.rename:
# if arguments.noaction:
print("Would rename {} from {} to {}".format(name, back_files[0]['dir'], dir))
continue
if arguments.noaction:
print("Would copy new file {} to {}".format(path, os.path.join(arguments.backup, dir, name)))
else:
os.makedirs(os.path.join(arguments.backup, dir), exist_ok=True)
shutil.copyfile(path, os.path.join(arguments.backup, dir, name))