Skip to content

Commit 4bda3bd

Browse files
authored
Add convenience methods to FileName attribute (#48)
1 parent cce5fd7 commit 4bda3bd

File tree

5 files changed

+63
-1
lines changed

5 files changed

+63
-1
lines changed

dissect/ntfs/attr.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,11 @@ def last_access_time_ns(self) -> int:
455455
"""Return the ``$FILE_NAME`` file ``LastAccessTime`` in nanoseconds."""
456456
return ts_to_ns(self.attr.LastAccessTime)
457457

458+
@property
459+
def allocated_size(self) -> int:
460+
"""Return the ``$FILE_NAME`` file ``AllocatedLength``."""
461+
return self.attr.AllocatedLength
462+
458463
@property
459464
def file_size(self) -> int:
460465
"""Return the ``$FILE_NAME`` file ``FileSize``."""
@@ -463,7 +468,16 @@ def file_size(self) -> int:
463468
@property
464469
def file_attributes(self) -> int:
465470
"""Return the ``$FILE_NAME`` file ``FileAttributes``."""
466-
return self.attr.FileAttributes
471+
attributes = self.attr.FileAttributes
472+
473+
if attributes & c_ntfs.FILE_NAME_INDEX_PRESENT:
474+
attributes &= ~c_ntfs.FILE_NAME_INDEX_PRESENT
475+
attributes |= c_ntfs.FILE_ATTRIBUTE.DIRECTORY.value
476+
477+
if attributes == 0:
478+
attributes |= c_ntfs.FILE_ATTRIBUTE.NORMAL.value
479+
480+
return c_ntfs.FILE_ATTRIBUTE(attributes)
467481

468482
@property
469483
def flags(self) -> int:
@@ -479,6 +493,47 @@ def full_path(self) -> str:
479493
"""Use the parent directory reference to try to generate a full path from this file name."""
480494
return get_full_path(self.record.ntfs.mft, self.file_name, self.attr.ParentDirectory)
481495

496+
def is_dir(self) -> bool:
497+
"""Return whether this ``$FILE_NAME`` attribute represents a directory."""
498+
return bool(self.attr.FileAttributes & c_ntfs.FILE_NAME_INDEX_PRESENT)
499+
500+
def is_file(self) -> bool:
501+
"""Return whether this ``$FILE_NAME`` attribute represents a file."""
502+
return not self.is_dir()
503+
504+
def is_reparse_point(self) -> bool:
505+
"""Return whether this ``$FILE_NAME`` attribute represents a reparse point."""
506+
return bool(self.attr.FileAttributes & c_ntfs.FILE_ATTRIBUTE.REPARSE_POINT)
507+
508+
def is_symlink(self) -> bool:
509+
"""Return whether this ``$FILE_NAME`` attribute represents a symlink reparse point."""
510+
return self.is_reparse_point() and self.attr.ReparsePointTag == IO_REPARSE_TAG.SYMLINK
511+
512+
def is_mount_point(self) -> bool:
513+
"""Return whether this ``$FILE_NAME`` attribute represents a mount point reparse point."""
514+
return self.is_reparse_point() and self.attr.ReparsePointTag == IO_REPARSE_TAG.MOUNT_POINT
515+
516+
def is_cloud_file(self) -> bool:
517+
"""Return whether this ``$FILE_NAME`` attribute represents a cloud file."""
518+
return self.is_reparse_point() and self.attr.ReparsePointTag in (
519+
IO_REPARSE_TAG.CLOUD,
520+
IO_REPARSE_TAG.CLOUD_1,
521+
IO_REPARSE_TAG.CLOUD_2,
522+
IO_REPARSE_TAG.CLOUD_3,
523+
IO_REPARSE_TAG.CLOUD_4,
524+
IO_REPARSE_TAG.CLOUD_5,
525+
IO_REPARSE_TAG.CLOUD_6,
526+
IO_REPARSE_TAG.CLOUD_7,
527+
IO_REPARSE_TAG.CLOUD_8,
528+
IO_REPARSE_TAG.CLOUD_9,
529+
IO_REPARSE_TAG.CLOUD_A,
530+
IO_REPARSE_TAG.CLOUD_B,
531+
IO_REPARSE_TAG.CLOUD_C,
532+
IO_REPARSE_TAG.CLOUD_D,
533+
IO_REPARSE_TAG.CLOUD_E,
534+
IO_REPARSE_TAG.CLOUD_F,
535+
)
536+
482537

483538
class ReparsePoint(AttributeRecord):
484539
"""Specific :class:`AttributeRecord` parser for ``$REPARSE_POINT``."""

dissect/ntfs/c_ntfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@
191191
WCHAR FileName[FileNameLength];
192192
} FILE_NAME;
193193
194+
#define FILE_NAME_INDEX_PRESENT 0x10000000
195+
194196
enum IO_REPARSE_TAG : ULONG {
195197
RESERVED_ZERO = 0x00000000,
196198
RESERVED_ONE = 0x00000001,

dissect/ntfs/c_ntfs.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ class _c_ntfs(__cs__.cstruct):
347347
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
348348

349349
FILE_NAME: TypeAlias = _FILE_NAME
350+
FILE_NAME_INDEX_PRESENT: Literal[0x10000000] = ...
350351
class IO_REPARSE_TAG(__cs__.Enum):
351352
RESERVED_ZERO = ...
352353
RESERVED_ONE = ...

dissect/ntfs/index.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Index:
3131
"""Open an index with he given name on the given MFT record.
3232
3333
Args:
34+
record: The :class:`MftRecord` to open the index on.
3435
name: The index to open.
3536
3637
Raises:

dissect/ntfs/mft.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ def reparse_point_record(self) -> MftRecord:
385385
if reparse_point.relative:
386386
target_name = ntpath.join(ntpath.dirname(self.full_path()), target_name)
387387

388+
if not target_name:
389+
raise NotAReparsePointError(f"{self!r} does not have a valid reparse target")
390+
388391
return self.ntfs.mft.get(target_name)
389392

390393
def _get_stream_attributes(

0 commit comments

Comments
 (0)