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