dissect.target 3.14.dev28__py3-none-any.whl → 3.15__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/containers/ewf.py +1 -1
- dissect/target/containers/vhd.py +5 -2
- dissect/target/filesystem.py +36 -18
- dissect/target/filesystems/dir.py +10 -4
- dissect/target/filesystems/jffs.py +122 -0
- dissect/target/helpers/compat/path_310.py +506 -0
- dissect/target/helpers/compat/path_311.py +539 -0
- dissect/target/helpers/compat/path_312.py +443 -0
- dissect/target/helpers/compat/path_39.py +545 -0
- dissect/target/helpers/compat/path_common.py +223 -0
- dissect/target/helpers/cyber.py +512 -0
- dissect/target/helpers/fsutil.py +128 -666
- dissect/target/helpers/hashutil.py +17 -57
- dissect/target/helpers/keychain.py +9 -3
- dissect/target/helpers/loaderutil.py +1 -1
- dissect/target/helpers/mount.py +47 -4
- dissect/target/helpers/polypath.py +73 -0
- dissect/target/helpers/record_modifier.py +100 -0
- dissect/target/loader.py +2 -1
- dissect/target/loaders/asdf.py +2 -0
- dissect/target/loaders/cyber.py +37 -0
- dissect/target/loaders/log.py +14 -3
- dissect/target/loaders/raw.py +2 -0
- dissect/target/loaders/remote.py +12 -0
- dissect/target/loaders/tar.py +13 -0
- dissect/target/loaders/targetd.py +2 -0
- dissect/target/loaders/velociraptor.py +12 -3
- dissect/target/loaders/vmwarevm.py +2 -0
- dissect/target/plugin.py +272 -143
- dissect/target/plugins/apps/ssh/openssh.py +11 -54
- dissect/target/plugins/apps/ssh/opensshd.py +4 -3
- dissect/target/plugins/apps/ssh/putty.py +236 -0
- dissect/target/plugins/apps/ssh/ssh.py +58 -0
- dissect/target/plugins/apps/vpn/openvpn.py +6 -0
- dissect/target/plugins/apps/webserver/apache.py +309 -95
- dissect/target/plugins/apps/webserver/caddy.py +5 -2
- dissect/target/plugins/apps/webserver/citrix.py +82 -0
- dissect/target/plugins/apps/webserver/iis.py +9 -12
- dissect/target/plugins/apps/webserver/nginx.py +5 -2
- dissect/target/plugins/apps/webserver/webserver.py +25 -41
- dissect/target/plugins/child/wsl.py +1 -1
- dissect/target/plugins/filesystem/ntfs/mft.py +10 -0
- dissect/target/plugins/filesystem/ntfs/mft_timeline.py +10 -0
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +10 -0
- dissect/target/plugins/filesystem/ntfs/utils.py +28 -5
- dissect/target/plugins/filesystem/resolver.py +6 -4
- dissect/target/plugins/general/default.py +0 -2
- dissect/target/plugins/general/example.py +0 -1
- dissect/target/plugins/general/loaders.py +3 -5
- dissect/target/plugins/os/unix/_os.py +3 -3
- dissect/target/plugins/os/unix/bsd/citrix/_os.py +68 -28
- dissect/target/plugins/os/unix/bsd/citrix/history.py +130 -0
- dissect/target/plugins/os/unix/generic.py +17 -10
- dissect/target/plugins/os/unix/linux/fortios/__init__.py +0 -0
- dissect/target/plugins/os/unix/linux/fortios/_os.py +534 -0
- dissect/target/plugins/os/unix/linux/fortios/generic.py +30 -0
- dissect/target/plugins/os/unix/linux/fortios/locale.py +109 -0
- dissect/target/plugins/os/windows/log/evt.py +1 -1
- dissect/target/plugins/os/windows/log/schedlgu.py +155 -0
- dissect/target/plugins/os/windows/regf/firewall.py +1 -1
- dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
- dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
- dissect/target/plugins/os/windows/registry.py +1 -1
- dissect/target/plugins/os/windows/sam.py +3 -0
- dissect/target/plugins/os/windows/sru.py +41 -28
- dissect/target/plugins/os/windows/tasks.py +5 -2
- dissect/target/target.py +7 -3
- dissect/target/tools/dd.py +7 -1
- dissect/target/tools/fs.py +8 -1
- dissect/target/tools/info.py +22 -15
- dissect/target/tools/mount.py +28 -3
- dissect/target/tools/query.py +146 -117
- dissect/target/tools/reg.py +21 -16
- dissect/target/tools/shell.py +30 -6
- dissect/target/tools/utils.py +28 -0
- dissect/target/volumes/bde.py +14 -10
- dissect/target/volumes/luks.py +18 -10
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/METADATA +4 -3
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/RECORD +85 -67
- dissect/target/plugins/os/unix/linux/fortigate/_os.py +0 -175
- /dissect/target/{plugins/os/unix/linux/fortigate → helpers/compat}/__init__.py +0 -0
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/LICENSE +0 -0
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/WHEEL +0 -0
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,506 @@
|
|
1
|
+
"""A pathlib.Path compatible implementation for dissect.target.
|
2
|
+
|
3
|
+
This allows for the majority of the pathlib.Path API to "just work" on dissect.target filesystems.
|
4
|
+
|
5
|
+
Most of this consists of subclassed internal classes with dissect.target specific patches,
|
6
|
+
but sometimes the change to a function is small, so the entire internal function is copied
|
7
|
+
and only a small part changed. To ease updating this code, the order of functions, comments
|
8
|
+
and code style is kept largely the same as the original pathlib.py.
|
9
|
+
|
10
|
+
Yes, we know, this is playing with fire and it can break on new CPython releases.
|
11
|
+
|
12
|
+
The implementation is split up in multiple files, one for each CPython version.
|
13
|
+
You're currently looking at the CPython 3.10 implementation.
|
14
|
+
|
15
|
+
Commit hash we're in sync with: b382bf5
|
16
|
+
"""
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
import fnmatch
|
21
|
+
import re
|
22
|
+
from pathlib import Path, PurePath, _Accessor, _PosixFlavour
|
23
|
+
from stat import S_ISBLK, S_ISCHR, S_ISFIFO, S_ISSOCK
|
24
|
+
from typing import IO, TYPE_CHECKING, Any, Callable, Iterator, Optional
|
25
|
+
|
26
|
+
from dissect.target import filesystem
|
27
|
+
from dissect.target.exceptions import FilesystemError, SymlinkRecursionError
|
28
|
+
from dissect.target.helpers.compat.path_common import (
|
29
|
+
_DissectPathParents,
|
30
|
+
io_open,
|
31
|
+
isjunction,
|
32
|
+
realpath,
|
33
|
+
scandir,
|
34
|
+
)
|
35
|
+
from dissect.target.helpers.polypath import normalize
|
36
|
+
|
37
|
+
if TYPE_CHECKING:
|
38
|
+
from dissect.target.filesystem import Filesystem, FilesystemEntry
|
39
|
+
from dissect.target.helpers.compat.path_common import _DissectScandirIterator
|
40
|
+
from dissect.target.helpers.fsutil import stat_result
|
41
|
+
|
42
|
+
|
43
|
+
class _DissectFlavour(_PosixFlavour):
|
44
|
+
is_supported = True
|
45
|
+
|
46
|
+
__variant_instances = {}
|
47
|
+
|
48
|
+
def __new__(cls, case_sensitive: bool = False, alt_separator: str = ""):
|
49
|
+
idx = (case_sensitive, alt_separator)
|
50
|
+
instance = cls.__variant_instances.get(idx, None)
|
51
|
+
if instance is None:
|
52
|
+
instance = _PosixFlavour.__new__(cls)
|
53
|
+
cls.__variant_instances[idx] = instance
|
54
|
+
|
55
|
+
return instance
|
56
|
+
|
57
|
+
def __init__(self, case_sensitive: bool = False, alt_separator: str = ""):
|
58
|
+
super().__init__()
|
59
|
+
self.altsep = alt_separator
|
60
|
+
self.case_sensitive = case_sensitive
|
61
|
+
|
62
|
+
def casefold(self, s: str) -> str:
|
63
|
+
return s if self.case_sensitive else s.lower()
|
64
|
+
|
65
|
+
def casefold_parts(self, parts: list[str]) -> list[str]:
|
66
|
+
return parts if self.case_sensitive else [p.lower() for p in parts]
|
67
|
+
|
68
|
+
def compile_pattern(self, pattern: str) -> Callable[..., Any]:
|
69
|
+
return re.compile(fnmatch.translate(pattern), 0 if self.case_sensitive else re.IGNORECASE).fullmatch
|
70
|
+
|
71
|
+
|
72
|
+
class _DissectAccessor(_Accessor):
|
73
|
+
@staticmethod
|
74
|
+
def stat(path: TargetPath, *, follow_symlinks: bool = True) -> stat_result:
|
75
|
+
if follow_symlinks:
|
76
|
+
return path.get().stat()
|
77
|
+
else:
|
78
|
+
return path.get().lstat()
|
79
|
+
|
80
|
+
@staticmethod
|
81
|
+
def open(
|
82
|
+
path: TargetPath,
|
83
|
+
mode: str = "rb",
|
84
|
+
buffering: int = 0,
|
85
|
+
encoding: Optional[str] = None,
|
86
|
+
errors: Optional[str] = None,
|
87
|
+
newline: Optional[str] = None,
|
88
|
+
) -> IO:
|
89
|
+
"""Open file and return a stream.
|
90
|
+
|
91
|
+
Supports a subset of features of the real pathlib.open/io.open.
|
92
|
+
|
93
|
+
Note: in contrast to regular Python, the mode is binary by default. Text mode
|
94
|
+
has to be explicitly specified. Buffering is also disabled by default.
|
95
|
+
"""
|
96
|
+
return io_open(path, mode, buffering, encoding, errors, newline)
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def listdir(path: TargetPath) -> Iterator[str]:
|
100
|
+
return path.get().listdir()
|
101
|
+
|
102
|
+
@staticmethod
|
103
|
+
def scandir(path: TargetPath) -> _DissectScandirIterator:
|
104
|
+
return scandir(path)
|
105
|
+
|
106
|
+
@staticmethod
|
107
|
+
def chmod(path: TargetPath, mode: int, *, follow_symlinks: bool = True) -> None:
|
108
|
+
raise NotImplementedError("TargetPath.chmod() is unsupported")
|
109
|
+
|
110
|
+
@staticmethod
|
111
|
+
def lchmod(path: TargetPath, mode: int) -> None:
|
112
|
+
raise NotImplementedError("TargetPath.lchmod() is unsupported")
|
113
|
+
|
114
|
+
@staticmethod
|
115
|
+
def mkdir(path: TargetPath, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None:
|
116
|
+
raise NotImplementedError("TargetPath.mkdir() is unsupported")
|
117
|
+
|
118
|
+
@staticmethod
|
119
|
+
def unlink(path: TargetPath, missing_ok: bool = False) -> None:
|
120
|
+
raise NotImplementedError("TargetPath.unlink() is unsupported")
|
121
|
+
|
122
|
+
@staticmethod
|
123
|
+
def link(src: str, dst: TargetPath) -> None:
|
124
|
+
raise NotImplementedError("TargetPath.link() is unsupported")
|
125
|
+
|
126
|
+
@staticmethod
|
127
|
+
def rmdir(path: TargetPath) -> None:
|
128
|
+
raise NotImplementedError("TargetPath.rmdir() is unsupported")
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
def rename(path: TargetPath, target: str) -> str:
|
132
|
+
raise NotImplementedError("TargetPath.rename() is unsupported")
|
133
|
+
|
134
|
+
@staticmethod
|
135
|
+
def replace(path: TargetPath, target: str) -> str:
|
136
|
+
raise NotImplementedError("TargetPath.replace() is unsupported")
|
137
|
+
|
138
|
+
@staticmethod
|
139
|
+
def symlink(path: TargetPath, target: str, target_is_directory: bool = False) -> None:
|
140
|
+
raise NotImplementedError("TargetPath.symlink() is unsupported")
|
141
|
+
|
142
|
+
@staticmethod
|
143
|
+
def touch(path: TargetPath, mode: int = 0o666, exist_ok: bool = True) -> None:
|
144
|
+
raise NotImplementedError("TargetPath.touch() is unsupported")
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def readlink(path: TargetPath) -> str:
|
148
|
+
return path.get().readlink()
|
149
|
+
|
150
|
+
@staticmethod
|
151
|
+
def owner(path: TargetPath) -> str:
|
152
|
+
raise NotImplementedError("TargetPath.owner() is unsupported")
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def group(path: TargetPath) -> str:
|
156
|
+
raise NotImplementedError("TargetPath.group() is unsupported")
|
157
|
+
|
158
|
+
@staticmethod
|
159
|
+
def getcwd() -> str:
|
160
|
+
raise NotImplementedError("TargetPath.getcwd() is unsupported")
|
161
|
+
|
162
|
+
@staticmethod
|
163
|
+
def expanduser(path: str) -> str:
|
164
|
+
raise NotImplementedError("TargetPath.expanduser() is unsupported")
|
165
|
+
|
166
|
+
realpath = staticmethod(realpath)
|
167
|
+
|
168
|
+
# NOTE: Forward compatibility with CPython >= 3.12
|
169
|
+
isjunction = staticmethod(isjunction)
|
170
|
+
|
171
|
+
|
172
|
+
_dissect_accessor = _DissectAccessor()
|
173
|
+
|
174
|
+
|
175
|
+
class PureDissectPath(PurePath):
|
176
|
+
_fs: Filesystem
|
177
|
+
_flavour = _DissectFlavour(case_sensitive=False)
|
178
|
+
|
179
|
+
def __reduce__(self) -> tuple:
|
180
|
+
raise TypeError("TargetPath pickling is currently not supported")
|
181
|
+
|
182
|
+
@classmethod
|
183
|
+
def _from_parts(cls, args: list) -> TargetPath:
|
184
|
+
fs = args[0]
|
185
|
+
|
186
|
+
if not isinstance(fs, filesystem.Filesystem):
|
187
|
+
raise TypeError(
|
188
|
+
"invalid PureDissectPath initialization: missing filesystem, "
|
189
|
+
"got %r (this might be a bug, please report)" % args
|
190
|
+
)
|
191
|
+
|
192
|
+
alt_separator = fs.alt_separator
|
193
|
+
path_args = []
|
194
|
+
for arg in args[1:]:
|
195
|
+
if isinstance(arg, str):
|
196
|
+
arg = normalize(arg, alt_separator=alt_separator)
|
197
|
+
path_args.append(arg)
|
198
|
+
|
199
|
+
self = super()._from_parts(path_args)
|
200
|
+
self._fs = fs
|
201
|
+
|
202
|
+
self._flavour = _DissectFlavour(alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive)
|
203
|
+
|
204
|
+
return self
|
205
|
+
|
206
|
+
def _make_child(self, args: list) -> TargetPath:
|
207
|
+
child = super()._make_child(args)
|
208
|
+
child._fs = self._fs
|
209
|
+
child._flavour = self._flavour
|
210
|
+
return child
|
211
|
+
|
212
|
+
def with_name(self, name: str) -> TargetPath:
|
213
|
+
result = super().with_name(name)
|
214
|
+
result._fs = self._fs
|
215
|
+
result._flavour = self._flavour
|
216
|
+
return result
|
217
|
+
|
218
|
+
def with_stem(self, stem: str) -> TargetPath:
|
219
|
+
result = super().with_stem(stem)
|
220
|
+
result._fs = self._fs
|
221
|
+
result._flavour = self._flavour
|
222
|
+
return result
|
223
|
+
|
224
|
+
def with_suffix(self, suffix: str) -> TargetPath:
|
225
|
+
result = super().with_suffix(suffix)
|
226
|
+
result._fs = self._fs
|
227
|
+
result._flavour = self._flavour
|
228
|
+
return result
|
229
|
+
|
230
|
+
def relative_to(self, *other) -> TargetPath:
|
231
|
+
result = super().relative_to(*other)
|
232
|
+
result._fs = self._fs
|
233
|
+
result._flavour = self._flavour
|
234
|
+
return result
|
235
|
+
|
236
|
+
def __rtruediv__(self, key: str) -> TargetPath:
|
237
|
+
try:
|
238
|
+
return self._from_parts([self._fs, key] + self._parts)
|
239
|
+
except TypeError:
|
240
|
+
return NotImplemented
|
241
|
+
|
242
|
+
@property
|
243
|
+
def parent(self) -> TargetPath:
|
244
|
+
result = super().parent
|
245
|
+
result._fs = self._fs
|
246
|
+
result._flavour = self._flavour
|
247
|
+
return result
|
248
|
+
|
249
|
+
@property
|
250
|
+
def parents(self) -> _DissectPathParents:
|
251
|
+
return _DissectPathParents(self)
|
252
|
+
|
253
|
+
|
254
|
+
class TargetPath(Path, PureDissectPath):
|
255
|
+
_accessor = _dissect_accessor
|
256
|
+
__slots__ = ("_entry",)
|
257
|
+
|
258
|
+
def _make_child_relpath(self, part: str) -> TargetPath:
|
259
|
+
child = super()._make_child_relpath(part)
|
260
|
+
child._fs = self._fs
|
261
|
+
child._flavour = self._flavour
|
262
|
+
return child
|
263
|
+
|
264
|
+
def get(self) -> FilesystemEntry:
|
265
|
+
try:
|
266
|
+
return self._entry
|
267
|
+
except AttributeError:
|
268
|
+
self._entry = self._fs.get(str(self))
|
269
|
+
return self._entry
|
270
|
+
|
271
|
+
@classmethod
|
272
|
+
def cwd(cls) -> TargetPath:
|
273
|
+
"""Return a new path pointing to the current working directory
|
274
|
+
(as returned by os.getcwd()).
|
275
|
+
"""
|
276
|
+
raise NotImplementedError("TargetPath.cwd() is unsupported")
|
277
|
+
|
278
|
+
@classmethod
|
279
|
+
def home(cls) -> TargetPath:
|
280
|
+
"""Return a new path pointing to the user's home directory (as
|
281
|
+
returned by os.path.expanduser('~')).
|
282
|
+
"""
|
283
|
+
raise NotImplementedError("TargetPath.home() is unsupported")
|
284
|
+
|
285
|
+
def iterdir(self) -> Iterator[TargetPath]:
|
286
|
+
"""Iterate over the files in this directory. Does not yield any
|
287
|
+
result for the special paths '.' and '..'.
|
288
|
+
"""
|
289
|
+
for entry in self._accessor.scandir(self):
|
290
|
+
if entry.name in {".", ".."}:
|
291
|
+
# Yielding a path object for these makes little sense
|
292
|
+
continue
|
293
|
+
child_path = self._make_child_relpath(entry.name)
|
294
|
+
child_path._entry = entry
|
295
|
+
yield child_path
|
296
|
+
|
297
|
+
# NOTE: Forward compatibility with CPython >= 3.12
|
298
|
+
def walk(
|
299
|
+
self, top_down: bool = True, on_error: Callable[[Exception], None] = None, follow_symlinks: bool = False
|
300
|
+
) -> Iterator[tuple[TargetPath, list[str], list[str]]]:
|
301
|
+
"""Walk the directory tree from this directory, similar to os.walk()."""
|
302
|
+
paths = [self]
|
303
|
+
|
304
|
+
while paths:
|
305
|
+
path = paths.pop()
|
306
|
+
if isinstance(path, tuple):
|
307
|
+
yield path
|
308
|
+
continue
|
309
|
+
|
310
|
+
# We may not have read permission for self, in which case we can't
|
311
|
+
# get a list of the files the directory contains. os.walk()
|
312
|
+
# always suppressed the exception in that instance, rather than
|
313
|
+
# blow up for a minor reason when (say) a thousand readable
|
314
|
+
# directories are still left to visit. That logic is copied here.
|
315
|
+
try:
|
316
|
+
scandir_it = self._accessor.scandir(path)
|
317
|
+
except OSError as error:
|
318
|
+
if on_error is not None:
|
319
|
+
on_error(error)
|
320
|
+
continue
|
321
|
+
|
322
|
+
with scandir_it:
|
323
|
+
dirnames = []
|
324
|
+
filenames = []
|
325
|
+
for entry in scandir_it:
|
326
|
+
try:
|
327
|
+
is_dir = entry.is_dir(follow_symlinks=follow_symlinks)
|
328
|
+
except OSError:
|
329
|
+
# Carried over from os.path.isdir().
|
330
|
+
is_dir = False
|
331
|
+
|
332
|
+
if is_dir:
|
333
|
+
dirnames.append(entry.name)
|
334
|
+
else:
|
335
|
+
filenames.append(entry.name)
|
336
|
+
|
337
|
+
if top_down:
|
338
|
+
yield path, dirnames, filenames
|
339
|
+
else:
|
340
|
+
paths.append((path, dirnames, filenames))
|
341
|
+
|
342
|
+
paths += [path._make_child_relpath(d) for d in reversed(dirnames)]
|
343
|
+
|
344
|
+
def absolute(self) -> TargetPath:
|
345
|
+
"""Return an absolute version of this path. This function works
|
346
|
+
even if the path doesn't point to anything.
|
347
|
+
|
348
|
+
No normalization is done, i.e. all '.' and '..' will be kept along.
|
349
|
+
Use resolve() to get the canonical path to a file.
|
350
|
+
"""
|
351
|
+
raise NotImplementedError("TargetPath.absolute() is unsupported in Dissect")
|
352
|
+
|
353
|
+
# NOTE: We changed some of the error handling here to deal with our own exception types
|
354
|
+
def resolve(self, strict: bool = False) -> TargetPath:
|
355
|
+
"""
|
356
|
+
Make the path absolute, resolving all symlinks on the way and also
|
357
|
+
normalizing it (for example turning slashes into backslashes under
|
358
|
+
Windows).
|
359
|
+
"""
|
360
|
+
|
361
|
+
s = self._accessor.realpath(self, strict=strict)
|
362
|
+
p = self._from_parts((self._fs, s))
|
363
|
+
|
364
|
+
# In non-strict mode, realpath() doesn't raise on symlink loops.
|
365
|
+
# Ensure we get an exception by calling stat()
|
366
|
+
if not strict:
|
367
|
+
try:
|
368
|
+
p.stat()
|
369
|
+
except FilesystemError as e:
|
370
|
+
if isinstance(e, SymlinkRecursionError):
|
371
|
+
raise
|
372
|
+
return p
|
373
|
+
|
374
|
+
def stat(self, *, follow_symlinks: bool = True) -> stat_result:
|
375
|
+
"""
|
376
|
+
Return the result of the stat() system call on this path, like
|
377
|
+
os.stat() does.
|
378
|
+
"""
|
379
|
+
return self._accessor.stat(self, follow_symlinks=follow_symlinks)
|
380
|
+
|
381
|
+
def open(
|
382
|
+
self,
|
383
|
+
mode: str = "rb",
|
384
|
+
buffering: int = 0,
|
385
|
+
encoding: Optional[str] = None,
|
386
|
+
errors: Optional[str] = None,
|
387
|
+
newline: Optional[str] = None,
|
388
|
+
) -> IO:
|
389
|
+
"""Open file and return a stream.
|
390
|
+
|
391
|
+
Supports a subset of features of the real pathlib.open/io.open.
|
392
|
+
|
393
|
+
Note: in contrast to regular Python, the mode is binary by default. Text mode
|
394
|
+
has to be explicitly specified. Buffering is also disabled by default.
|
395
|
+
"""
|
396
|
+
return self._accessor.open(self, mode, buffering, encoding, errors, newline)
|
397
|
+
|
398
|
+
def write_bytes(self, data: bytes) -> int:
|
399
|
+
"""
|
400
|
+
Open the file in bytes mode, write to it, and close the file.
|
401
|
+
"""
|
402
|
+
raise NotImplementedError("TargetPath.write_bytes() is unsupported")
|
403
|
+
|
404
|
+
def write_text(
|
405
|
+
self, data: str, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None
|
406
|
+
) -> int:
|
407
|
+
"""
|
408
|
+
Open the file in text mode, write to it, and close the file.
|
409
|
+
"""
|
410
|
+
raise NotImplementedError("TargetPath.write_text() is unsupported")
|
411
|
+
|
412
|
+
def readlink(self) -> TargetPath:
|
413
|
+
"""
|
414
|
+
Return the path to which the symbolic link points.
|
415
|
+
"""
|
416
|
+
path = self._accessor.readlink(self)
|
417
|
+
obj = self._from_parts((self._fs, path))
|
418
|
+
return obj
|
419
|
+
|
420
|
+
def exists(self) -> bool:
|
421
|
+
"""
|
422
|
+
Whether this path exists.
|
423
|
+
"""
|
424
|
+
try:
|
425
|
+
# .exists() must resolve possible symlinks
|
426
|
+
self.get().stat()
|
427
|
+
return True
|
428
|
+
except (FilesystemError, ValueError):
|
429
|
+
return False
|
430
|
+
|
431
|
+
def is_dir(self) -> bool:
|
432
|
+
"""
|
433
|
+
Whether this path is a directory.
|
434
|
+
"""
|
435
|
+
try:
|
436
|
+
return self.get().is_dir()
|
437
|
+
except (FilesystemError, ValueError):
|
438
|
+
return False
|
439
|
+
|
440
|
+
def is_file(self) -> bool:
|
441
|
+
"""
|
442
|
+
Whether this path is a regular file (also True for symlinks pointing
|
443
|
+
to regular files).
|
444
|
+
"""
|
445
|
+
try:
|
446
|
+
return self.get().is_file()
|
447
|
+
except (FilesystemError, ValueError):
|
448
|
+
return False
|
449
|
+
|
450
|
+
def is_symlink(self) -> bool:
|
451
|
+
"""
|
452
|
+
Whether this path is a symbolic link.
|
453
|
+
"""
|
454
|
+
try:
|
455
|
+
return self.get().is_symlink()
|
456
|
+
except (FilesystemError, ValueError):
|
457
|
+
return False
|
458
|
+
|
459
|
+
# NOTE: Forward compatibility with CPython >= 3.12
|
460
|
+
def is_junction(self) -> bool:
|
461
|
+
"""
|
462
|
+
Whether this path is a junction.
|
463
|
+
"""
|
464
|
+
return self._accessor.isjunction(self)
|
465
|
+
|
466
|
+
def is_block_device(self) -> bool:
|
467
|
+
"""
|
468
|
+
Whether this path is a block device.
|
469
|
+
"""
|
470
|
+
try:
|
471
|
+
return S_ISBLK(self.stat().st_mode)
|
472
|
+
except (FilesystemError, ValueError):
|
473
|
+
return False
|
474
|
+
|
475
|
+
def is_char_device(self) -> bool:
|
476
|
+
"""
|
477
|
+
Whether this path is a character device.
|
478
|
+
"""
|
479
|
+
try:
|
480
|
+
return S_ISCHR(self.stat().st_mode)
|
481
|
+
except (FilesystemError, ValueError):
|
482
|
+
return False
|
483
|
+
|
484
|
+
def is_fifo(self) -> bool:
|
485
|
+
"""
|
486
|
+
Whether this path is a FIFO.
|
487
|
+
"""
|
488
|
+
try:
|
489
|
+
return S_ISFIFO(self.stat().st_mode)
|
490
|
+
except (FilesystemError, ValueError):
|
491
|
+
return False
|
492
|
+
|
493
|
+
def is_socket(self) -> bool:
|
494
|
+
"""
|
495
|
+
Whether this path is a socket.
|
496
|
+
"""
|
497
|
+
try:
|
498
|
+
return S_ISSOCK(self.stat().st_mode)
|
499
|
+
except (FilesystemError, ValueError):
|
500
|
+
return False
|
501
|
+
|
502
|
+
def expanduser(self) -> TargetPath:
|
503
|
+
"""Return a new path with expanded ~ and ~user constructs
|
504
|
+
(as returned by os.path.expanduser)
|
505
|
+
"""
|
506
|
+
raise NotImplementedError("TargetPath.expanduser() is unsupported")
|