Commit e73f5576 authored by owsla's avatar owsla

First pass at fixing escape DOS devices and trailing spaces/periods.


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@991 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent 507c7be3
New in v1.2.5 (????/??/??)
---------------------------
Fix escape DOS devices and escape trailing spaces/periods. (Andrew Ferguson)
New in v1.2.4 (2009/01/01) New in v1.2.4 (2009/01/01)
--------------------------- ---------------------------
......
...@@ -85,18 +85,17 @@ def quote(path): ...@@ -85,18 +85,17 @@ def quote(path):
""" """
QuotedPath = chars_to_quote_regexp.sub(quote_single, path) QuotedPath = chars_to_quote_regexp.sub(quote_single, path)
if not Globals.must_escape_dos_devices \ if not Globals.escape_dos_devices and not Globals.escape_trailing_spaces:
and not Globals.must_escape_trailing_spaces:
return QuotedPath return QuotedPath
# Escape a trailing space or period (invalid in names on FAT32 under DOS, # Escape a trailing space or period (invalid in names on FAT32 under DOS,
# Windows and modern Linux) # Windows and modern Linux)
if Globals.must_escape_trailing_spaces: if Globals.escape_trailing_spaces:
if len(QuotedPath) and (QuotedPath[-1] == ' ' or QuotedPath[-1] == '.'): if len(QuotedPath) and (QuotedPath[-1] == ' ' or QuotedPath[-1] == '.'):
QuotedPath = QuotedPath[:-1] + \ QuotedPath = QuotedPath[:-1] + \
"%s%03d" % (quoting_char, ord(QuotedPath[-1])) "%s%03d" % (quoting_char, ord(QuotedPath[-1]))
if not Globals.must_escape_dos_devices: if not Globals.escape_dos_devices:
return QuotedPath return QuotedPath
# Escape first char of any special DOS device files even if filename has an # Escape first char of any special DOS device files even if filename has an
......
...@@ -860,7 +860,8 @@ def checkdest_need_check(dest_rp): ...@@ -860,7 +860,8 @@ def checkdest_need_check(dest_rp):
"""Return None if no dest dir found, 1 if dest dir needs check, 0 o/w""" """Return None if no dest dir found, 1 if dest dir needs check, 0 o/w"""
if not dest_rp.isdir() or not Globals.rbdir.isdir(): return None if not dest_rp.isdir() or not Globals.rbdir.isdir(): return None
for filename in Globals.rbdir.listdir(): for filename in Globals.rbdir.listdir():
if filename not in ['chars_to_quote', 'backup.log']: break if filename not in ['chars_to_quote', 'special_escapes',
'backup.log']: break
else: # This may happen the first backup just after we test for quoting else: # This may happen the first backup just after we test for quoting
return None return None
curmirroot = Globals.rbdir.append("current_mirror") curmirroot = Globals.rbdir.append("current_mirror")
......
...@@ -50,8 +50,8 @@ class FSAbilities: ...@@ -50,8 +50,8 @@ class FSAbilities:
high_perms = None # True if suid etc perms are (read/write) supported high_perms = None # True if suid etc perms are (read/write) supported
escape_dos_devices = None # True if dos device files can't be created (e.g., escape_dos_devices = None # True if dos device files can't be created (e.g.,
# aux, con, com1, etc) # aux, con, com1, etc)
escape_trailing_spaces = None # True if files with trailing spaces or escape_trailing_spaces = None # True if trailing spaces or periods at the
# periods can't be created # end of filenames aren't preserved
symlink_perms = None # True if symlink perms are affected by umask symlink_perms = None # True if symlink perms are affected by umask
def __init__(self, name = None): def __init__(self, name = None):
...@@ -131,7 +131,7 @@ class FSAbilities: ...@@ -131,7 +131,7 @@ class FSAbilities:
self.set_carbonfile() self.set_carbonfile()
self.set_case_sensitive_readonly(rp) self.set_case_sensitive_readonly(rp)
self.set_escape_dos_devices(rp) self.set_escape_dos_devices(rp)
self.set_escape_trailing_spaces(rp) self.set_escape_trailing_spaces_readonly(rp)
return self return self
def init_readwrite(self, rbdir): def init_readwrite(self, rbdir):
...@@ -166,7 +166,7 @@ class FSAbilities: ...@@ -166,7 +166,7 @@ class FSAbilities:
self.set_high_perms_readwrite(subdir) self.set_high_perms_readwrite(subdir)
self.set_symlink_perms(subdir) self.set_symlink_perms(subdir)
self.set_escape_dos_devices(subdir) self.set_escape_dos_devices(subdir)
self.set_escape_trailing_spaces(subdir) self.set_escape_trailing_spaces_readwrite(subdir)
subdir.delete() subdir.delete()
return self return self
...@@ -554,44 +554,88 @@ class FSAbilities: ...@@ -554,44 +554,88 @@ class FSAbilities:
sym_source.delete() sym_source.delete()
def set_escape_dos_devices(self, subdir): def set_escape_dos_devices(self, subdir):
"""If special file con can be stat'd, escape special files""" """Test if DOS device files can be used as filenames.
This test must detect if the underlying OS is Windows, whehter we are
running under Cygwin or natively. Cygwin allows these special files to
be stat'd from any directory. Native Windows returns OSError (like
non-Cygwin POSIX), but we can check for that using os.name.
Note that 'con' and 'aux' have some unusual behaviors as shown below.
os.lstat() | con aux prn
-------------+-------------------------------------
Unix | OSError,2 OSError,2 OSError,2
Cygwin/NTFS | -success- -success- -success-
Cygwin/FAT32 | -success- -HANGS-
Native Win | WinError,2 WinError,87 WinError,87
"""
if os.name == "nt":
self.escape_dos_devices = 1
return
try: try:
device_rp = subdir.append("con") device_rp = subdir.append("con")
if device_rp.lstat(): if device_rp.lstat():
log.Log("escape_dos_devices required by filesystem at %s" \
% (subdir.path), 4)
self.escape_dos_devices = 1 self.escape_dos_devices = 1
else: else:
log.Log("escape_dos_devices not required by filesystem at %s" \
% (subdir.path), 4)
self.escape_dos_devices = 0 self.escape_dos_devices = 0
except(OSError): except(OSError):
log.Log("escape_dos_devices required by filesystem at %s" \
% (subdir.path), 4)
self.escape_dos_devices = 1 self.escape_dos_devices = 1
def set_escape_trailing_spaces(self, subdir): def set_escape_trailing_spaces_readwrite(self, testdir):
# Disable this for 1.2.4 """Windows and Linux/FAT32 will not preserve trailing spaces or periods.
Linux/FAT32 behaves inconsistently: It will give an OSError,22 if
os.mkdir() is called on a directory name with a space at the end, but
will give an IOError("invalid mode") if you attempt to create a filename
with a space at the end. However, if a period is placed at the end of
the name, Linux/FAT32 is consistent with Cygwin and Native Windows.
"""
period_rp = testdir.append("foo.")
assert not period_rp.lstat()
tmp_rp = testdir.append("foo")
tmp_rp.touch()
assert tmp_rp.lstat()
period_rp.setdata()
if period_rp.lstat():
self.escape_trailing_spaces = 1
else:
self.escape_trailing_spaces = 0 self.escape_trailing_spaces = 0
return
"""If file with trailing space can't be created, escape such files""" tmp_rp.delete()
def set_escape_trailing_spaces_readonly(self, rp):
"""Determine if directory at rp permits filenames with trailing
spaces or periods without writing."""
def test_period(dir_rp, dirlist):
"""Return 1 if trailing spaces and periods should be escaped"""
filename = dirlist[0]
try: try:
space_rp = subdir.append("test ") test_rp = dir_rp.append(filename)
space_rp.touch() except OSError:
if space_rp.lstat(): return 0
log.Log("escape_trailing_spaces not required by filesystem " \ assert test_rp.lstat(), test_rp
"at %s" % (subdir.path), 4) period = filename + '.'
self.escape_trailing_spaces = 0 if period in dirlist: return 0
space_rp.delete()
period_rp = dir_rp.append(period)
if period_rp.lstat(): return 1
return 0
dirlist = robust.listrp(rp)
if len(dirlist):
self.escape_trailing_spaces = test_period(rp, dirlist)
else: else:
log.Log("escape_trailing_spaces required by filesystem at %s" \ log.Log("Warning: could not determine if source directory at\n "
% (subdir.path), 4) + rp.path + "\npermits trailing spaces or periods in "
self.escape_trailing_spaces = 1 "filenames because we can't find any files.\n"
except (OSError, IOError): "It will be treated as permitting such files.", 2)
log.Log("escape_trailing_spaces required by filesystem at %s" \ self.escape_trailing_spaces = 0
% (subdir.path), 4)
self.escape_trailing_spaces = 1
def get_readonly_fsa(desc_string, rp): def get_readonly_fsa(desc_string, rp):
...@@ -665,14 +709,6 @@ class SetGlobals: ...@@ -665,14 +709,6 @@ class SetGlobals:
SetConnections.UpdateGlobal('symlink_perms', SetConnections.UpdateGlobal('symlink_perms',
self.dest_fsa.symlink_perms) self.dest_fsa.symlink_perms)
def set_escape_dos_devices(self):
SetConnections.UpdateGlobal('escape_dos_devices', \
self.dest_fsa.escape_dos_devices)
def set_escape_trailing_spaces(self):
SetConnections.UpdateGlobal('escape_trailing_spaces', \
self.dest_fsa.escape_trailing_spaces)
class BackupSetGlobals(SetGlobals): class BackupSetGlobals(SetGlobals):
"""Functions for setting fsa related globals for backup session""" """Functions for setting fsa related globals for backup session"""
def update_triple(self, src_support, dest_support, attr_triple): def update_triple(self, src_support, dest_support, attr_triple):
...@@ -687,43 +723,50 @@ class BackupSetGlobals(SetGlobals): ...@@ -687,43 +723,50 @@ class BackupSetGlobals(SetGlobals):
SetConnections.UpdateGlobal(write_attr, 1) SetConnections.UpdateGlobal(write_attr, 1)
self.out_conn.Globals.set_local(conn_attr, 1) self.out_conn.Globals.set_local(conn_attr, 1)
def set_must_escape_dos_devices(self, rbdir): def set_special_escapes(self, rbdir):
"""If local edd or src edd, then must escape """ """Escaping DOS devices and trailing periods/spaces works like
try: regular filename escaping. If only the destination requires it,
device_rp = rbdir.append("con") then we do it. Otherwise, it is not necessary, since the files
if device_rp.lstat(): local_edd = 1 couldn't have been created in the first place. We also record
else: local_edd = 0 whether we have done it in order to handle the case where a
except (OSError): local_edd = 1 volume which was escaped is later restored by an OS that does
SetConnections.UpdateGlobal('must_escape_dos_devices', \ not require it."""
self.src_fsa.escape_dos_devices or local_edd)
log.Log("Backup: must_escape_dos_devices = %d" % \ suggested_edd = (self.dest_fsa.escape_dos_devices and not \
(self.src_fsa.escape_dos_devices or local_edd), 4) self.src_fsa.escape_dos_devices)
suggested_ets = (self.dest_fsa.escape_trailing_spaces and not \
def set_must_escape_trailing_spaces(self, rbdir): self.src_fsa.escape_trailing_spaces)
"""If local ets or src ets, then must escape """
# Disable this for 1.2.4 se_rp = rbdir.append("special_escapes")
SetConnections.UpdateGlobal('must_escape_trailing_spaces', 0) if not se_rp.lstat():
return actual_edd, actual_ets = suggested_edd, suggested_ets
se = ""
try: if actual_edd: se = se + "escape_dos_devices\n"
space_rp = rbdir.append("test ") if actual_ets: se = se + "escape_trailing_spaces\n"
space_rp.touch() se_rp.write_string(se)
if space_rp.lstat():
local_ets = 0
space_rp.delete()
else: else:
local_ets = 1 se = se_rp.get_data().split("\n")
except (OSError, IOError): actual_edd = ("escape_dos_devices" in se)
local_ets = 1 actual_ets = ("escape_trailing_spaces" in se)
SetConnections.UpdateGlobal('must_escape_trailing_spaces', \
self.src_fsa.escape_trailing_spaces or local_ets) if actual_edd != suggested_edd and not suggested_edd:
log.Log("Backup: must_escape_trailing_spaces = %d" % \ log.Log("Warning: System no longer needs DOS devices escaped, "
(self.src_fsa.escape_trailing_spaces or local_ets), 4) "but we will retain for backwards compatibility.", 2)
if actual_ets != suggested_ets and not suggested_ets:
log.Log("Warning: System no longer needs trailing spaces or "
"periods escaped, but we will retain for backwards "
"compatibility.", 2)
SetConnections.UpdateGlobal('escape_dos_devices', actual_edd)
log.Log("Backup: escape_dos_devices = %d" % actual_edd, 4)
SetConnections.UpdateGlobal('escape_trailing_spaces', actual_ets)
log.Log("Backup: escape_trailing_spaces = %d" % actual_ets, 4)
def set_chars_to_quote(self, rbdir, force): def set_chars_to_quote(self, rbdir, force):
"""Set chars_to_quote setting for backup session """Set chars_to_quote setting for backup session
Unlike the other options, the chars_to_quote setting also Unlike most other options, the chars_to_quote setting also
depends on the current settings in the rdiff-backup-data depends on the current settings in the rdiff-backup-data
directory, not just the current fs features. directory, not just the current fs features.
...@@ -818,45 +861,39 @@ class RestoreSetGlobals(SetGlobals): ...@@ -818,45 +861,39 @@ class RestoreSetGlobals(SetGlobals):
self.out_conn.Globals.set_local(write_attr, 1) self.out_conn.Globals.set_local(write_attr, 1)
if src_support: self.in_conn.Globals.set_local(conn_attr, 1) if src_support: self.in_conn.Globals.set_local(conn_attr, 1)
def set_must_escape_dos_devices(self, rbdir): def set_special_escapes(self, rbdir):
"""If local edd or src edd, then must escape """ """Set escape_dos_devices and escape_trailing_spaces from
rdiff-backup-data dir, just like chars_to_quote"""
se_rp = rbdir.append("special_escapes")
if se_rp.lstat():
se = se_rp.get_data().split("\n")
actual_edd = ("escape_dos_devices" in se)
actual_ets = ("escape_trailing_spaces" in se)
else:
log.Log("Warning: special_escapes file not found,\n"
"will assume need to escape DOS devices and trailing "
"spaces based on file systems.", 2)
if getattr(self, "src_fsa", None) is not None: if getattr(self, "src_fsa", None) is not None:
src_edd = self.src_fsa.escape_dos_devices actual_edd = (self.src_fsa.escape_dos_devices and not
else: src_edd = 0 self.dest_fsa.escape_dos_devices)
try: try:
device_rp = rbdir.append("con") actual_ets = (self.src_fsa.escape_trailing_spaces and not
if device_rp.lstat(): local_edd = 1 self.dest_fsa.escape_trailing_spaces)
else: local_edd = 0 except AttributeError: # ETS fixed in version 1.2.5
except (OSError): local_edd = 1 actual_ets = 0
SetConnections.UpdateGlobal('must_escape_dos_devices', \
src_edd or local_edd)
log.Log("Restore: must_escape_dos_devices = %d" % \
(src_edd or local_edd), 4)
def set_must_escape_trailing_spaces(self, rbdir):
"""If local ets or src ets, then must escape """
# Disable this for 1.2.4
SetConnections.UpdateGlobal('must_escape_trailing_spaces', 0)
return
if getattr(self, "src_fsa", None) is not None:
src_ets = self.src_fsa.escape_trailing_spaces
else: else:
src_ets = 0 # Single filesystem operation
actual_edd = self.dest_fsa.escape_dos_devices
try: try:
space_rp = rbdir.append("test ") actual_ets = self.dest_fsa.escape_trailing_spaces
space_rp.touch() except AttributeError:
if space_rp.lstat(): actual_ets = 0
local_ets = 0
space_rp.delete() SetConnections.UpdateGlobal('escape_dos_devices', actual_edd)
else: log.Log("Backup: escape_dos_devices = %d" % actual_edd, 4)
local_ets = 1
except (OSError, IOError): SetConnections.UpdateGlobal('escape_trailing_spaces', actual_ets)
local_ets = 1 log.Log("Backup: escape_trailing_spaces = %d" % actual_ets, 4)
SetConnections.UpdateGlobal('must_escape_trailing_spaces', \
src_ets or local_ets)
log.Log("Restore: must_escape_trailing_spaces = %d" % \
(src_ets or local_ets), 4)
def set_chars_to_quote(self, rbdir): def set_chars_to_quote(self, rbdir):
"""Set chars_to_quote from rdiff-backup-data dir""" """Set chars_to_quote from rdiff-backup-data dir"""
...@@ -931,10 +968,7 @@ def backup_set_globals(rpin, force): ...@@ -931,10 +968,7 @@ def backup_set_globals(rpin, force):
bsg.set_high_perms() bsg.set_high_perms()
bsg.set_symlink_perms() bsg.set_symlink_perms()
update_quoting = bsg.set_chars_to_quote(Globals.rbdir, force) update_quoting = bsg.set_chars_to_quote(Globals.rbdir, force)
bsg.set_escape_dos_devices() bsg.set_special_escapes(Globals.rbdir)
bsg.set_escape_trailing_spaces()
bsg.set_must_escape_dos_devices(Globals.rbdir)
bsg.set_must_escape_trailing_spaces(Globals.rbdir)
if update_quoting and force: if update_quoting and force:
FilenameMapping.update_quoting(Globals.rbdir) FilenameMapping.update_quoting(Globals.rbdir)
...@@ -961,10 +995,7 @@ def restore_set_globals(rpout): ...@@ -961,10 +995,7 @@ def restore_set_globals(rpout):
rsg.set_high_perms() rsg.set_high_perms()
rsg.set_symlink_perms() rsg.set_symlink_perms()
rsg.set_chars_to_quote(Globals.rbdir) rsg.set_chars_to_quote(Globals.rbdir)
rsg.set_escape_dos_devices() rsg.set_special_escapes(Globals.rbdir)
rsg.set_escape_trailing_spaces()
rsg.set_must_escape_dos_devices(Globals.rbdir)
rsg.set_must_escape_trailing_spaces(Globals.rbdir)
def single_set_globals(rp, read_only = None): def single_set_globals(rp, read_only = None):
"""Set fsa related globals for operation on single filesystem""" """Set fsa related globals for operation on single filesystem"""
...@@ -984,8 +1015,5 @@ def single_set_globals(rp, read_only = None): ...@@ -984,8 +1015,5 @@ def single_set_globals(rp, read_only = None):
ssg.set_high_perms() ssg.set_high_perms()
ssg.set_symlink_perms() ssg.set_symlink_perms()
ssg.set_chars_to_quote(Globals.rbdir) ssg.set_chars_to_quote(Globals.rbdir)
ssg.set_escape_dos_devices() ssg.set_special_escapes(Globals.rbdir)
ssg.set_escape_trailing_spaces()
ssg.set_must_escape_dos_devices(Globals.rbdir)
ssg.set_must_escape_trailing_spaces(Globals.rbdir)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment