28 |
""" |
""" |
29 |
|
|
30 |
import errno, os |
import errno, os |
31 |
import Globals, log, TempFile, selection, robust |
import Globals, log, TempFile, selection, robust, SetConnections, \ |
32 |
|
static, FilenameMapping |
33 |
|
|
34 |
class FSAbilities: |
class FSAbilities: |
35 |
"""Store capabilities of given file system""" |
"""Store capabilities of given file system""" |
36 |
chars_to_quote = None # Hold characters not allowable in file names |
extended_filenames = None # True if filenames can handle ":" etc. |
37 |
case_sensitive = None # True if "foobar" and "FoObAr" are different files |
case_sensitive = None # True if "foobar" and "FoObAr" are different files |
38 |
ownership = None # True if chown works on this filesystem |
ownership = None # True if chown works on this filesystem |
39 |
acls = None # True if access control lists supported |
acls = None # True if access control lists supported |
41 |
hardlinks = None # True if hard linking supported |
hardlinks = None # True if hard linking supported |
42 |
fsync_dirs = None # True if directories can be fsync'd |
fsync_dirs = None # True if directories can be fsync'd |
43 |
dir_inc_perms = None # True if regular files can have full permissions |
dir_inc_perms = None # True if regular files can have full permissions |
44 |
resource_forks = None # True if regular_file/..namedfork/rsrc holds resource fork |
resource_forks = None # True if system supports resource forks |
45 |
carbonfile = None # True if Mac Carbon file data is supported. |
carbonfile = None # True if Mac Carbon file data is supported. |
46 |
name = None # Short string, not used for any technical purpose |
name = None # Short string, not used for any technical purpose |
47 |
read_only = None # True if capabilities were determined non-destructively |
read_only = None # True if capabilities were determined non-destructively |
79 |
else: return ('Detected abilities for %s file system' % |
else: return ('Detected abilities for %s file system' % |
80 |
(read_string,)) |
(read_string,)) |
81 |
|
|
|
def add_ctq_line(): |
|
|
"""Get line describing chars to quote""" |
|
|
ctq_str = (self.chars_to_quote is None and 'N/A' |
|
|
or repr(self.chars_to_quote)) |
|
|
addline('Characters needing quoting', ctq_str) |
|
|
|
|
82 |
s.append(get_title_line()) |
s.append(get_title_line()) |
83 |
if not self.read_only: |
if not self.read_only: |
|
add_ctq_line() |
|
84 |
add_boolean_list([('Ownership changing', self.ownership), |
add_boolean_list([('Ownership changing', self.ownership), |
85 |
('Hard linking', self.hardlinks), |
('Hard linking', self.hardlinks), |
86 |
('fsync() directories', self.fsync_dirs), |
('fsync() directories', self.fsync_dirs), |
87 |
('Directory inc permissions', |
('Directory inc permissions', |
88 |
self.dir_inc_perms), |
self.dir_inc_perms), |
89 |
('High-bit permissions', self.high_perms)]) |
('High-bit permissions', self.high_perms), |
90 |
|
('Extended filenames', self.extended_filenames)]) |
91 |
add_boolean_list([('Access control lists', self.acls), |
add_boolean_list([('Access control lists', self.acls), |
92 |
('Extended attributes', self.eas), |
('Extended attributes', self.eas), |
93 |
('Case sensitivity', self.case_sensitive), |
('Case sensitivity', self.case_sensitive), |
117 |
self.set_case_sensitive_readonly(rp) |
self.set_case_sensitive_readonly(rp) |
118 |
return self |
return self |
119 |
|
|
120 |
def init_readwrite(self, rbdir, use_ctq_file = 1, |
def init_readwrite(self, rbdir): |
|
override_chars_to_quote = None): |
|
121 |
"""Set variables using fs tested at rp_base. Run locally. |
"""Set variables using fs tested at rp_base. Run locally. |
122 |
|
|
123 |
This method creates a temp directory in rp_base and writes to |
This method creates a temp directory in rp_base and writes to |
124 |
it in order to test various features. Use on a file system |
it in order to test various features. Use on a file system |
125 |
that will be written to. |
that will be written to. |
126 |
|
|
|
This sets self.chars_to_quote, self.ownership, self.acls, |
|
|
self.eas, self.hardlinks, and self.fsync_dirs. |
|
|
|
|
|
If user_ctq_file is true, try reading the "chars_to_quote" |
|
|
file in directory. |
|
|
|
|
127 |
""" |
""" |
128 |
assert rbdir.conn is Globals.local_connection |
assert rbdir.conn is Globals.local_connection |
129 |
if not rbdir.isdir(): |
if not rbdir.isdir(): |
131 |
rbdir.mkdir() |
rbdir.mkdir() |
132 |
self.root_rp = rbdir |
self.root_rp = rbdir |
133 |
self.read_only = 0 |
self.read_only = 0 |
|
|
|
134 |
subdir = TempFile.new_in_dir(rbdir) |
subdir = TempFile.new_in_dir(rbdir) |
135 |
subdir.mkdir() |
subdir.mkdir() |
136 |
|
|
137 |
|
self.set_extended_filenames(subdir) |
138 |
|
self.set_case_sensitive_readwrite(subdir) |
139 |
self.set_ownership(subdir) |
self.set_ownership(subdir) |
140 |
self.set_hardlinks(subdir) |
self.set_hardlinks(subdir) |
141 |
self.set_fsync_dirs(subdir) |
self.set_fsync_dirs(subdir) |
145 |
self.set_resource_fork_readwrite(subdir) |
self.set_resource_fork_readwrite(subdir) |
146 |
self.set_carbonfile() |
self.set_carbonfile() |
147 |
self.set_high_perms_readwrite(subdir) |
self.set_high_perms_readwrite(subdir) |
148 |
if override_chars_to_quote is None: self.set_chars_to_quote(subdir) |
|
|
else: self.chars_to_quote = override_chars_to_quote |
|
|
if use_ctq_file: self.compare_chars_to_quote(rbdir) |
|
149 |
subdir.delete() |
subdir.delete() |
150 |
return self |
return self |
151 |
|
|
|
def compare_chars_to_quote(self, rbdir): |
|
|
"""Read chars_to_quote file, compare with current settings""" |
|
|
assert self.chars_to_quote is not None |
|
|
ctq_rp = rbdir.append("chars_to_quote") |
|
|
def write_new_chars(): |
|
|
"""Replace old chars_to_quote file with new value""" |
|
|
if ctq_rp.lstat(): ctq_rp.delete() |
|
|
fp = ctq_rp.open("wb") |
|
|
fp.write(self.chars_to_quote) |
|
|
assert not fp.close() |
|
|
|
|
|
if not ctq_rp.lstat(): write_new_chars() |
|
|
else: |
|
|
old_chars = ctq_rp.get_data() |
|
|
if old_chars != self.chars_to_quote: |
|
|
if self.chars_to_quote == "": |
|
|
log.Log("Warning: File system no longer needs quoting, " |
|
|
"but will retain for backwards compatibility.", 2) |
|
|
self.chars_to_quote = old_chars |
|
|
else: log.Log.FatalError("""New quoting requirements |
|
|
|
|
|
This may be caused when you copy an rdiff-backup directory from a |
|
|
normal file system on to a windows one that cannot support the same |
|
|
characters. If you want to risk it, remove the file |
|
|
rdiff-backup-data/chars_to_quote. |
|
|
""") |
|
|
|
|
152 |
def set_ownership(self, testdir): |
def set_ownership(self, testdir): |
153 |
"""Set self.ownership to true iff testdir's ownership can be changed""" |
"""Set self.ownership to true iff testdir's ownership can be changed""" |
154 |
tmp_rp = testdir.append("foo") |
tmp_rp = testdir.append("foo") |
157 |
try: |
try: |
158 |
tmp_rp.chown(uid+1, gid+1) # just choose random uid/gid |
tmp_rp.chown(uid+1, gid+1) # just choose random uid/gid |
159 |
tmp_rp.chown(0, 0) |
tmp_rp.chown(0, 0) |
160 |
except (IOError, OSError), exc: self.ownership = 0 |
except (IOError, OSError): self.ownership = 0 |
161 |
else: self.ownership = 1 |
else: self.ownership = 1 |
162 |
tmp_rp.delete() |
tmp_rp.delete() |
163 |
|
|
170 |
hl_dest.hardlink(hl_source.path) |
hl_dest.hardlink(hl_source.path) |
171 |
if hl_source.getinode() != hl_dest.getinode(): |
if hl_source.getinode() != hl_dest.getinode(): |
172 |
raise IOError(errno.EOPNOTSUPP, "Hard links don't compare") |
raise IOError(errno.EOPNOTSUPP, "Hard links don't compare") |
173 |
except (IOError, OSError), exc: |
except (IOError, OSError): |
174 |
log.Log("Warning: hard linking not supported by filesystem " |
log.Log("Warning: hard linking not supported by filesystem " |
175 |
"at %s" % (self.root_rp.path,), 3) |
"at %s" % (self.root_rp.path,), 3) |
176 |
self.hardlinks = 0 |
self.hardlinks = 0 |
180 |
"""Set self.fsync_dirs if directories can be fsync'd""" |
"""Set self.fsync_dirs if directories can be fsync'd""" |
181 |
assert testdir.conn is Globals.local_connection |
assert testdir.conn is Globals.local_connection |
182 |
try: testdir.fsync() |
try: testdir.fsync() |
183 |
except (IOError, OSError), exc: |
except (IOError, OSError): |
184 |
log.Log("Directories on file system at %s are not fsyncable.\n" |
log.Log("Directories on file system at %s are not fsyncable.\n" |
185 |
"Assuming it's unnecessary." % (testdir.path,), 4) |
"Assuming it's unnecessary." % (testdir.path,), 4) |
186 |
self.fsync_dirs = 0 |
self.fsync_dirs = 0 |
187 |
else: self.fsync_dirs = 1 |
else: self.fsync_dirs = 1 |
188 |
|
|
189 |
def set_chars_to_quote(self, subdir): |
def set_extended_filenames(self, subdir): |
190 |
"""Set self.chars_to_quote by trying to write various paths""" |
"""Set self.extended_filenames by trying to write a path""" |
191 |
def is_case_sensitive(): |
assert not self.read_only |
192 |
"""Return true if file system is case sensitive""" |
|
193 |
upper_a = subdir.append("A") |
# Make sure ordinary filenames ok |
194 |
upper_a.touch() |
ordinary_filename = '5-_ a.' |
195 |
lower_a = subdir.append("a") |
ord_rp = subdir.append(ordinary_filename) |
196 |
if lower_a.lstat(): |
ord_rp.touch() |
197 |
lower_a.delete() |
assert ord_rp.lstat() |
198 |
upper_a.setdata() |
ord_rp.delete() |
|
assert not upper_a.lstat() |
|
|
self.case_sensitive = 0 |
|
|
else: |
|
|
upper_a.delete() |
|
|
self.case_sensitive = 1 |
|
|
return self.case_sensitive |
|
|
|
|
|
def supports_unusual_chars(): |
|
|
"""Test handling of several chars sometimes not supported""" |
|
|
for filename in [':', '\\', chr(175)]: |
|
|
try: |
|
|
rp = subdir.append(filename) |
|
|
rp.touch() |
|
|
except (IOError, OSError): |
|
|
assert not rp.lstat() |
|
|
return 0 |
|
|
else: |
|
|
assert rp.lstat() |
|
|
rp.delete() |
|
|
return 1 |
|
199 |
|
|
200 |
def sanity_check(): |
extended_filename = ':\\' + chr(175) |
201 |
"""Make sure basic filenames writable""" |
try: |
202 |
for filename in ['5-_ a.']: |
ext_rp = subdir.append(extended_filename) |
203 |
rp = subdir.append(filename) |
ext_rp.touch() |
204 |
rp.touch() |
except (IOError, OSError): |
205 |
assert rp.lstat() |
assert not ext_rp.lstat() |
206 |
rp.delete() |
self.extended_filenames = 0 |
|
|
|
|
sanity_check() |
|
|
if is_case_sensitive(): |
|
|
if supports_unusual_chars(): self.chars_to_quote = "" |
|
|
else: self.chars_to_quote = "^A-Za-z0-9_ -." |
|
207 |
else: |
else: |
208 |
if supports_unusual_chars(): self.chars_to_quote = "A-Z;" |
assert ext_rp.lstat() |
209 |
else: self.chars_to_quote = "^a-z0-9_ -." |
ext_rp.delete() |
210 |
|
self.extended_filenames = 1 |
211 |
|
|
212 |
def set_acls(self, rp): |
def set_acls(self, rp): |
213 |
"""Set self.acls based on rp. Does not write. Needs to be local""" |
"""Set self.acls based on rp. Does not write. Needs to be local""" |
222 |
return |
return |
223 |
|
|
224 |
try: posix1e.ACL(file=rp.path) |
try: posix1e.ACL(file=rp.path) |
225 |
except IOError, exc: |
except IOError: |
226 |
log.Log("ACLs not supported by filesystem at %s" % (rp.path,), 4) |
log.Log("ACLs not supported by filesystem at %s" % (rp.path,), 4) |
227 |
self.acls = 0 |
self.acls = 0 |
228 |
else: self.acls = 1 |
else: self.acls = 1 |
229 |
|
|
230 |
|
def set_case_sensitive_readwrite(self, subdir): |
231 |
|
"""Determine if directory at rp is case sensitive by writing""" |
232 |
|
assert not self.read_only |
233 |
|
upper_a = subdir.append("A") |
234 |
|
upper_a.touch() |
235 |
|
lower_a = subdir.append("a") |
236 |
|
if lower_a.lstat(): |
237 |
|
lower_a.delete() |
238 |
|
upper_a.setdata() |
239 |
|
assert not upper_a.lstat() |
240 |
|
self.case_sensitive = 0 |
241 |
|
else: |
242 |
|
upper_a.delete() |
243 |
|
self.case_sensitive = 1 |
244 |
|
|
245 |
def set_case_sensitive_readonly(self, rp): |
def set_case_sensitive_readonly(self, rp): |
246 |
"""Determine if directory at rp is case sensitive without writing""" |
"""Determine if directory at rp is case sensitive without writing""" |
247 |
def find_letter(subdir): |
def find_letter(subdir): |
301 |
if write: |
if write: |
302 |
xattr.setxattr(rp.path, "user.test", "test val") |
xattr.setxattr(rp.path, "user.test", "test val") |
303 |
assert xattr.getxattr(rp.path, "user.test") == "test val" |
assert xattr.getxattr(rp.path, "user.test") == "test val" |
304 |
except IOError, exc: |
except IOError: |
305 |
log.Log("Extended attributes not supported by " |
log.Log("Extended attributes not supported by " |
306 |
"filesystem at %s" % (rp.path,), 4) |
"filesystem at %s" % (rp.path,), 4) |
307 |
self.eas = 0 |
self.eas = 0 |
354 |
fp_read = open(os.path.join(reg_rp.path, '..namedfork', 'rsrc'), 'rb') |
fp_read = open(os.path.join(reg_rp.path, '..namedfork', 'rsrc'), 'rb') |
355 |
s_back = fp_read.read() |
s_back = fp_read.read() |
356 |
assert not fp_read.close() |
assert not fp_read.close() |
357 |
except (OSError, IOError), e: self.resource_forks = 0 |
except (OSError, IOError): self.resource_forks = 0 |
358 |
else: self.resource_forks = (s_back == s) |
else: self.resource_forks = (s_back == s) |
359 |
reg_rp.delete() |
reg_rp.delete() |
360 |
|
|
373 |
fp = rfork.open('rb') |
fp = rfork.open('rb') |
374 |
fp.read() |
fp.read() |
375 |
assert not fp.close() |
assert not fp.close() |
376 |
except (OSError, IOError), e: |
except (OSError, IOError): |
377 |
self.resource_forks = 0 |
self.resource_forks = 0 |
378 |
return |
return |
379 |
self.resource_forks = 1 |
self.resource_forks = 1 |
387 |
try: |
try: |
388 |
tmp_rp.chmod(07000) |
tmp_rp.chmod(07000) |
389 |
tmp_rp.chmod(07777) |
tmp_rp.chmod(07777) |
390 |
except (OSError, IOError), e: self.high_perms = 0 |
except (OSError, IOError): self.high_perms = 0 |
391 |
else: self.high_perms = 1 |
else: self.high_perms = 1 |
392 |
tmp_rp.delete() |
tmp_rp.delete() |
393 |
|
|
394 |
def get_fsabilities_readonly(desc_string, rp): |
def get_readonly_fsa(desc_string, rp): |
395 |
"""Return an FSAbilities object with given description_string |
"""Return an fsa with given description_string |
396 |
|
|
397 |
Will be initialized read_only with given RPath rp. |
Will be initialized read_only with given RPath rp. We separate |
398 |
|
this out into a separate function so the request can be vetted by |
399 |
|
the security module. |
400 |
|
|
401 |
""" |
""" |
402 |
return FSAbilities(desc_string).init_readonly(rp) |
return FSAbilities(desc_string).init_readonly(rp) |
403 |
|
|
404 |
def get_fsabilities_readwrite(desc_string, rb, use_ctq_file = 1, ctq = None): |
|
405 |
"""Like above but initialize read/write and pass other arguments""" |
class SetGlobals: |
406 |
return FSAbilities(desc_string).init_readwrite( |
"""Various functions for setting Globals vars given FSAbilities above |
407 |
rb, use_ctq_file = use_ctq_file, override_chars_to_quote = ctq) |
|
408 |
|
Container for BackupSetGlobals and RestoreSetGlobals (don't use directly) |
409 |
def get_fsabilities_restoresource(rp): |
|
410 |
"""Used when restoring, get abilities of source directory""" |
""" |
411 |
fsa = FSAbilities('source').init_readonly(rp) |
def __init__(self, in_conn, out_conn, src_fsa, dest_fsa): |
412 |
ctq_rp = rp.append("chars_to_quote") |
"""Just store some variables for use below""" |
413 |
if ctq_rp.lstat(): fsa.chars_to_quote = ctq_rp.get_data() |
self.in_conn, self.out_conn = in_conn, out_conn |
414 |
else: fsa.chars_to_quote = "" |
self.src_fsa, self.dest_fsa = src_fsa, dest_fsa |
415 |
return fsa |
|
416 |
|
def set_eas(self): |
417 |
|
self.update_triple(self.src_fsa.eas, self.dest_fsa.eas, |
418 |
|
('eas_active', 'eas_write', 'eas_conn')) |
419 |
|
|
420 |
|
def set_acls(self): |
421 |
|
self.update_triple(self.src_fsa.acls, self.dest_fsa.acls, |
422 |
|
('acls_active', 'acls_write', 'acls_conn')) |
423 |
|
if Globals.never_drop_acls and not Globals.acls_active: |
424 |
|
Log.FatalError("--never-drop-acls specified, but ACL support\n" |
425 |
|
"missing from destination filesystem") |
426 |
|
|
427 |
|
def set_resource_forks(self): |
428 |
|
self.update_triple(self.src_fsa.resource_forks, |
429 |
|
self.dest_fsa.resource_forks, |
430 |
|
('resource_forks_active', 'resource_forks_write', |
431 |
|
'resource_forks_conn')) |
432 |
|
|
433 |
|
def set_carbonfile(self): |
434 |
|
self.update_triple(self.src_fsa.carbonfile, self.dest_fsa.carbonfile, |
435 |
|
('carbonfile_active', 'carbonfile_write', 'carbonfile_conn')) |
436 |
|
if self.src_fsa.carbonfile and not Globals.carbonfile_active: |
437 |
|
Log("Source may have carbonfile support, but support defaults to " |
438 |
|
"off.\n Use --carbonfile to enable.", 5) |
439 |
|
|
440 |
|
def set_hardlinks(self): |
441 |
|
if Globals.preserve_hardlinks != 0: |
442 |
|
SetConnections.UpdateGlobal('preserve_hardlinks', |
443 |
|
self.dest_fsa.hardlinks) |
444 |
|
|
445 |
|
def set_fsync_directories(self): |
446 |
|
SetConnections.UpdateGlobal('fsync_directories', |
447 |
|
self.dest_fsa.fsync_dirs) |
448 |
|
|
449 |
|
def set_change_ownership(self): |
450 |
|
SetConnections.UpdateGlobal('change_ownership', |
451 |
|
self.dest_fsa.ownership) |
452 |
|
|
453 |
|
def set_high_perms(self): |
454 |
|
if not self.dest_fsa.high_perms: |
455 |
|
SetConnections.UpdateGlobal('permission_mask', 0777) |
456 |
|
|
457 |
|
|
458 |
|
class BackupSetGlobals(SetGlobals): |
459 |
|
"""Functions for setting fsa related globals for backup session""" |
460 |
|
def update_triple(self, src_support, dest_support, attr_triple): |
461 |
|
"""Many of the settings have a common form we can handle here""" |
462 |
|
active_attr, write_attr, conn_attr = attr_triple |
463 |
|
if Globals.get(active_attr) == 0: return # don't override 0 |
464 |
|
for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) |
465 |
|
if not src_support: return # if source doesn't support, nothing |
466 |
|
SetConnections.UpdateGlobal(active_attr, 1) |
467 |
|
self.in_conn.Globals.set_local(conn_attr, 1) |
468 |
|
if dest_support: |
469 |
|
SetConnections.UpdateGlobal(write_attr, 1) |
470 |
|
self.out_conn.Globals.set_local(conn_attr, 1) |
471 |
|
|
472 |
|
def set_chars_to_quote(self, rbdir): |
473 |
|
"""Set chars_to_quote setting for backup session |
474 |
|
|
475 |
|
Unlike the other options, the chars_to_quote setting also |
476 |
|
depends on the current settings in the rdiff-backup-data |
477 |
|
directory, not just the current fs features. |
478 |
|
|
479 |
|
""" |
480 |
|
ctq = self.compare_ctq_file(rbdir, self.get_ctq_from_fsas()) |
481 |
|
|
482 |
|
SetConnections.UpdateGlobal('chars_to_quote', ctq) |
483 |
|
if Globals.chars_to_quote: FilenameMapping.set_init_quote_vals() |
484 |
|
|
485 |
|
def get_ctq_from_fsas(self): |
486 |
|
"""Determine chars_to_quote just from filesystems, no ctq file""" |
487 |
|
if not self.src_fsa.case_sensitive and self.dest_fsa.case_sensitive: |
488 |
|
if self.dest_fsa.extended_filenames: |
489 |
|
return "A-Z;" # Quote upper case and quoting char |
490 |
|
else: return "^a-z0-9_ -." # quote everything but basic chars |
491 |
|
|
492 |
|
if self.dest_fsa.extended_filenames: |
493 |
|
return "" # Don't quote anything |
494 |
|
else: return "^A-Za-z0-9_ -." |
495 |
|
|
496 |
|
def compare_ctq_file(self, rbdir, suggested_ctq): |
497 |
|
"""Compare ctq file with suggested result, return actual ctq""" |
498 |
|
ctq_rp = rbdir.append("chars_to_quote") |
499 |
|
if not ctq_rp.lstat(): |
500 |
|
if Globals.chars_to_quote is None: actual_ctq = suggested_ctq |
501 |
|
else: actual_ctq = Globals.chars_to_quote |
502 |
|
ctq_rp.write_string(actual_ctq) |
503 |
|
return actual_ctq |
504 |
|
|
505 |
|
if Globals.chars_to_quote is None: actual_ctq = ctq_rp.get_data() |
506 |
|
else: actual_ctq = Globals.chars_to_quote # Globals override |
507 |
|
|
508 |
|
if actual_ctq == suggested_ctq: return actual_ctq |
509 |
|
if suggested_ctq == "": |
510 |
|
log.Log("Warning: File system no longer needs quoting, " |
511 |
|
"but we will retain for backwards compatibility.", 2) |
512 |
|
return actual_ctq |
513 |
|
log.Log.FatalError("""New quoting requirements! |
514 |
|
|
515 |
|
The quoting this session appears to need do not match those in |
516 |
|
|
517 |
|
%s |
518 |
|
|
519 |
|
This may be caused when you copy an rdiff-backup repository from a |
520 |
|
normal file system onto a windows one that cannot support the same |
521 |
|
characters, or if you backup a case-sensitive file system onto a |
522 |
|
case-insensitive one that previously only had case-insensitive ones |
523 |
|
backed up onto it. |
524 |
|
|
525 |
|
If you want to risk it, remove the file |
526 |
|
rdiff-backup-data/chars_to_quote.""" % (ctq_rp.path,)) |
527 |
|
|
528 |
|
|
529 |
|
class RestoreSetGlobals(SetGlobals): |
530 |
|
"""Functions for setting fsa-related globals for restore session""" |
531 |
|
def update_triple(self, src_support, dest_support, attr_triple): |
532 |
|
"""Update global settings for feature based on fsa results |
533 |
|
|
534 |
|
This is slightly different from BackupSetGlobals.update_triple |
535 |
|
because (using the mirror_metadata file) rpaths from the |
536 |
|
source may have more information than the file system |
537 |
|
supports. |
538 |
|
|
539 |
|
""" |
540 |
|
active_attr, write_attr, conn_attr = attr_triple |
541 |
|
if Globals.get(active_attr) == 0: return # don't override 0 |
542 |
|
for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) |
543 |
|
if not dest_support: return # if dest doesn't support, do nothing |
544 |
|
SetConnections.UpdateGlobal(active_attr, 1) |
545 |
|
self.out_conn.Globals.set_local(conn_attr, 1) |
546 |
|
self.out_conn.Globals.set_local(write_attr, 1) |
547 |
|
if src_support: self.in_conn.Globals.set_local(conn_attr, 1) |
548 |
|
|
549 |
|
def set_chars_to_quote(self, rbdir): |
550 |
|
"""Set chars_to_quote from rdiff-backup-data dir""" |
551 |
|
if Globals.chars_to_quote is not None: return # already overridden |
552 |
|
|
553 |
|
ctq_rp = rbdir.append("chars_to_quote") |
554 |
|
if ctq_rp.lstat(): |
555 |
|
SetConnections.UpdateGlobal("chars_to_quote", ctq_rp.get_data()) |
556 |
|
else: |
557 |
|
log.Log("Warning: chars_to_quote file not found,\n" |
558 |
|
"assuming no quoting in backup repository.", 2) |
559 |
|
SetConnections.UpdateGlobal("chars_to_quote", "") |
560 |
|
|
561 |
|
|
562 |
|
class SingleSetGlobals(RestoreSetGlobals): |
563 |
|
"""For setting globals when dealing only with one filesystem""" |
564 |
|
def __init__(self, conn, fsa): |
565 |
|
self.conn = conn |
566 |
|
self.dest_fsa = fsa |
567 |
|
|
568 |
|
def update_triple(self, fsa_support, attr_triple): |
569 |
|
"""Update global vars from single fsa test""" |
570 |
|
active_attr, write_attr, conn_attr = attr_triple |
571 |
|
if Globals.get(active_attr) == 0: return # don't override 0 |
572 |
|
for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) |
573 |
|
if not fsa_support: return |
574 |
|
SetConnections.UpdateGlobal(active_attr, 1) |
575 |
|
SetConnections.UpdateGlobal(write_attr, 1) |
576 |
|
self.conn.Globals.set_local(conn_attr, 1) |
577 |
|
|
578 |
|
def set_eas(self): |
579 |
|
self.update_triple(self.dest_fsa.eas, |
580 |
|
('eas_active', 'eas_write', 'eas_conn')) |
581 |
|
def set_acls(self): |
582 |
|
self.update_triple(self.dest_fsa.acls, |
583 |
|
('acls_active', 'acls_write', 'acls_conn')) |
584 |
|
def set_resource_forks(self): |
585 |
|
self.update_triple(self.dest_fsa.resource_forks, |
586 |
|
('resource_forks_active', |
587 |
|
'resource_forks_write', 'resource_forks_conn')) |
588 |
|
def set_carbonfile(self): |
589 |
|
self.update_triple(self.dest_fsa.carbonfile, |
590 |
|
('carbonfile_active', 'carbonfile_write', 'carbonfile_conn')) |
591 |
|
|
592 |
|
|
593 |
|
def backup_set_globals(rpin): |
594 |
|
"""Given rps for source filesystem and repository, set fsa globals |
595 |
|
|
596 |
|
This should be run on the destination connection, because we may |
597 |
|
need to write a new chars_to_quote file. |
598 |
|
|
599 |
|
""" |
600 |
|
assert Globals.rbdir.conn is Globals.local_connection |
601 |
|
src_fsa = rpin.conn.fs_abilities.get_readonly_fsa('source', rpin) |
602 |
|
log.Log(str(src_fsa), 4) |
603 |
|
dest_fsa = FSAbilities('destination').init_readwrite(Globals.rbdir) |
604 |
|
log.Log(str(dest_fsa), 4) |
605 |
|
|
606 |
|
bsg = BackupSetGlobals(rpin.conn, Globals.rbdir.conn, src_fsa, dest_fsa) |
607 |
|
bsg.set_eas() |
608 |
|
bsg.set_acls() |
609 |
|
bsg.set_resource_forks() |
610 |
|
bsg.set_carbonfile() |
611 |
|
bsg.set_hardlinks() |
612 |
|
bsg.set_fsync_directories() |
613 |
|
bsg.set_change_ownership() |
614 |
|
bsg.set_high_perms() |
615 |
|
bsg.set_chars_to_quote(Globals.rbdir) |
616 |
|
|
617 |
|
def restore_set_globals(rpout): |
618 |
|
"""Set fsa related globals for restore session, given in/out rps""" |
619 |
|
assert rpout.conn is Globals.local_connection |
620 |
|
src_fsa = Globals.rbdir.conn.fs_abilities.get_readonly_fsa( |
621 |
|
'rdiff-backup repository', Globals.rbdir) |
622 |
|
log.Log(str(src_fsa), 4) |
623 |
|
dest_fsa = FSAbilities('restore target').init_readwrite(rpout) |
624 |
|
log.Log(str(dest_fsa), 4) |
625 |
|
|
626 |
|
rsg = RestoreSetGlobals(Globals.rbdir.conn, rpout.conn, src_fsa, dest_fsa) |
627 |
|
rsg.set_eas() |
628 |
|
rsg.set_acls() |
629 |
|
rsg.set_resource_forks() |
630 |
|
rsg.set_carbonfile() |
631 |
|
rsg.set_hardlinks() |
632 |
|
# No need to fsync anything when restoring |
633 |
|
rsg.set_change_ownership() |
634 |
|
rsg.set_high_perms() |
635 |
|
rsg.set_chars_to_quote(Globals.rbdir) |
636 |
|
|
637 |
|
def single_set_globals(rp, read_only = None): |
638 |
|
"""Set fsa related globals for operation on single filesystem""" |
639 |
|
if read_only: |
640 |
|
fsa = rp.conn.fs_abilities.get_readonly_fsa(rp.path, rp) |
641 |
|
else: fsa = FSAbilities(rp.path).init_readwrite(rp) |
642 |
|
log.Log(str(fsa), 4) |
643 |
|
|
644 |
|
ssg = SingleSetGlobals(rp.conn, fsa) |
645 |
|
ssg.set_eas() |
646 |
|
ssg.set_acls() |
647 |
|
ssg.set_resource_forks() |
648 |
|
ssg.set_carbonfile() |
649 |
|
if not read_only: |
650 |
|
ssg.set_hardlinks() |
651 |
|
ssg.set_change_ownership() |
652 |
|
ssg.set_high_perms() |
653 |
|
ssg.set_chars_to_quote(Globals.rbdir) |
654 |
|
|