1# Copyright 2005 Ben Escoto
2#
3# This file is part of rdiff-backup.
4#
5# rdiff-backup is free software; you can redistribute it and/or modify
6# under the terms of the GNU General Public License as published by the
7# Free Software Foundation; either version 2 of the License, or (at your
8# option) any later version.
9#
10# rdiff-backup is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with rdiff-backup; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18# 02110-1301, USA
19"""Handle long filenames
20
21rdiff-backup sometimes wants to write filenames longer than allowed by
22the destination directory.  This can happen in 3 ways:
23
241)  Because the destination directory has a low maximum length limit.
252)  When the source directory has a filename close to the limit, so
26    that its increments would be above the limit.
273)  When quoting is enabled, so that even the mirror filenames are too
28    long.
29
30When rdiff-backup would otherwise write a file whose name is too long,
31instead it either skips the operation altogether (for non-regular
32files), or writes the data to a unique file in the
33rdiff-backup-data/long-filename directory.  This file will have an
34arbitrary basename, but if it's an increment the suffix will be the
35same.  The name will be recorded in the mirror_metadata so we can find
36it later.
37
38"""
39
40import errno
41from . import log, Globals, restore, regress
42
43long_name_dir = b"long_filename_data"
44rootrp = None
45
46
47def get_long_rp(base=None):
48    """Return an rpath in long name directory with given base"""
49    global rootrp
50    if not rootrp:
51        rootrp = Globals.rbdir.append(long_name_dir)
52        if not rootrp.lstat():
53            rootrp.mkdir()
54    if base:
55        return rootrp.append(base)
56    else:
57        return rootrp
58
59
60# ------------------------------------------------------------------
61# These functions used mainly for backing up
62
63# integer number of next free prefix.  Names will be created from
64# integers consecutively like '1', '2', and so on.
65free_name_counter = None
66
67# Filename which holds the next available free name in it
68counter_filename = b"next_free"
69
70
71def get_next_free():
72    """Return next free filename available in the long filename directory"""
73    global free_name_counter
74
75    def scan_next_free():
76        """Return value of free_name_counter by listing long filename dir"""
77        log.Log("Setting next free from long filenames dir", 5)
78        cur_high = 0
79        for filename in get_long_rp().listdir():
80            try:
81                i = int(filename.split(b'.')[0])
82            except ValueError:
83                continue
84            if i > cur_high:
85                cur_high = i
86        return cur_high + 1
87
88    def read_next_free():
89        """Return next int free by reading the next_free file, or None"""
90        rp = get_long_rp(counter_filename)
91        if not rp.lstat():
92            return None
93        return int(rp.get_string())
94
95    def write_next_free(i):
96        """Write value i into the counter file"""
97        rp = get_long_rp(counter_filename)
98        if rp.lstat():
99            rp.delete()
100        rp.write_string(str(free_name_counter))
101        rp.fsync_with_dir()
102
103    if not free_name_counter:
104        free_name_counter = read_next_free()
105    if not free_name_counter:
106        free_name_counter = scan_next_free()
107    filename = b'%i' % free_name_counter
108    rp = get_long_rp(filename)
109    assert not rp.lstat(), "Unexpected file at '%s' found" % rp.get_safepath()
110    free_name_counter += 1
111    write_next_free(free_name_counter)
112    return filename
113
114
115def check_new_index(base, index, make_dirs=0):
116    """Return new rpath with given index, or None if that is too long
117
118    If make_dir is True, make any parent directories to assure that
119    file is really too long, and not just in directories that don't exist.
120
121    """
122
123    def wrap_call(func, *args):
124        try:
125            result = func(*args)
126        except EnvironmentError as exc:
127            if (exc.errno == errno.ENAMETOOLONG):
128                return None
129            raise
130        return result
131
132    def make_parent(rp):
133        parent = rp.get_parent_rp()
134        if parent.lstat():
135            return 1
136        parent.makedirs()
137        return 2
138
139    rp = wrap_call(base.new_index, index)
140    if not make_dirs or not rp or rp.lstat():
141        return rp
142
143    parent_result = wrap_call(make_parent, rp)
144    if not parent_result:
145        return None
146    elif parent_result == 1:
147        return rp
148    else:
149        return wrap_call(base.new_index, index)
150
151
152def get_mirror_rp(mirror_base, mirror_rorp):
153    """Get the mirror_rp for reading a regular file
154
155    This will just be in the mirror_base, unless rorp has an alt
156    mirror name specified.  Use new_rorp, unless it is None or empty,
157    and mirror_rorp exists.
158
159    """
160    if mirror_rorp.has_alt_mirror_name():
161        return get_long_rp(mirror_rorp.get_alt_mirror_name())
162    else:
163        rp = check_new_index(mirror_base, mirror_rorp.index)
164        if rp:
165            return rp
166        else:
167            raise Exception("the following line doesn't make any sense but does it matter?")
168            # FIXME index isn't defined anywhere, is mirror_rorp.index meant?
169            # return mirror_base.new_index_empty(index)
170
171
172def get_mirror_inc_rps(rorp_pair, mirror_root, inc_root=None):
173    """Get (mirror_rp, inc_rp) pair, possibly making new longname base
174
175    To test inc_rp, pad incbase with 50 random (non-quoted) characters
176    and see if that raises an error.
177
178    """
179    if not inc_root:  # make fake inc_root if not available
180        inc_root = mirror_root.append_path(b'rdiff-backup-data/increments')
181
182    def mir_triple_old(old_rorp):
183        """Return (mirror_rp, alt_mirror, alt_inc) from old_rorp"""
184        if old_rorp.has_alt_mirror_name():
185            alt_mirror = old_rorp.get_alt_mirror_name()
186            return (get_long_rp(alt_mirror), alt_mirror, None)
187        else:
188            mirror_rp = mirror_root.new_index(old_rorp.index)
189            if old_rorp.has_alt_inc_name():
190                return (mirror_rp, None, old_rorp.get_alt_inc_name())
191            else:
192                return (mirror_rp, None, None)
193
194    def mir_triple_new(new_rorp):
195        """Return (mirror_rp, alt_mirror, None) from new_rorp"""
196        mirror_rp = check_new_index(mirror_root, new_rorp.index)
197        if mirror_rp:
198            return (mirror_rp, None, None)
199        alt_mirror = get_next_free()
200        return (get_long_rp(alt_mirror), alt_mirror, None)
201
202    def update_rorp(new_rorp, alt_mirror, alt_inc):
203        """Update new_rorp with alternate mirror/inc information"""
204        if not new_rorp or not new_rorp.lstat():
205            return
206        if alt_mirror:
207            new_rorp.set_alt_mirror_name(alt_mirror)
208        elif alt_inc:
209            new_rorp.set_alt_inc_name(alt_inc)
210
211    def find_inc_pair(index, mirror_rp, alt_mirror, alt_inc):
212        """Return (alt_inc, inc_rp) pair"""
213        if alt_mirror:
214            return (None, mirror_rp)
215        elif alt_inc:
216            return (alt_inc, get_long_rp(alt_inc))
217        elif not index:
218            return (None, inc_root)
219
220        trial_inc_index = index[:-1] + (index[-1] + (b'a' * 50), )
221        if check_new_index(inc_root, trial_inc_index, make_dirs=1):
222            return (None, inc_root.new_index(index))
223        alt_inc = get_next_free()
224        return (alt_inc, get_long_rp(alt_inc))
225
226    (new_rorp, old_rorp) = rorp_pair
227    if old_rorp and old_rorp.lstat():
228        mirror_rp, alt_mirror, alt_inc = mir_triple_old(old_rorp)
229        index = old_rorp.index
230    else:
231        assert new_rorp and new_rorp.lstat(), (old_rorp, new_rorp)
232        mirror_rp, alt_mirror, alt_inc = mir_triple_new(new_rorp)
233        index = new_rorp.index
234
235    alt_inc, inc_rp = find_inc_pair(index, mirror_rp, alt_mirror, alt_inc)
236    update_rorp(new_rorp, alt_mirror, alt_inc)
237    return mirror_rp, inc_rp
238
239
240# ------------------------------------------------------------------
241# The following section is for restoring
242
243# This holds a dictionary {incbase: inclist}.  The keys are increment
244# bases like '1' or '23', and the values are lists containing the
245# associated increments.
246restore_inc_cache = None
247
248
249def set_restore_cache():
250    """Initialize restore_inc_cache based on long filename dir"""
251    global restore_inc_cache
252    restore_inc_cache = {}
253    root_rf = restore.RestoreFile(get_long_rp(), get_long_rp(), [])
254    for incbase_rp, inclist in root_rf.yield_inc_complexes(get_long_rp()):
255        restore_inc_cache[incbase_rp.index[-1]] = inclist
256
257
258def get_inclist(inc_base_name):
259    if not restore_inc_cache:
260        set_restore_cache()
261    try:
262        return restore_inc_cache[inc_base_name]
263    except KeyError:
264        return []
265
266
267def update_rf(rf, rorp, mirror_root):
268    """Return new or updated restorefile based on alt name info in rorp"""
269
270    def _safe_str(cmd):
271        """Transform bytes into string without risk of conversion error"""
272        if isinstance(cmd, str):
273            return cmd
274        else:
275            return str(cmd, errors='replace')
276
277    def update_incs(rf, inc_base):
278        """Swap inclist in rf with those with base inc_base and return"""
279        log.Log(
280            "Restoring with increment base %s for file %s" %
281            (_safe_str(inc_base), rorp.get_safeindexpath()), 6)
282        rf.inc_rp = get_long_rp(inc_base)
283        rf.inc_list = get_inclist(inc_base)
284        rf.set_relevant_incs()
285
286    def update_existing_rf(rf, rorp):
287        """Update rf based on rorp, don't make new one"""
288        if rorp.has_alt_mirror_name():
289            inc_name = rorp.get_alt_mirror_name()
290            raise Exception("the following line doesn't make any sense but does it matter?")
291            # FIXME mirror_name isn't defined anywhere, is inc_name meant?
292            # rf.mirror_rp = get_long_rp(mirror_name)
293        elif rorp.has_alt_inc_name():
294            inc_name = rorp.get_alt_inc_name()
295        else:
296            inc_name = None
297
298        if inc_name:
299            update_incs(rf, inc_name)
300
301    def make_new_rf(rorp, mirror_root):
302        """Make a new rf when long name info is available"""
303        if rorp.has_alt_mirror_name():
304            inc_name = rorp.get_alt_mirror_name()
305            mirror_rp = get_long_rp(inc_name)
306        else:
307            mirror_rp = mirror_root.new_index(rorp.index)
308            if rorp.has_alt_inc_name():
309                inc_name = rorp.get_alt_inc_name()
310            else:
311                return restore.RestoreFile(mirror_rp, None, [])
312
313        rf = restore.RestoreFile(mirror_rp, None, [])
314        update_incs(rf, inc_name)
315        return rf
316
317    if not rorp:
318        return rf
319    if rf and not rorp.has_alt_mirror_name() and not rorp.has_alt_inc_name():
320        return rf  # Most common case
321    if rf:
322        update_existing_rf(rf, rorp)
323        return rf
324    else:
325        return make_new_rf(rorp, mirror_root)
326
327
328def update_regressfile(rf, rorp, mirror_root):
329    """Like update_rf except return a regress file object"""
330    rf = update_rf(rf, rorp, mirror_root)
331    if isinstance(rf, regress.RegressFile):
332        return rf
333    return regress.RegressFile(rf.mirror_rp, rf.inc_rp, rf.inc_list)
334