7 | 7 |
from .ui import io
|
8 | 8 |
|
9 | 9 |
|
10 | |
def _parse_file_output(file_output):
|
11 | |
if file_output.startswith("cannot open "):
|
12 | |
# required for Mac OS X, OpenBSD, and CentOS/RHEL
|
13 | |
return ('nonexistent', "")
|
14 | |
elif file_output.endswith("directory"):
|
15 | |
return ('directory', file_output)
|
16 | |
elif file_output.startswith("block special") or \
|
17 | |
file_output.startswith("character special"):
|
18 | |
return ('other', file_output)
|
19 | |
elif file_output.startswith("symbolic link to ") or \
|
20 | |
file_output.startswith("broken symbolic link to "):
|
21 | |
return ('symlink', file_output)
|
22 | |
else:
|
23 | |
return ('file', file_output)
|
24 | |
|
25 | |
|
26 | |
def get_path_type(node, path):
|
27 | |
"""
|
28 | |
Returns (TYPE, DESC) where TYPE is one of:
|
29 | |
|
30 | |
'directory', 'file', 'nonexistent', 'other', 'symlink'
|
31 | |
|
32 | |
and DESC is the output of the 'file' command line utility.
|
33 | |
"""
|
34 | |
result = node.run("file -bh -- {}".format(quote(path)), may_fail=True)
|
35 | |
file_output = force_text(result.stdout.strip())
|
36 | |
if (
|
37 | |
result.return_code != 0 or
|
38 | |
"No such file or directory" in file_output # thanks CentOS
|
39 | |
):
|
40 | |
return ('nonexistent', "")
|
41 | |
|
42 | |
return _parse_file_output(file_output)
|
43 | |
|
44 | |
|
45 | 10 |
def stat(node, path):
|
46 | 11 |
if node.os in node.OS_FAMILY_BSD:
|
47 | |
result = node.run("stat -f '%Su:%Sg:%p:%z' -- {}".format(quote(path)))
|
|
12 |
result = node.run(
|
|
13 |
"stat -f '%Su:%Sg:%p:%z:%HT' -- {}".format(quote(path)),
|
|
14 |
may_fail=True,
|
|
15 |
)
|
48 | 16 |
else:
|
49 | |
result = node.run("stat -c '%U:%G:%a:%s' -- {}".format(quote(path)))
|
50 | |
owner, group, mode, size = force_text(result.stdout).split(":")
|
|
17 |
result = node.run(
|
|
18 |
"stat -c '%U:%G:%a:%s:%F' -- {}".format(quote(path)),
|
|
19 |
may_fail=True,
|
|
20 |
)
|
|
21 |
if result.return_code != 0:
|
|
22 |
return {}
|
|
23 |
owner, group, mode, size, ftype = \
|
|
24 |
force_text(result.stdout).strip().split(":", 5)
|
51 | 25 |
mode = mode[-4:].zfill(4) # cut off BSD file type
|
52 | 26 |
file_stat = {
|
53 | 27 |
'owner': owner,
|
54 | 28 |
'group': group,
|
55 | 29 |
'mode': mode,
|
56 | 30 |
'size': int(size),
|
|
31 |
'type': ftype.lower(),
|
57 | 32 |
}
|
58 | 33 |
io.debug(_("stat for '{path}' on {node}: {result}".format(
|
59 | 34 |
node=node.name,
|
|
67 | 42 |
"""
|
68 | 43 |
Serves as a proxy to get_path_type.
|
69 | 44 |
"""
|
|
45 |
|
70 | 46 |
def __init__(self, node, path):
|
71 | 47 |
self.node = node
|
72 | 48 |
self.path = path
|
73 | |
self.path_type, self.desc = get_path_type(node, path)
|
74 | |
self.stat = stat(node, path) if self.path_type != 'nonexistent' else {}
|
|
49 |
self.stat = stat(node, path)
|
75 | 50 |
|
76 | 51 |
def __repr__(self):
|
77 | 52 |
return "<PathInfo for {}:{}>".format(self.node.name, quote(self.path))
|
78 | 53 |
|
79 | 54 |
@property
|
80 | 55 |
def exists(self):
|
81 | |
return self.path_type != 'nonexistent'
|
|
56 |
return bool(self.stat)
|
82 | 57 |
|
83 | 58 |
@property
|
84 | 59 |
def group(self):
|
|
90 | 65 |
|
91 | 66 |
@property
|
92 | 67 |
def is_directory(self):
|
93 | |
return self.path_type == 'directory'
|
|
68 |
return self.stat['type'] == "directory"
|
94 | 69 |
|
95 | 70 |
@property
|
96 | 71 |
def is_file(self):
|
97 | |
return self.path_type == 'file'
|
|
72 |
return self.stat['type'] in ("regular file", "regular empty file")
|
98 | 73 |
|
99 | 74 |
@property
|
100 | 75 |
def is_symlink(self):
|
101 | |
return self.path_type == 'symlink'
|
|
76 |
return self.stat['type'] == "symbolic link"
|
102 | 77 |
|
103 | 78 |
@property
|
104 | 79 |
def is_text_file(self):
|
105 | 80 |
return self.is_file and (
|
106 | 81 |
"text" in self.desc or
|
107 | 82 |
self.desc in (
|
108 | |
"empty",
|
109 | |
"OpenSSH ED25519 public key",
|
110 | |
"OpenSSH RSA public key",
|
111 | |
"OpenSSH DSA public key",
|
112 | |
)
|
|
83 |
"empty",
|
|
84 |
"OpenSSH ED25519 public key",
|
|
85 |
"OpenSSH RSA public key",
|
|
86 |
"OpenSSH DSA public key",
|
|
87 |
)
|
113 | 88 |
)
|
114 | 89 |
|
115 | 90 |
@property
|
|
119 | 94 |
@property
|
120 | 95 |
def owner(self):
|
121 | 96 |
return self.stat['owner']
|
|
97 |
|
|
98 |
@cached_property
|
|
99 |
def desc(self):
|
|
100 |
return force_text(self.node.run(
|
|
101 |
"file -bh -- {}".format(quote(self.path))
|
|
102 |
).stdout).strip()
|
122 | 103 |
|
123 | 104 |
@cached_property
|
124 | 105 |
def sha1(self):
|