]> arthur.barton.de Git - bup.git/blob - lib/bup/drecurse.py
pylint: enable unidiomatic-typecheck
[bup.git] / lib / bup / drecurse.py
1
2 from __future__ import absolute_import
3 import stat, os
4
5 from bup.compat import str_type
6 from bup.helpers import add_error, should_rx_exclude_path, debug1, resolve_parent
7 from bup.io import path_msg
8 import bup.xstat as xstat
9
10
11 try:
12     O_LARGEFILE = os.O_LARGEFILE
13 except AttributeError:
14     O_LARGEFILE = 0
15 try:
16     O_NOFOLLOW = os.O_NOFOLLOW
17 except AttributeError:
18     O_NOFOLLOW = 0
19
20
21 # the use of fchdir() and lstat() is for two reasons:
22 #  - help out the kernel by not making it repeatedly look up the absolute path
23 #  - avoid race conditions caused by doing listdir() on a changing symlink
24 class OsFile:
25     def __init__(self, path):
26         self.fd = None
27         self.fd = os.open(path, os.O_RDONLY|O_LARGEFILE|O_NOFOLLOW|os.O_NDELAY)
28
29     def __del__(self):
30         if self.fd:
31             fd = self.fd
32             self.fd = None
33             os.close(fd)
34
35     def fchdir(self):
36         os.fchdir(self.fd)
37
38     def stat(self):
39         return xstat.fstat(self.fd)
40
41 def _dirlist():
42     l = []
43     for n in os.listdir(b'.'):
44         try:
45             st = xstat.lstat(n)
46         except OSError as e:
47             add_error(Exception('%s: %s' % (resolve_parent(n), str(e))))
48             continue
49         if stat.S_ISDIR(st.st_mode):
50             n += b'/'
51         l.append((n,st))
52     l.sort(reverse=True)
53     return l
54
55 def _recursive_dirlist(prepend, xdev, bup_dir=None,
56                        excluded_paths=None,
57                        exclude_rxs=None,
58                        xdev_exceptions=frozenset()):
59     for (name,pst) in _dirlist():
60         path = prepend + name
61         if excluded_paths:
62             if os.path.normpath(path) in excluded_paths:
63                 debug1('Skipping %r: excluded.\n' % path_msg(path))
64                 continue
65         if exclude_rxs and should_rx_exclude_path(path, exclude_rxs):
66             continue
67         if name.endswith(b'/'):
68             if bup_dir != None:
69                 if os.path.normpath(path) == bup_dir:
70                     debug1('Skipping BUP_DIR.\n')
71                     continue
72             if xdev != None and pst.st_dev != xdev \
73                and path not in xdev_exceptions:
74                 debug1('Skipping contents of %r: different filesystem.\n'
75                        % path_msg(path))
76             else:
77                 try:
78                     OsFile(name).fchdir()
79                 except OSError as e:
80                     add_error('%s: %s' % (prepend, e))
81                 else:
82                     for i in _recursive_dirlist(prepend=prepend+name, xdev=xdev,
83                                                 bup_dir=bup_dir,
84                                                 excluded_paths=excluded_paths,
85                                                 exclude_rxs=exclude_rxs,
86                                                 xdev_exceptions=xdev_exceptions):
87                         yield i
88                     os.chdir(b'..')
89         yield (path, pst)
90
91
92 def recursive_dirlist(paths, xdev, bup_dir=None,
93                       excluded_paths=None,
94                       exclude_rxs=None,
95                       xdev_exceptions=frozenset()):
96     startdir = OsFile(b'.')
97     try:
98         assert not isinstance(paths, str_type)
99         for path in paths:
100             try:
101                 pst = xstat.lstat(path)
102                 if stat.S_ISLNK(pst.st_mode):
103                     yield (path, pst)
104                     continue
105             except OSError as e:
106                 add_error('recursive_dirlist: %s' % e)
107                 continue
108             try:
109                 pfile = OsFile(path)
110             except OSError as e:
111                 add_error(e)
112                 continue
113             pst = pfile.stat()
114             if xdev:
115                 xdev = pst.st_dev
116             else:
117                 xdev = None
118             if stat.S_ISDIR(pst.st_mode):
119                 pfile.fchdir()
120                 prepend = os.path.join(path, b'')
121                 for i in _recursive_dirlist(prepend=prepend, xdev=xdev,
122                                             bup_dir=bup_dir,
123                                             excluded_paths=excluded_paths,
124                                             exclude_rxs=exclude_rxs,
125                                             xdev_exceptions=xdev_exceptions):
126                     yield i
127                 startdir.fchdir()
128             else:
129                 prepend = path
130             yield (prepend,pst)
131     except:
132         try:
133             startdir.fchdir()
134         except:
135             pass
136         raise