1# Copyright 2002 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"""Provides functions and *ITR classes, for writing increment files"""
20
21import os
22from . import Globals, Time, rpath, Rdiff, log, statistics, robust
23
24
25def Increment(new, mirror, incpref):
26    """Main file incrementing function, returns inc file created
27
28    new is the file on the active partition,
29    mirror is the mirrored file from the last backup,
30    incpref is the prefix of the increment file.
31
32    This function basically moves the information about the mirror
33    file to incpref.
34
35    """
36    log.Log("Incrementing mirror file %s" % mirror.get_safepath(), 5)
37    if ((new and new.isdir()) or mirror.isdir()) and not incpref.lstat():
38        incpref.mkdir()
39
40    if not mirror.lstat():
41        incrp = makemissing(incpref)
42    elif mirror.isdir():
43        incrp = makedir(mirror, incpref)
44    elif new.isreg() and mirror.isreg():
45        incrp = makediff(new, mirror, incpref)
46    else:
47        incrp = makesnapshot(mirror, incpref)
48    statistics.process_increment(incrp)
49    return incrp
50
51
52def makemissing(incpref):
53    """Signify that mirror file was missing"""
54    incrp = get_inc(incpref, "missing")
55    incrp.touch()
56    return incrp
57
58
59def iscompressed(mirror):
60    """Return true if mirror's increments should be compressed"""
61    return (Globals.compression
62            and not Globals.no_compression_regexp.match(mirror.path))
63
64
65def makesnapshot(mirror, incpref):
66    """Copy mirror to incfile, since new is quite different"""
67    compress = iscompressed(mirror)
68    if compress and mirror.isreg():
69        snapshotrp = get_inc(incpref, b"snapshot.gz")
70    else:
71        snapshotrp = get_inc(incpref, b"snapshot")
72
73    if mirror.isspecial():  # check for errors when creating special increments
74        eh = robust.get_error_handler("SpecialFileError")
75        if robust.check_common_error(eh, rpath.copy_with_attribs,
76                                     (mirror, snapshotrp, compress)) == 0:
77            snapshotrp.setdata()
78            if snapshotrp.lstat():
79                snapshotrp.delete()
80            snapshotrp.touch()
81    else:
82        rpath.copy_with_attribs(mirror, snapshotrp, compress)
83    return snapshotrp
84
85
86def makediff(new, mirror, incpref):
87    """Make incfile which is a diff new -> mirror"""
88    compress = iscompressed(mirror)
89    if compress:
90        diff = get_inc(incpref, b"diff.gz")
91    else:
92        diff = get_inc(incpref, b"diff")
93
94    old_new_perms, old_mirror_perms = (None, None)
95
96    if Globals.process_uid != 0:
97        # Check for unreadable files
98        if not new.readable():
99            old_new_perms = new.getperms()
100            new.chmod(0o400 | old_new_perms)
101        if not mirror.readable():
102            old_mirror_perms = mirror.getperms()
103            mirror.chmod(0o400 | old_mirror_perms)
104
105    Rdiff.write_delta(new, mirror, diff, compress)
106
107    if old_new_perms:
108        new.chmod(old_new_perms)
109    if old_mirror_perms:
110        mirror.chmod(old_mirror_perms)
111
112    rpath.copy_attribs_inc(mirror, diff)
113    return diff
114
115
116def makedir(mirrordir, incpref):
117    """Make file indicating directory mirrordir has changed"""
118    dirsign = get_inc(incpref, "dir")
119    dirsign.touch()
120    rpath.copy_attribs_inc(mirrordir, dirsign)
121    return dirsign
122
123
124def get_inc(rp, typestr, time=None):
125    """Return increment like rp but with time and typestr suffixes
126
127    To avoid any quoting, the returned rpath has empty index, and the
128    whole filename is in the base (which is not quoted).
129
130    """
131    if time is None:
132        time = Time.prevtime
133
134    def addtostr(s):
135        return b'.'.join(map(os.fsencode, (s, Time.timetostring(time), typestr)))
136
137    if rp.index:
138        incrp = rp.__class__(rp.conn, rp.base,
139                             rp.index[:-1] + (addtostr(rp.index[-1]), ))
140    else:
141        dirname, basename = rp.dirsplit()
142        incrp = rp.__class__(rp.conn, dirname, (addtostr(basename), ))
143    assert not incrp.lstat(), incrp
144    return incrp
145