dissect.target 3.19.dev58__py3-none-any.whl → 3.20__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. dissect/target/container.py +1 -1
  2. dissect/target/exceptions.py +6 -5
  3. dissect/target/filesystem.py +2 -2
  4. dissect/target/filesystems/btrfs.py +14 -5
  5. dissect/target/filesystems/config.py +5 -1
  6. dissect/target/filesystems/extfs.py +5 -4
  7. dissect/target/filesystems/fat.py +22 -16
  8. dissect/target/filesystems/ffs.py +11 -4
  9. dissect/target/filesystems/jffs.py +12 -7
  10. dissect/target/filesystems/ntfs.py +22 -6
  11. dissect/target/filesystems/overlay.py +14 -4
  12. dissect/target/filesystems/smb.py +3 -3
  13. dissect/target/filesystems/squashfs.py +4 -4
  14. dissect/target/filesystems/vmfs.py +4 -4
  15. dissect/target/filesystems/xfs.py +15 -8
  16. dissect/target/helpers/compat/path_common.py +5 -5
  17. dissect/target/helpers/configutil.py +128 -32
  18. dissect/target/helpers/cyber.py +2 -0
  19. dissect/target/helpers/data/windowsZones.xml +19 -23
  20. dissect/target/helpers/docs.py +1 -1
  21. dissect/target/helpers/keychain.py +2 -0
  22. dissect/target/helpers/mount.py +2 -1
  23. dissect/target/helpers/record.py +29 -2
  24. dissect/target/helpers/record_modifier.py +5 -1
  25. dissect/target/helpers/regutil.py +56 -26
  26. dissect/target/loader.py +1 -1
  27. dissect/target/loaders/mqtt.py +104 -9
  28. dissect/target/loaders/proxmox.py +68 -0
  29. dissect/target/loaders/vma.py +1 -1
  30. dissect/target/loaders/xva.py +1 -1
  31. dissect/target/plugin.py +24 -21
  32. dissect/target/plugins/apps/av/mcafee.py +2 -0
  33. dissect/target/plugins/apps/av/sophos.py +2 -0
  34. dissect/target/plugins/apps/av/trendmicro.py +2 -0
  35. dissect/target/plugins/apps/browser/chromium.py +27 -6
  36. dissect/target/plugins/apps/container/docker.py +48 -32
  37. dissect/target/plugins/apps/editor/__init__.py +0 -0
  38. dissect/target/plugins/apps/editor/editor.py +23 -0
  39. dissect/target/plugins/apps/{texteditor → editor}/windowsnotepad.py +40 -31
  40. dissect/target/plugins/apps/other/__init__.py +0 -0
  41. dissect/target/plugins/apps/other/env.py +56 -0
  42. dissect/target/plugins/apps/shell/powershell.py +6 -2
  43. dissect/target/plugins/apps/shell/wget.py +91 -0
  44. dissect/target/plugins/apps/ssh/openssh.py +2 -0
  45. dissect/target/plugins/apps/ssh/opensshd.py +2 -0
  46. dissect/target/plugins/apps/virtualization/__init__.py +0 -0
  47. dissect/target/plugins/apps/virtualization/vmware_workstation.py +61 -0
  48. dissect/target/plugins/apps/vpn/wireguard.py +9 -9
  49. dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
  50. dissect/target/plugins/apps/webserver/caddy.py +2 -0
  51. dissect/target/plugins/apps/webserver/nginx.py +2 -0
  52. dissect/target/plugins/child/esxi.py +3 -1
  53. dissect/target/plugins/child/parallels.py +68 -0
  54. dissect/target/plugins/child/proxmox.py +23 -0
  55. dissect/target/plugins/child/virtuozzo.py +12 -8
  56. dissect/target/plugins/child/vmware_workstation.py +23 -8
  57. dissect/target/plugins/filesystem/acquire_hash.py +2 -1
  58. dissect/target/plugins/filesystem/icat.py +15 -11
  59. dissect/target/plugins/filesystem/ntfs/mft.py +10 -6
  60. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
  61. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
  62. dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
  63. dissect/target/plugins/filesystem/unix/suid.py +4 -1
  64. dissect/target/plugins/filesystem/walkfs.py +2 -0
  65. dissect/target/plugins/general/example.py +2 -2
  66. dissect/target/plugins/general/loaders.py +18 -5
  67. dissect/target/plugins/general/network.py +20 -5
  68. dissect/target/plugins/general/osinfo.py +1 -0
  69. dissect/target/plugins/general/plugins.py +53 -10
  70. dissect/target/plugins/os/unix/_os.py +70 -44
  71. dissect/target/plugins/os/unix/applications.py +78 -0
  72. dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
  73. dissect/target/plugins/os/unix/bsd/osx/_os.py +4 -21
  74. dissect/target/plugins/os/unix/bsd/osx/network.py +92 -0
  75. dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
  76. dissect/target/plugins/os/unix/cronjobs.py +8 -4
  77. dissect/target/plugins/os/unix/etc/etc.py +4 -0
  78. dissect/target/plugins/os/unix/generic.py +2 -0
  79. dissect/target/plugins/os/unix/history.py +27 -25
  80. dissect/target/plugins/os/unix/linux/_os.py +8 -10
  81. dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
  82. dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
  83. dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
  84. dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py +0 -0
  85. dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +141 -0
  86. dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py +29 -0
  87. dissect/target/plugins/os/unix/linux/debian/snap.py +79 -0
  88. dissect/target/plugins/os/unix/linux/environ.py +2 -0
  89. dissect/target/plugins/os/unix/linux/fortios/_os.py +74 -63
  90. dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
  91. dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
  92. dissect/target/plugins/os/unix/linux/modules.py +2 -0
  93. dissect/target/plugins/os/unix/linux/netstat.py +2 -0
  94. dissect/target/{helpers → plugins/os/unix/linux}/network_managers.py +11 -9
  95. dissect/target/plugins/os/unix/linux/processes.py +2 -0
  96. dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
  97. dissect/target/plugins/os/unix/linux/services.py +5 -3
  98. dissect/target/plugins/os/unix/linux/sockets.py +2 -0
  99. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
  100. dissect/target/plugins/os/unix/locale.py +2 -0
  101. dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
  102. dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
  103. dissect/target/plugins/os/unix/locate/plocate.py +3 -1
  104. dissect/target/plugins/os/unix/log/atop.py +2 -0
  105. dissect/target/plugins/os/unix/log/audit.py +3 -1
  106. dissect/target/plugins/os/unix/log/auth.py +351 -38
  107. dissect/target/plugins/os/unix/log/journal.py +123 -101
  108. dissect/target/plugins/os/unix/log/lastlog.py +5 -3
  109. dissect/target/plugins/os/unix/log/messages.py +51 -27
  110. dissect/target/plugins/os/unix/log/utmp.py +52 -71
  111. dissect/target/plugins/os/unix/packagemanager.py +5 -38
  112. dissect/target/plugins/os/unix/shadow.py +3 -1
  113. dissect/target/plugins/os/unix/trash.py +132 -0
  114. dissect/target/plugins/os/windows/_os.py +22 -41
  115. dissect/target/plugins/os/windows/activitiescache.py +9 -4
  116. dissect/target/plugins/os/windows/adpolicy.py +2 -1
  117. dissect/target/plugins/os/windows/amcache.py +16 -13
  118. dissect/target/plugins/os/windows/defender.py +4 -3
  119. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
  120. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
  121. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
  122. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
  123. dissect/target/plugins/os/windows/env.py +1 -2
  124. dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
  125. dissect/target/plugins/os/windows/generic.py +68 -19
  126. dissect/target/plugins/os/windows/lnk.py +2 -0
  127. dissect/target/plugins/os/windows/locale.py +9 -3
  128. dissect/target/plugins/os/windows/log/etl.py +5 -4
  129. dissect/target/plugins/os/windows/log/evt.py +12 -8
  130. dissect/target/plugins/os/windows/log/evtx.py +9 -7
  131. dissect/target/plugins/os/windows/log/mssql.py +103 -0
  132. dissect/target/plugins/os/windows/log/pfro.py +2 -1
  133. dissect/target/plugins/os/windows/network.py +380 -0
  134. dissect/target/plugins/os/windows/notifications.py +6 -4
  135. dissect/target/plugins/os/windows/prefetch.py +7 -2
  136. dissect/target/plugins/os/windows/regf/7zip.py +9 -1
  137. dissect/target/plugins/os/windows/regf/applications.py +62 -0
  138. dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
  139. dissect/target/plugins/os/windows/regf/bam.py +3 -1
  140. dissect/target/plugins/os/windows/regf/cit.py +14 -12
  141. dissect/target/plugins/os/windows/regf/clsid.py +6 -3
  142. dissect/target/plugins/os/windows/regf/firewall.py +2 -1
  143. dissect/target/plugins/os/windows/regf/mru.py +9 -8
  144. dissect/target/plugins/os/windows/regf/nethist.py +6 -3
  145. dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
  146. dissect/target/plugins/os/windows/regf/regf.py +5 -1
  147. dissect/target/plugins/os/windows/regf/shellbags.py +351 -345
  148. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  149. dissect/target/plugins/os/windows/regf/usb.py +2 -1
  150. dissect/target/plugins/os/windows/regf/userassist.py +2 -1
  151. dissect/target/plugins/os/windows/registry.py +11 -0
  152. dissect/target/plugins/os/windows/services.py +3 -2
  153. dissect/target/plugins/os/windows/startupinfo.py +7 -2
  154. dissect/target/plugins/os/windows/syscache.py +5 -2
  155. dissect/target/plugins/os/windows/tasks.py +1 -1
  156. dissect/target/plugins/os/windows/thumbcache.py +11 -5
  157. dissect/target/plugins/os/windows/ual.py +12 -9
  158. dissect/target/plugins/os/windows/wer.py +21 -6
  159. dissect/target/plugins/os/windows/wua_history.py +0 -1
  160. dissect/target/target.py +13 -8
  161. dissect/target/tools/dump/utils.py +4 -0
  162. dissect/target/tools/fsutils.py +1 -1
  163. dissect/target/tools/info.py +1 -1
  164. dissect/target/tools/mount.py +15 -5
  165. dissect/target/tools/query.py +15 -9
  166. dissect/target/tools/shell.py +98 -9
  167. dissect/target/tools/utils.py +7 -7
  168. dissect/target/volume.py +4 -4
  169. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/METADATA +6 -2
  170. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/RECORD +176 -160
  171. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/WHEEL +1 -1
  172. dissect/target/helpers/targetd.py +0 -58
  173. dissect/target/loaders/targetd.py +0 -223
  174. dissect/target/plugins/apps/texteditor/texteditor.py +0 -13
  175. dissect/target/plugins/os/unix/etc.py +0 -9
  176. /dissect/target/plugins/apps/{texteditor → database}/__init__.py +0 -0
  177. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/COPYRIGHT +0 -0
  178. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/LICENSE +0 -0
  179. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/entry_points.txt +0 -0
  180. {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/top_level.txt +0 -0
@@ -239,7 +239,7 @@ def open(item: Union[list, str, BinaryIO, Path], *args, **kwargs):
239
239
  log.info("Failed to import %s", container)
240
240
  log.debug("", exc_info=e)
241
241
  except Exception as e:
242
- raise ContainerError(f"Failed to open container {item}", cause=e)
242
+ raise ContainerError(f"Failed to open container {item}") from e
243
243
  finally:
244
244
  if first_fh_opened:
245
245
  first_fh.close()
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import sys
3
5
  import traceback
@@ -7,13 +9,12 @@ from typing import Callable
7
9
  class Error(Exception):
8
10
  """Generic dissect.target error"""
9
11
 
10
- def __init__(self, message=None, cause=None, extra=None):
12
+ def __init__(self, message: str | None = None, extra: list[Exception] | None = None):
11
13
  if extra:
12
14
  exceptions = "\n\n".join(["".join(traceback.format_exception_only(type(e), e)) for e in extra])
13
15
  message = f"{message}\n\nAdditionally, the following exceptions occurred:\n\n{exceptions}"
14
16
 
15
17
  super().__init__(message)
16
- self.__cause__ = cause
17
18
  self.__extra__ = extra
18
19
 
19
20
 
@@ -72,15 +73,15 @@ class PluginNotFoundError(PluginError):
72
73
  """Plugin cannot be found."""
73
74
 
74
75
 
75
- class FileNotFoundError(FilesystemError):
76
+ class FileNotFoundError(FilesystemError, FileNotFoundError):
76
77
  """The requested path could not be found."""
77
78
 
78
79
 
79
- class IsADirectoryError(FilesystemError):
80
+ class IsADirectoryError(FilesystemError, IsADirectoryError):
80
81
  """The entry is a directory."""
81
82
 
82
83
 
83
- class NotADirectoryError(FilesystemError):
84
+ class NotADirectoryError(FilesystemError, NotADirectoryError):
84
85
  """The entry is not a directory."""
85
86
 
86
87
 
@@ -1142,7 +1142,7 @@ class VirtualFilesystem(Filesystem):
1142
1142
  try:
1143
1143
  return entry.top.get(fsutil.join(*parts[i:], alt_separator=self.alt_separator))
1144
1144
  except FilesystemError as e:
1145
- raise FileNotFoundError(full_path, cause=e)
1145
+ raise FileNotFoundError(full_path) from e
1146
1146
  else:
1147
1147
  raise FileNotFoundError(full_path)
1148
1148
 
@@ -1715,7 +1715,7 @@ def open(fh: BinaryIO, *args, **kwargs) -> Filesystem:
1715
1715
  log.info("Failed to import %s", filesystem)
1716
1716
  log.debug("", exc_info=e)
1717
1717
  except Exception as e:
1718
- raise FilesystemError(f"Failed to open filesystem for {fh}", cause=e)
1718
+ raise FilesystemError(f"Failed to open filesystem for {fh}") from e
1719
1719
  finally:
1720
1720
  fh.seek(offset)
1721
1721
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
3
4
  from typing import BinaryIO, Iterator, Optional, Union
4
5
 
5
6
  import dissect.btrfs as btrfs
@@ -78,13 +79,13 @@ class BtrfsSubvolumeFilesystem(Filesystem):
78
79
  try:
79
80
  return self.subvolume.get(path, node)
80
81
  except btrfs.FileNotFoundError as e:
81
- raise FileNotFoundError(path, cause=e)
82
+ raise FileNotFoundError(path) from e
82
83
  except btrfs.NotADirectoryError as e:
83
- raise NotADirectoryError(path, cause=e)
84
+ raise NotADirectoryError(path) from e
84
85
  except btrfs.NotASymlinkError as e:
85
- raise NotASymlinkError(path, cause=e)
86
+ raise NotASymlinkError(path) from e
86
87
  except btrfs.Error as e:
87
- raise FileNotFoundError(path, cause=e)
88
+ raise FileNotFoundError(path) from e
88
89
 
89
90
 
90
91
  class BtrfsFilesystemEntry(FilesystemEntry):
@@ -153,7 +154,7 @@ class BtrfsFilesystemEntry(FilesystemEntry):
153
154
  node = self.entry.inode
154
155
 
155
156
  # mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime
156
- st_info = st_info = fsutil.stat_result(
157
+ st_info = fsutil.stat_result(
157
158
  [
158
159
  entry.mode,
159
160
  entry.inum,
@@ -176,5 +177,13 @@ class BtrfsFilesystemEntry(FilesystemEntry):
176
177
 
177
178
  # Btrfs has a birth time, called otime
178
179
  st_info.st_birthtime = entry.otime.timestamp()
180
+ st_info.st_birthtime_ns = entry.otime_ns
181
+
182
+ # Add block information of the filesystem
183
+ st_info.st_blksize = entry.btrfs.sector_size
184
+
185
+ st_info.st_blocks = 0
186
+ if not self.is_dir():
187
+ st_info.st_blocks = (st_info.st_blksize // 512) * math.ceil(st_info.st_size / st_info.st_blksize)
179
188
 
180
189
  return st_info
@@ -133,7 +133,7 @@ class ConfigurationEntry(FilesystemEntry):
133
133
  Behaves like a ``directory`` when :attr:`parser_items` is a :class:`.ConfigurationParser` or a ``dict``.
134
134
  Behaves like a ``file`` otherwise.
135
135
 
136
- Attributes:
136
+ Args:
137
137
  parser_items: A dict-like object containing all configuration entries and values.
138
138
  In most cases this is either a :class:`.ConfigurationParser` or ``dict``.
139
139
  Otherwise, its the entry's value
@@ -247,10 +247,14 @@ class ConfigurationEntry(FilesystemEntry):
247
247
  Returns:
248
248
  A file-like object holding a byte representation of :attr:`parser_items`.
249
249
  """
250
+
250
251
  if isinstance(self.parser_items, ConfigurationParser):
251
252
  # Currently trying to open the underlying entry
252
253
  return self.entry.open()
253
254
 
255
+ if isinstance(self.parser_items, bytes):
256
+ return io.BytesIO(self.parser_items)
257
+
254
258
  output_data = self._write_value_mapping(self.parser_items)
255
259
  return io.BytesIO(bytes(output_data, "utf-8"))
256
260
 
@@ -33,13 +33,13 @@ class ExtFilesystem(Filesystem):
33
33
  try:
34
34
  return self.extfs.get(path, node)
35
35
  except extfs.FileNotFoundError as e:
36
- raise FileNotFoundError(path, cause=e)
36
+ raise FileNotFoundError(path) from e
37
37
  except extfs.NotADirectoryError as e:
38
- raise NotADirectoryError(path, cause=e)
38
+ raise NotADirectoryError(path) from e
39
39
  except extfs.NotASymlinkError as e:
40
- raise NotASymlinkError(path, cause=e)
40
+ raise NotASymlinkError(path) from e
41
41
  except extfs.Error as e:
42
- raise FileNotFoundError(path, cause=e)
42
+ raise FileNotFoundError(path) from e
43
43
 
44
44
 
45
45
  class ExtFilesystemEntry(FilesystemEntry):
@@ -128,6 +128,7 @@ class ExtFilesystemEntry(FilesystemEntry):
128
128
  # Set birthtime if available
129
129
  if self.entry.crtime:
130
130
  st_info.st_birthtime = self.entry.crtime.timestamp()
131
+ st_info.st_birthtime_ns = self.entry.crtime_ns
131
132
 
132
133
  # Set the nanosecond resolution separately
133
134
  st_info.st_atime_ns = self.entry.atime_ns
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import math
2
3
  import stat
3
4
  from typing import BinaryIO, Iterator, Optional, Union
4
5
 
@@ -41,11 +42,11 @@ class FatFilesystem(Filesystem):
41
42
  try:
42
43
  return self.fatfs.get(path, dirent=entry)
43
44
  except fat_exc.FileNotFoundError as e:
44
- raise FileNotFoundError(path, cause=e)
45
+ raise FileNotFoundError(path) from e
45
46
  except fat_exc.NotADirectoryError as e:
46
- raise NotADirectoryError(path, cause=e)
47
+ raise NotADirectoryError(path) from e
47
48
  except fat_exc.Error as e:
48
- raise FileNotFoundError(path, cause=e)
49
+ raise FileNotFoundError(path) from e
49
50
 
50
51
 
51
52
  class FatFilesystemEntry(FilesystemEntry):
@@ -100,16 +101,21 @@ class FatFilesystemEntry(FilesystemEntry):
100
101
  def lstat(self) -> fsutil.stat_result:
101
102
  """Return the stat information of the given path, without resolving links."""
102
103
  # mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime
103
- st_info = [
104
- (stat.S_IFDIR if self.is_dir() else stat.S_IFREG) | 0o777,
105
- self.entry.cluster,
106
- id(self.fs),
107
- 1,
108
- 0,
109
- 0,
110
- self.entry.size,
111
- self.entry.atime.replace(tzinfo=self.fs.tzinfo).timestamp(),
112
- self.entry.mtime.replace(tzinfo=self.fs.tzinfo).timestamp(),
113
- self.entry.ctime.replace(tzinfo=self.fs.tzinfo).timestamp(),
114
- ]
115
- return fsutil.stat_result(st_info)
104
+ st_info = fsutil.stat_result(
105
+ [
106
+ (stat.S_IFDIR if self.is_dir() else stat.S_IFREG) | 0o777,
107
+ self.entry.cluster,
108
+ id(self.fs),
109
+ 1,
110
+ 0,
111
+ 0,
112
+ self.entry.size,
113
+ self.entry.atime.replace(tzinfo=self.fs.tzinfo).timestamp(),
114
+ self.entry.mtime.replace(tzinfo=self.fs.tzinfo).timestamp(),
115
+ self.entry.ctime.replace(tzinfo=self.fs.tzinfo).timestamp(),
116
+ ]
117
+ )
118
+
119
+ st_info.st_blocks = math.ceil(self.entry.size / self.entry.fs.cluster_size)
120
+ st_info.st_blksize = self.entry.fs.cluster_size
121
+ return st_info
@@ -39,13 +39,13 @@ class FfsFilesystem(Filesystem):
39
39
  try:
40
40
  return self.ffs.get(path, node)
41
41
  except ffs.FileNotFoundError as e:
42
- raise FileNotFoundError(path, cause=e)
42
+ raise FileNotFoundError(path) from e
43
43
  except ffs.NotADirectoryError as e:
44
- raise NotADirectoryError(path, cause=e)
44
+ raise NotADirectoryError(path) from e
45
45
  except ffs.NotASymlinkError as e:
46
- raise NotASymlinkError(path, cause=e)
46
+ raise NotASymlinkError(path) from e
47
47
  except ffs.Error as e:
48
- raise FileNotFoundError(path, cause=e)
48
+ raise FileNotFoundError(path) from e
49
49
 
50
50
 
51
51
  class FfsFilesystemEntry(FilesystemEntry):
@@ -126,6 +126,12 @@ class FfsFilesystemEntry(FilesystemEntry):
126
126
  ]
127
127
  )
128
128
 
129
+ # Note: stat on linux always returns the default block size of 4096
130
+ # We are returning the actual block size of the filesystem, as on BSD
131
+ st_info.st_blksize = self.fs.ffs.block_size
132
+ # Note: st_blocks * 512 can be lower than st_blksize because FFS employs fragments
133
+ st_info.st_blocks = self.entry.nblocks
134
+
129
135
  # Set the nanosecond resolution separately
130
136
  st_info.st_atime_ns = self.entry.atime_ns
131
137
  st_info.st_mtime_ns = self.entry.mtime_ns
@@ -134,5 +140,6 @@ class FfsFilesystemEntry(FilesystemEntry):
134
140
  # FFS2 has a birth time, FFS1 does not
135
141
  if btime := self.entry.btime:
136
142
  st_info.st_birthtime = btime.timestamp()
143
+ st_info.st_birthtime_ns = self.entry.btime_ns
137
144
 
138
145
  return st_info
@@ -35,13 +35,13 @@ class JFFSFilesystem(Filesystem):
35
35
  try:
36
36
  return self.jffs2.get(path, node)
37
37
  except jffs2.FileNotFoundError as e:
38
- raise FileNotFoundError(path, cause=e)
38
+ raise FileNotFoundError(path) from e
39
39
  except jffs2.NotADirectoryError as e:
40
- raise NotADirectoryError(path, cause=e)
40
+ raise NotADirectoryError(path) from e
41
41
  except jffs2.NotASymlinkError as e:
42
- raise NotASymlinkError(path, cause=e)
42
+ raise NotASymlinkError(path) from e
43
43
  except jffs2.Error as e:
44
- raise FileNotFoundError(path, cause=e)
44
+ raise FileNotFoundError(path) from e
45
45
 
46
46
 
47
47
  class JFFSFilesystemEntry(FilesystemEntry):
@@ -76,13 +76,13 @@ class JFFSFilesystemEntry(FilesystemEntry):
76
76
  entry_path = fsutil.join(self.path, name, alt_separator=self.fs.alt_separator)
77
77
  yield JFFSFilesystemEntry(self.fs, entry_path, entry)
78
78
 
79
- def is_dir(self, follow_symlinks: bool = False) -> bool:
79
+ def is_dir(self, follow_symlinks: bool = True) -> bool:
80
80
  try:
81
81
  return self._resolve(follow_symlinks).entry.is_dir()
82
82
  except FilesystemError:
83
83
  return False
84
84
 
85
- def is_file(self, follow_symlinks: bool = False) -> bool:
85
+ def is_file(self, follow_symlinks: bool = True) -> bool:
86
86
  try:
87
87
  return self._resolve(follow_symlinks).entry.is_file()
88
88
  except FilesystemError:
@@ -97,7 +97,7 @@ class JFFSFilesystemEntry(FilesystemEntry):
97
97
 
98
98
  return self.entry.link
99
99
 
100
- def stat(self, follow_symlinks: bool = False) -> fsutil.stat_result:
100
+ def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result:
101
101
  return self._resolve(follow_symlinks).lstat()
102
102
 
103
103
  def lstat(self) -> fsutil.stat_result:
@@ -119,4 +119,9 @@ class JFFSFilesystemEntry(FilesystemEntry):
119
119
  ]
120
120
  )
121
121
 
122
+ # JFFS2 block size is a function of the "erase size" of the underlying flash device.
123
+ # Linux stat reports the default block size, which is defined as 4k in libc.
124
+ st_info.st_blksize = 4096
125
+ st_info.st_blocks = (node.isize + 511) // 512 if self.is_file() else 0
126
+
122
127
  return st_info
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
3
4
  import stat
4
5
  from typing import BinaryIO, Iterator, Optional
5
6
 
@@ -47,12 +48,12 @@ class NtfsFilesystem(Filesystem):
47
48
  try:
48
49
  path = path.rsplit(":", maxsplit=1)[0]
49
50
  return self.ntfs.mft.get(path, root=root)
50
- except NtfsFileNotFoundError:
51
- raise FileNotFoundError(path)
51
+ except NtfsFileNotFoundError as e:
52
+ raise FileNotFoundError(path) from e
52
53
  except NtfsNotADirectoryError as e:
53
- raise NotADirectoryError(path, cause=e)
54
+ raise NotADirectoryError(path) from e
54
55
  except NtfsError as e:
55
- raise FileNotFoundError(path, cause=e)
56
+ raise FileNotFoundError(path) from e
56
57
 
57
58
 
58
59
  class NtfsFilesystemEntry(FilesystemEntry):
@@ -151,12 +152,14 @@ class NtfsFilesystemEntry(FilesystemEntry):
151
152
  record = self.dereference()
152
153
 
153
154
  size = 0
155
+ real_size = 0
154
156
  if self.is_symlink():
155
157
  mode = stat.S_IFLNK
156
158
  elif self.is_file():
157
159
  mode = stat.S_IFREG
158
160
  try:
159
161
  size = record.size(self.ads)
162
+ real_size = record.size(self.ads, allocated=True)
160
163
  except NtfsFileNotFoundError as e:
161
164
  # Occurs when it cannot find the the specific ads inside its attributes
162
165
  raise FileNotFoundError from e
@@ -176,16 +179,29 @@ class NtfsFilesystemEntry(FilesystemEntry):
176
179
  0,
177
180
  size,
178
181
  stdinfo.last_access_time.timestamp(),
179
- stdinfo.last_change_time.timestamp(),
182
+ stdinfo.last_modification_time.timestamp(),
183
+ # ctime gets set to creation time for python <3.12 purposes
180
184
  stdinfo.creation_time.timestamp(),
181
185
  ]
182
186
  )
183
187
 
184
188
  # Set the nanosecond resolution separately
185
189
  st_info.st_atime_ns = stdinfo.last_access_time_ns
186
- st_info.st_mtime_ns = stdinfo.last_change_time_ns
190
+ st_info.st_mtime_ns = stdinfo.last_modification_time_ns
191
+
187
192
  st_info.st_ctime_ns = stdinfo.creation_time_ns
188
193
 
194
+ st_info.st_birthtime = stdinfo.creation_time.timestamp()
195
+ st_info.st_birthtime_ns = stdinfo.creation_time_ns
196
+
197
+ # real_size is none if the size is resident
198
+ st_info.st_blksize = record.ntfs.cluster_size
199
+ blocks = 0
200
+ if not record.resident:
201
+ blocks = math.ceil(real_size / 512)
202
+
203
+ st_info.st_blocks = blocks
204
+
189
205
  return st_info
190
206
 
191
207
  def attr(self) -> AttributeMap:
@@ -89,15 +89,25 @@ class Overlay2Filesystem(LayerFilesystem):
89
89
 
90
90
  # append and mount every layer
91
91
  for dest, layer in layers:
92
- if layer.is_file() and dest in ["/etc/hosts", "/etc/hostname", "/etc/resolv.conf"]:
92
+ # we could have collected a layer reference that actually does not exist on the host
93
+ if not layer.exists():
94
+ log.warning(
95
+ "Can not mount layer %s for container %s as it does not exist on the host", layer, path.name
96
+ )
97
+ continue
98
+
99
+ # mount points can be files
100
+ if layer.is_file():
93
101
  layer_fs = VirtualFilesystem()
94
- layer_fs.map_file_fh("/etc/" + layer.name, layer.open("rb"))
95
- dest = dest.split("/")[0]
102
+ layer_fs.map_file_fh(dest, layer.open("rb"))
96
103
 
104
+ # regular overlay2 layers are directories
105
+ # mount points can be directories too
97
106
  else:
98
107
  layer_fs = DirectoryFilesystem(layer)
99
108
 
100
- self.append_layer().mount(dest, layer_fs)
109
+ log.info("Adding layer %s to destination %s", layer, dest)
110
+ self.append_layer().mount("/" if layer.is_file() else dest, layer_fs)
101
111
 
102
112
  def __repr__(self) -> str:
103
113
  return f"<{self.__class__.__name__} {self.base_path}>"
@@ -58,10 +58,10 @@ class SmbFilesystem(Filesystem):
58
58
  except SessionError as e:
59
59
  if e.error == STATUS_NOT_A_DIRECTORY:
60
60
  # STATUS_NOT_A_DIRECTORY
61
- raise NotADirectoryError(path, cause=e)
61
+ raise NotADirectoryError(path) from e
62
62
  else:
63
63
  # 0xC000000F is STATUS_NO_SUCH_FILE, but everything else should raise a FileNotFoundError anyway
64
- raise FileNotFoundError(path, cause=e)
64
+ raise FileNotFoundError(path) from e
65
65
 
66
66
  if len(result) != 1:
67
67
  raise FileNotFoundError(path)
@@ -106,7 +106,7 @@ class SmbFilesystemEntry(FilesystemEntry):
106
106
  try:
107
107
  return SmbStream(self.fs.conn, self.fs.share_name, self.path, self.entry.get_filesize())
108
108
  except SessionError as e:
109
- raise FilesystemError(f"Failed to open file: {self.path}", cause=e)
109
+ raise FilesystemError(f"Failed to open file: {self.path}") from e
110
110
 
111
111
  def is_dir(self, follow_symlinks: bool = True) -> bool:
112
112
  try:
@@ -32,13 +32,13 @@ class SquashFSFilesystem(Filesystem):
32
32
  try:
33
33
  return self.squashfs.get(path, node)
34
34
  except exceptions.FileNotFoundError as e:
35
- raise FileNotFoundError(path, cause=e)
35
+ raise FileNotFoundError(path) from e
36
36
  except exceptions.NotADirectoryError as e:
37
- raise NotADirectoryError(path, cause=e)
37
+ raise NotADirectoryError(path) from e
38
38
  except exceptions.NotASymlinkError as e:
39
- raise NotASymlinkError(path, cause=e)
39
+ raise NotASymlinkError(path) from e
40
40
  except exceptions.Error as e:
41
- raise FileNotFoundError(path, cause=e)
41
+ raise FileNotFoundError(path) from e
42
42
 
43
43
 
44
44
  class SquashFSFilesystemEntry(FilesystemEntry):
@@ -41,13 +41,13 @@ class VmfsFilesystem(Filesystem):
41
41
  try:
42
42
  return self.vmfs.get(path, node)
43
43
  except vmfs.FileNotFoundError as e:
44
- raise FileNotFoundError(path, cause=e)
44
+ raise FileNotFoundError(path) from e
45
45
  except vmfs.NotADirectoryError as e:
46
- raise NotADirectoryError(path, cause=e)
46
+ raise NotADirectoryError(path) from e
47
47
  except vmfs.NotASymlinkError as e:
48
- raise NotASymlinkError(path, cause=e)
48
+ raise NotASymlinkError(path) from e
49
49
  except vmfs.Error as e:
50
- raise FileNotFoundError(path, cause=e)
50
+ raise FileNotFoundError(path) from e
51
51
 
52
52
 
53
53
  class VmfsFilesystemEntry(FilesystemEntry):
@@ -35,14 +35,14 @@ class XfsFilesystem(Filesystem):
35
35
  def _get_node(self, path: str, node: Optional[xfs.INode] = None) -> xfs.INode:
36
36
  try:
37
37
  return self.xfs.get(path, node)
38
- except xfs.FileNotFoundError:
39
- raise FileNotFoundError(path)
40
- except xfs.NotADirectoryError:
41
- raise NotADirectoryError(path)
42
- except xfs.NotASymlinkError:
43
- raise NotASymlinkError(path)
38
+ except xfs.FileNotFoundError as e:
39
+ raise FileNotFoundError(path) from e
40
+ except xfs.NotADirectoryError as e:
41
+ raise NotADirectoryError(path) from e
42
+ except xfs.NotASymlinkError as e:
43
+ raise NotASymlinkError(path) from e
44
44
  except xfs.Error as e:
45
- raise FileNotFoundError(path, cause=e)
45
+ raise FileNotFoundError(path) from e
46
46
 
47
47
 
48
48
  class XfsFilesystemEntry(FilesystemEntry):
@@ -130,8 +130,15 @@ class XfsFilesystemEntry(FilesystemEntry):
130
130
  st_info.st_mtime_ns = self.entry.mtime_ns
131
131
  st_info.st_ctime_ns = self.entry.ctime_ns
132
132
 
133
- # XFS has a birth time, called crtime
133
+ st_info.st_blksize = self.fs.xfs.block_size
134
+ # Convert number of filesystem blocks to basic blocks
135
+ # Reference: https://github.com/torvalds/linux/blob/e32cde8d2bd7d251a8f9b434143977ddf13dcec6/fs/xfs/xfs_iops.c#L602 # noqa: E501
136
+ # Note that block size in XFS is always a multiple of 512, so the division below is safe
137
+ st_info.st_blocks = self.entry.nblocks * (self.fs.xfs.block_size // 512)
138
+
139
+ # XFS has a birth time, since inode version 3 (version 5 of filesystem)
134
140
  st_info.st_birthtime = self.entry.crtime.timestamp()
141
+ st_info.st_birthtime_ns = self.entry.crtime_ns
135
142
 
136
143
  return st_info
137
144
 
@@ -56,11 +56,11 @@ class _DissectScandirIterator:
56
56
 
57
57
  The _DissectScandirIterator provides a context manager, so scandir can be called as:
58
58
 
59
- ```
60
- with scandir(path) as it:
61
- for entry in it
62
- print(entry.name)
63
- ```
59
+ .. code-block:: python
60
+
61
+ with scandir(path) as it:
62
+ for entry in it
63
+ print(entry.name)
64
64
 
65
65
  similar to os.scandir() behaviour since Python 3.6.
66
66
  """