dissect.target 3.14.dev29__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 -12
- 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 -16
- 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.dev29.dist-info → dissect.target-3.15.dist-info}/METADATA +4 -3
- {dissect.target-3.14.dev29.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.dev29.dist-info → dissect.target-3.15.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/LICENSE +0 -0
- {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/WHEEL +0 -0
- {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,443 @@
|
|
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.12 implementation.
|
14
|
+
|
15
|
+
Commit hash we're in sync with:
|
16
|
+
|
17
|
+
Notes:
|
18
|
+
- CPython 3.12 changed a lot in preparation of proper subclassing, so our patches differ
|
19
|
+
a lot from previous versions
|
20
|
+
- Flavours don't really exist anymore, but since we kind of "multi-flavour" we need to emulate it
|
21
|
+
"""
|
22
|
+
|
23
|
+
from __future__ import annotations
|
24
|
+
|
25
|
+
import posixpath
|
26
|
+
import sys
|
27
|
+
from pathlib import Path, PurePath
|
28
|
+
from stat import S_ISBLK, S_ISCHR, S_ISFIFO, S_ISSOCK
|
29
|
+
from typing import IO, TYPE_CHECKING, Iterator, Optional
|
30
|
+
|
31
|
+
from dissect.target import filesystem
|
32
|
+
from dissect.target.exceptions import FilesystemError, SymlinkRecursionError
|
33
|
+
from dissect.target.helpers import polypath
|
34
|
+
from dissect.target.helpers.compat.path_common import (
|
35
|
+
io_open,
|
36
|
+
isjunction,
|
37
|
+
realpath,
|
38
|
+
scandir,
|
39
|
+
)
|
40
|
+
|
41
|
+
if TYPE_CHECKING:
|
42
|
+
from dissect.target.filesystem import Filesystem, FilesystemEntry
|
43
|
+
from dissect.target.helpers.compat.path_common import _DissectScandirIterator
|
44
|
+
from dissect.target.helpers.fsutil import stat_result
|
45
|
+
|
46
|
+
|
47
|
+
class _DissectFlavour:
|
48
|
+
sep = "/"
|
49
|
+
altsep = ""
|
50
|
+
case_sensitive = False
|
51
|
+
|
52
|
+
__variant_instances = {}
|
53
|
+
|
54
|
+
def __new__(cls, case_sensitive: bool = False, alt_separator: str = ""):
|
55
|
+
idx = (case_sensitive, alt_separator)
|
56
|
+
instance = cls.__variant_instances.get(idx, None)
|
57
|
+
if instance is None:
|
58
|
+
instance = object.__new__(cls)
|
59
|
+
cls.__variant_instances[idx] = instance
|
60
|
+
|
61
|
+
return instance
|
62
|
+
|
63
|
+
def __init__(self, case_sensitive: bool = False, alt_separator: str = ""):
|
64
|
+
self.altsep = alt_separator
|
65
|
+
self.case_sensitive = case_sensitive
|
66
|
+
|
67
|
+
def normcase(self, s: str) -> str:
|
68
|
+
return s if self.case_sensitive else s.lower()
|
69
|
+
|
70
|
+
splitdrive = staticmethod(posixpath.splitdrive)
|
71
|
+
|
72
|
+
def splitroot(self, part: str) -> tuple[str, str]:
|
73
|
+
return polypath.splitroot(part, alt_separator=self.altsep)
|
74
|
+
|
75
|
+
def join(self, *args) -> str:
|
76
|
+
return polypath.join(*args, alt_separator=self.altsep)
|
77
|
+
|
78
|
+
# NOTE: Fallback implementation from older versions of pathlib.py
|
79
|
+
def ismount(self, path: TargetPath) -> bool:
|
80
|
+
# Need to exist and be a dir
|
81
|
+
if not path.exists() or not path.is_dir():
|
82
|
+
return False
|
83
|
+
|
84
|
+
try:
|
85
|
+
parent_dev = path.parent.stat().st_dev
|
86
|
+
except FilesystemError:
|
87
|
+
return False
|
88
|
+
|
89
|
+
dev = path.stat().st_dev
|
90
|
+
if dev != parent_dev:
|
91
|
+
return True
|
92
|
+
ino = path.stat().st_ino
|
93
|
+
parent_ino = path.parent.stat().st_ino
|
94
|
+
return ino == parent_ino
|
95
|
+
|
96
|
+
isjunction = staticmethod(isjunction)
|
97
|
+
|
98
|
+
samestat = staticmethod(posixpath.samestat)
|
99
|
+
|
100
|
+
def isabs(self, path: str) -> bool:
|
101
|
+
return polypath.isabs(path, alt_separator=self.altsep)
|
102
|
+
|
103
|
+
realpath = staticmethod(realpath)
|
104
|
+
|
105
|
+
|
106
|
+
class PureDissectPath(PurePath):
|
107
|
+
_fs: Filesystem
|
108
|
+
_flavour = _DissectFlavour(case_sensitive=False)
|
109
|
+
|
110
|
+
def __reduce__(self) -> tuple:
|
111
|
+
raise TypeError("TargetPath pickling is currently not supported")
|
112
|
+
|
113
|
+
def __init__(self, fs: Filesystem, *pathsegments):
|
114
|
+
if not isinstance(fs, filesystem.Filesystem):
|
115
|
+
raise TypeError(
|
116
|
+
"invalid PureDissectPath initialization: missing filesystem, "
|
117
|
+
"got %r (this might be a bug, please report)" % pathsegments
|
118
|
+
)
|
119
|
+
|
120
|
+
alt_separator = fs.alt_separator
|
121
|
+
path_args = []
|
122
|
+
for arg in pathsegments:
|
123
|
+
if isinstance(arg, str):
|
124
|
+
arg = polypath.normalize(arg, alt_separator=alt_separator)
|
125
|
+
path_args.append(arg)
|
126
|
+
|
127
|
+
super().__init__(*path_args)
|
128
|
+
self._fs = fs
|
129
|
+
self._flavour = _DissectFlavour(alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive)
|
130
|
+
|
131
|
+
def with_segments(self, *pathsegments) -> TargetPath:
|
132
|
+
return type(self)(self._fs, *pathsegments)
|
133
|
+
|
134
|
+
# NOTE: This is copied from pathlib.py but turned into an instance method so we get access to the correct flavour
|
135
|
+
def _parse_path(self, path: str) -> tuple[str, str, list[str]]:
|
136
|
+
if not path:
|
137
|
+
return "", "", []
|
138
|
+
sep = self._flavour.sep
|
139
|
+
altsep = self._flavour.altsep
|
140
|
+
if altsep:
|
141
|
+
path = path.replace(altsep, sep)
|
142
|
+
drv, root, rel = self._flavour.splitroot(path)
|
143
|
+
if not root and drv.startswith(sep) and not drv.endswith(sep):
|
144
|
+
drv_parts = drv.split(sep)
|
145
|
+
if len(drv_parts) == 4 and drv_parts[2] not in "?.":
|
146
|
+
# e.g. //server/share
|
147
|
+
root = sep
|
148
|
+
elif len(drv_parts) == 6:
|
149
|
+
# e.g. //?/unc/server/share
|
150
|
+
root = sep
|
151
|
+
parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != "."]
|
152
|
+
return drv, root, parsed
|
153
|
+
|
154
|
+
def is_reserved(self) -> bool:
|
155
|
+
"""Return True if the path contains one of the special names reserved
|
156
|
+
by the system, if any."""
|
157
|
+
return False
|
158
|
+
|
159
|
+
|
160
|
+
class TargetPath(Path, PureDissectPath):
|
161
|
+
__slots__ = ("_entry",)
|
162
|
+
|
163
|
+
def get(self) -> FilesystemEntry:
|
164
|
+
try:
|
165
|
+
return self._entry
|
166
|
+
except AttributeError:
|
167
|
+
self._entry = self._fs.get(str(self))
|
168
|
+
return self._entry
|
169
|
+
|
170
|
+
def stat(self, *, follow_symlinks: bool = True) -> stat_result:
|
171
|
+
"""
|
172
|
+
Return the result of the stat() system call on this path, like
|
173
|
+
os.stat() does.
|
174
|
+
"""
|
175
|
+
if follow_symlinks:
|
176
|
+
return self.get().stat()
|
177
|
+
else:
|
178
|
+
return self.get().lstat()
|
179
|
+
|
180
|
+
def exists(self, *, follow_symlinks: bool = True) -> bool:
|
181
|
+
"""
|
182
|
+
Whether this path exists.
|
183
|
+
|
184
|
+
This method normally follows symlinks; to check whether a symlink exists,
|
185
|
+
add the argument follow_symlinks=False.
|
186
|
+
"""
|
187
|
+
try:
|
188
|
+
# .exists() must resolve possible symlinks
|
189
|
+
self.stat(follow_symlinks=follow_symlinks)
|
190
|
+
return True
|
191
|
+
except (FilesystemError, ValueError):
|
192
|
+
return False
|
193
|
+
|
194
|
+
def is_dir(self) -> bool:
|
195
|
+
"""
|
196
|
+
Whether this path is a directory.
|
197
|
+
"""
|
198
|
+
try:
|
199
|
+
return self.get().is_dir()
|
200
|
+
except (FilesystemError, ValueError):
|
201
|
+
return False
|
202
|
+
|
203
|
+
def is_file(self) -> bool:
|
204
|
+
"""
|
205
|
+
Whether this path is a regular file (also True for symlinks pointing
|
206
|
+
to regular files).
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
return self.get().is_file()
|
210
|
+
except (FilesystemError, ValueError):
|
211
|
+
return False
|
212
|
+
|
213
|
+
def is_symlink(self) -> bool:
|
214
|
+
"""
|
215
|
+
Whether this path is a symbolic link.
|
216
|
+
"""
|
217
|
+
try:
|
218
|
+
return self.get().is_symlink()
|
219
|
+
except (FilesystemError, ValueError):
|
220
|
+
return False
|
221
|
+
|
222
|
+
def is_block_device(self) -> bool:
|
223
|
+
"""
|
224
|
+
Whether this path is a block device.
|
225
|
+
"""
|
226
|
+
try:
|
227
|
+
return S_ISBLK(self.stat().st_mode)
|
228
|
+
except (FilesystemError, ValueError):
|
229
|
+
return False
|
230
|
+
|
231
|
+
def is_char_device(self) -> bool:
|
232
|
+
"""
|
233
|
+
Whether this path is a character device.
|
234
|
+
"""
|
235
|
+
try:
|
236
|
+
return S_ISCHR(self.stat().st_mode)
|
237
|
+
except (FilesystemError, ValueError):
|
238
|
+
return False
|
239
|
+
|
240
|
+
def is_fifo(self) -> bool:
|
241
|
+
"""
|
242
|
+
Whether this path is a FIFO.
|
243
|
+
"""
|
244
|
+
try:
|
245
|
+
return S_ISFIFO(self.stat().st_mode)
|
246
|
+
except (FilesystemError, ValueError):
|
247
|
+
return False
|
248
|
+
|
249
|
+
def is_socket(self) -> bool:
|
250
|
+
"""
|
251
|
+
Whether this path is a socket.
|
252
|
+
"""
|
253
|
+
try:
|
254
|
+
return S_ISSOCK(self.stat().st_mode)
|
255
|
+
except (FilesystemError, ValueError):
|
256
|
+
return False
|
257
|
+
|
258
|
+
def open(
|
259
|
+
self,
|
260
|
+
mode: str = "rb",
|
261
|
+
buffering: int = 0,
|
262
|
+
encoding: Optional[str] = None,
|
263
|
+
errors: Optional[str] = None,
|
264
|
+
newline: Optional[str] = None,
|
265
|
+
) -> IO:
|
266
|
+
"""Open file and return a stream.
|
267
|
+
|
268
|
+
Supports a subset of features of the real pathlib.open/io.open.
|
269
|
+
|
270
|
+
Note: in contrast to regular Python, the mode is binary by default. Text mode
|
271
|
+
has to be explicitly specified. Buffering is also disabled by default.
|
272
|
+
"""
|
273
|
+
return io_open(self, mode, buffering, encoding, errors, newline)
|
274
|
+
|
275
|
+
def write_bytes(self, data: bytes) -> int:
|
276
|
+
"""
|
277
|
+
Open the file in bytes mode, write to it, and close the file.
|
278
|
+
"""
|
279
|
+
raise NotImplementedError("TargetPath.write_bytes() is unsupported")
|
280
|
+
|
281
|
+
def write_text(
|
282
|
+
self, data: str, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None
|
283
|
+
) -> int:
|
284
|
+
"""
|
285
|
+
Open the file in text mode, write to it, and close the file.
|
286
|
+
"""
|
287
|
+
raise NotImplementedError("TargetPath.write_text() is unsupported")
|
288
|
+
|
289
|
+
def iterdir(self) -> Iterator[TargetPath]:
|
290
|
+
"""Iterate over the files in this directory. Does not yield any
|
291
|
+
result for the special paths '.' and '..'.
|
292
|
+
"""
|
293
|
+
for entry in scandir(self):
|
294
|
+
if entry.name in {".", ".."}:
|
295
|
+
# Yielding a path object for these makes little sense
|
296
|
+
continue
|
297
|
+
child_path = self._make_child_relpath(entry.name)
|
298
|
+
child_path._entry = entry
|
299
|
+
yield child_path
|
300
|
+
|
301
|
+
def _scandir(self) -> _DissectScandirIterator:
|
302
|
+
return scandir(self)
|
303
|
+
|
304
|
+
@classmethod
|
305
|
+
def cwd(cls) -> TargetPath:
|
306
|
+
"""Return a new path pointing to the current working directory."""
|
307
|
+
raise NotImplementedError("TargetPath.cwd() is unsupported")
|
308
|
+
|
309
|
+
@classmethod
|
310
|
+
def home(cls) -> TargetPath:
|
311
|
+
"""Return a new path pointing to the user's home directory (as
|
312
|
+
returned by os.path.expanduser('~')).
|
313
|
+
"""
|
314
|
+
raise NotImplementedError("TargetPath.home() is unsupported")
|
315
|
+
|
316
|
+
def absolute(self) -> TargetPath:
|
317
|
+
"""Return an absolute version of this path by prepending the current
|
318
|
+
working directory. No normalization or symlink resolution is performed.
|
319
|
+
|
320
|
+
Use resolve() to get the canonical path to a file.
|
321
|
+
"""
|
322
|
+
raise NotImplementedError("TargetPath.absolute() is unsupported in Dissect")
|
323
|
+
|
324
|
+
# NOTE: We changed some of the error handling here to deal with our own exception types
|
325
|
+
def resolve(self, strict: bool = False) -> TargetPath:
|
326
|
+
"""
|
327
|
+
Make the path absolute, resolving all symlinks on the way and also
|
328
|
+
normalizing it.
|
329
|
+
"""
|
330
|
+
|
331
|
+
s = self._flavour.realpath(self, strict=strict)
|
332
|
+
p = self.with_segments(s)
|
333
|
+
|
334
|
+
# In non-strict mode, realpath() doesn't raise on symlink loops.
|
335
|
+
# Ensure we get an exception by calling stat()
|
336
|
+
if not strict:
|
337
|
+
try:
|
338
|
+
p.stat()
|
339
|
+
except FilesystemError as e:
|
340
|
+
if isinstance(e, SymlinkRecursionError):
|
341
|
+
raise
|
342
|
+
return p
|
343
|
+
|
344
|
+
def owner(self) -> str:
|
345
|
+
"""
|
346
|
+
Return the login name of the file owner.
|
347
|
+
"""
|
348
|
+
raise NotImplementedError("TargetPath.owner() is unsupported")
|
349
|
+
|
350
|
+
def group(self) -> str:
|
351
|
+
"""
|
352
|
+
Return the group name of the file gid.
|
353
|
+
"""
|
354
|
+
raise NotImplementedError("TargetPath.group() is unsupported")
|
355
|
+
|
356
|
+
def readlink(self) -> TargetPath:
|
357
|
+
"""
|
358
|
+
Return the path to which the symbolic link points.
|
359
|
+
"""
|
360
|
+
return self.with_segments(self.get().readlink())
|
361
|
+
|
362
|
+
def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
|
363
|
+
"""
|
364
|
+
Create this file with the given access mode, if it doesn't exist.
|
365
|
+
"""
|
366
|
+
raise NotImplementedError("TargetPath.touch() is unsupported")
|
367
|
+
|
368
|
+
def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None:
|
369
|
+
"""
|
370
|
+
Create a new directory at this given path.
|
371
|
+
"""
|
372
|
+
raise NotImplementedError("TargetPath.mkdir() is unsupported")
|
373
|
+
|
374
|
+
def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
|
375
|
+
"""
|
376
|
+
Change the permissions of the path, like os.chmod().
|
377
|
+
"""
|
378
|
+
raise NotImplementedError("TargetPath.chmod() is unsupported")
|
379
|
+
|
380
|
+
def lchmod(self, mode: int) -> None:
|
381
|
+
"""
|
382
|
+
Like chmod(), except if the path points to a symlink, the symlink's
|
383
|
+
permissions are changed, rather than its target's.
|
384
|
+
"""
|
385
|
+
raise NotImplementedError("TargetPath.lchmod() is unsupported")
|
386
|
+
|
387
|
+
def unlink(self, missing_ok: bool = False) -> None:
|
388
|
+
"""
|
389
|
+
Remove this file or link.
|
390
|
+
If the path is a directory, use rmdir() instead.
|
391
|
+
"""
|
392
|
+
raise NotImplementedError("TargetPath.unlink() is unsupported")
|
393
|
+
|
394
|
+
def rmdir(self) -> None:
|
395
|
+
"""
|
396
|
+
Remove this directory. The directory must be empty.
|
397
|
+
"""
|
398
|
+
raise NotImplementedError("TargetPath.rmdir() is unsupported")
|
399
|
+
|
400
|
+
def rename(self, target: str) -> TargetPath:
|
401
|
+
"""
|
402
|
+
Rename this path to the target path.
|
403
|
+
|
404
|
+
The target path may be absolute or relative. Relative paths are
|
405
|
+
interpreted relative to the current working directory, *not* the
|
406
|
+
directory of the Path object.
|
407
|
+
|
408
|
+
Returns the new Path instance pointing to the target path.
|
409
|
+
"""
|
410
|
+
raise NotImplementedError("TargetPath.rename() is unsupported")
|
411
|
+
|
412
|
+
def replace(self, target: str) -> TargetPath:
|
413
|
+
"""
|
414
|
+
Rename this path to the target path, overwriting if that path exists.
|
415
|
+
|
416
|
+
The target path may be absolute or relative. Relative paths are
|
417
|
+
interpreted relative to the current working directory, *not* the
|
418
|
+
directory of the Path object.
|
419
|
+
|
420
|
+
Returns the new Path instance pointing to the target path.
|
421
|
+
"""
|
422
|
+
raise NotImplementedError("TargetPath.replace() is unsupported")
|
423
|
+
|
424
|
+
def symlink_to(self, target: str, target_is_directory: bool = False) -> None:
|
425
|
+
"""
|
426
|
+
Make this path a symlink pointing to the target path.
|
427
|
+
Note the order of arguments (link, target) is the reverse of os.symlink.
|
428
|
+
"""
|
429
|
+
raise NotImplementedError("TargetPath.symlink_to() is unsupported")
|
430
|
+
|
431
|
+
def hardlink_to(self, target: str) -> None:
|
432
|
+
"""
|
433
|
+
Make this path a hard link pointing to the same file as *target*.
|
434
|
+
|
435
|
+
Note the order of arguments (self, target) is the reverse of os.link's.
|
436
|
+
"""
|
437
|
+
raise NotImplementedError("TargetPath.hardlink_to() is unsupported")
|
438
|
+
|
439
|
+
def expanduser(self) -> TargetPath:
|
440
|
+
"""Return a new path with expanded ~ and ~user constructs
|
441
|
+
(as returned by os.path.expanduser)
|
442
|
+
"""
|
443
|
+
raise NotImplementedError("TargetPath.expanduser() is unsupported")
|