common/unixStatAdapter.go (355 lines of code) (raw):

package common import ( "os" "strconv" "time" ) const ( // POSIX property metadata POSIXNlinkMeta = "posix_nlink" POSIXINodeMeta = "posix_ino" POSIXCTimeMeta = "posix_ctime" LINUXBTimeMeta = "linux_btime" POSIXBlockDeviceMeta = "is_block_dev" // todo: read & use these POSIXCharDeviceMeta = "is_char_dev" POSIXSocketMeta = "is_socket" POSIXFIFOMeta = "is_fifo" POSIXDevMeta = "posix_dev" POSIXRDevMeta = "posix_rdev" POSIXATimeMeta = "posix_atime" POSIXFolderMeta = "hdi_isfolder" // todo: read & use these POSIXSymlinkMeta = "is_symlink" POSIXOwnerMeta = "posix_owner" POSIXGroupMeta = "posix_group" POSIXModeMeta = "permissions" POSIXModTimeMeta = "modtime" LINUXAttributeMeta = "linux_attribute" LINUXAttributeMaskMeta = "linux_attribute_mask" LINUXStatxMaskMeta = "linux_statx_mask" ) var AllLinuxProperties = []string{ POSIXNlinkMeta, POSIXINodeMeta, LINUXBTimeMeta, POSIXBlockDeviceMeta, POSIXCharDeviceMeta, POSIXSocketMeta, POSIXFIFOMeta, POSIXDevMeta, POSIXRDevMeta, POSIXATimeMeta, POSIXFolderMeta, POSIXSymlinkMeta, POSIXOwnerMeta, POSIXGroupMeta, POSIXModeMeta, LINUXStatxMaskMeta, LINUXAttributeMaskMeta, POSIXCTimeMeta, POSIXModTimeMeta, LINUXAttributeMeta, } //goland:noinspection GoCommentStart type UnixStatAdapter interface { Extended() bool // Did this call come from StatX? // Statx properties StatxMask() uint32 // Mask determines the availability of all stat/stax properties (except the device ID!). It is only used in statx though. Attribute() uint64 // Attribute is masked by AttributeMask. AttributeMask() uint64 BTime() time.Time // BTime may not always be available on every filesystem. It's important to check Mask first! // ========== // Base Stat properties NLink() uint64 Owner() uint32 Group() uint32 FileMode() uint32 // Mode may not always be available to check in a Statx call (though it should be, since we requested it.) Best safe than sorry; check Mask! INode() uint64 Device() uint64 RDevice() uint64 // RDevice is ONLY useful when Mode has S_IFCHR or S_IFBLK; as those determine if the file is a representitive of a block or character device. ATime() time.Time MTime() time.Time CTime() time.Time } type UnixStatContainer struct { // Created for downloads statx bool // Does the call contain extended properties (attributes, birthTime)? mask uint32 attributes uint64 numLinks uint64 ownerUID uint32 groupGID uint32 mode uint32 iNode uint64 size uint64 attributesMask uint64 accessTime time.Time // atime birthTime time.Time // btime, statx only changeTime time.Time // ctime modTime time.Time // mtime repDevID uint64 devID uint64 } func (u UnixStatContainer) Extended() bool { return u.statx } func (u UnixStatContainer) StatxMask() uint32 { return u.mask } func (u UnixStatContainer) Attribute() uint64 { return u.attributes } func (u UnixStatContainer) AttributeMask() uint64 { return u.attributesMask } func (u UnixStatContainer) BTime() time.Time { return u.birthTime } func (u UnixStatContainer) NLink() uint64 { return u.numLinks } func (u UnixStatContainer) Owner() uint32 { return u.ownerUID } func (u UnixStatContainer) Group() uint32 { return u.groupGID } func (u UnixStatContainer) FileMode() uint32 { return u.mode } func (u UnixStatContainer) INode() uint64 { return u.iNode } func (u UnixStatContainer) Device() uint64 { return u.devID } func (u UnixStatContainer) RDevice() uint64 { return u.repDevID } func (u UnixStatContainer) ATime() time.Time { return u.accessTime } func (u UnixStatContainer) MTime() time.Time { return u.modTime } func (u UnixStatContainer) CTime() time.Time { return u.changeTime } // ReadStatFromMetadata is not fault-tolerant. If any given article does not parse, // it will throw an error instead of continuing on, as it may be considered incorrect to attempt to persist the rest of the data. // despite this function being used only in Downloads at the current moment, it still attempts to re-create as complete of a UnixStatAdapter as possible. func ReadStatFromMetadata(metadata Metadata, contentLength int64) (UnixStatAdapter, error) { s := UnixStatContainer{size: uint64(contentLength)} if mask, ok := TryReadMetadata(metadata, LINUXStatxMaskMeta); ok { m, err := strconv.ParseUint(*mask, 10, 32) if err != nil { return s, err } s.statx = true s.mask = uint32(m) } // cover additional statx properties here if attr, ok := TryReadMetadata(metadata, LINUXAttributeMeta); ok { a, err := strconv.ParseUint(*attr, 10, 64) if err != nil { return s, err } s.attributes = a } if attr, ok := TryReadMetadata(metadata, LINUXAttributeMaskMeta); ok { a, err := strconv.ParseUint(*attr, 10, 64) if err != nil { return s, err } s.attributesMask = a } if btime, ok := TryReadMetadata(metadata, LINUXBTimeMeta); ok { b, err := strconv.ParseInt(*btime, 10, 64) if err != nil { return s, err } s.birthTime = time.Unix(0, b) } // base stat properties if nlink, ok := TryReadMetadata(metadata, POSIXNlinkMeta); ok { n, err := strconv.ParseUint(*nlink, 10, 64) if err != nil { return s, err } s.numLinks = n } if owner, ok := TryReadMetadata(metadata, POSIXOwnerMeta); ok { o, err := strconv.ParseUint(*owner, 10, 32) if err != nil { return s, err } s.ownerUID = uint32(o) } if group, ok := TryReadMetadata(metadata, POSIXGroupMeta); ok { g, err := strconv.ParseUint(*group, 10, 32) if err != nil { return s, err } s.groupGID = uint32(g) } if mode, ok := TryReadMetadata(metadata, POSIXModeMeta); ok { m, err := strconv.ParseUint(*mode, 10, 32) if err != nil { return s, err } s.mode = uint32(m) } if inode, ok := TryReadMetadata(metadata, POSIXINodeMeta); ok { ino, err := strconv.ParseUint(*inode, 10, 64) if err != nil { return s, err } s.iNode = ino } if dev, ok := TryReadMetadata(metadata, POSIXDevMeta); ok { d, err := strconv.ParseUint(*dev, 10, 64) if err != nil { return s, err } s.devID = d } if rdev, ok := TryReadMetadata(metadata, POSIXRDevMeta); ok { rd, err := strconv.ParseUint(*rdev, 10, 64) if err != nil { return s, err } s.repDevID = rd } if atime, ok := TryReadMetadata(metadata, POSIXATimeMeta); ok { at, err := strconv.ParseInt(*atime, 10, 64) if err != nil { return s, err } s.accessTime = time.Unix(0, at) } if mtime, ok := TryReadMetadata(metadata, POSIXModTimeMeta); ok { mt, err := strconv.ParseInt(*mtime, 10, 64) if err != nil { return s, err } s.modTime = time.Unix(0, mt) } if ctime, ok := TryReadMetadata(metadata, POSIXCTimeMeta); ok { ct, err := strconv.ParseInt(*ctime, 10, 64) if err != nil { return s, err } s.changeTime = time.Unix(0, ct) } return s, nil } const ( // Values cloned from x/sys/unix to avoid dependency STATX_ALL = 0xfff STATX_ATIME = 0x20 STATX_ATTR_APPEND = 0x20 STATX_ATTR_AUTOMOUNT = 0x1000 STATX_ATTR_COMPRESSED = 0x4 STATX_ATTR_DAX = 0x200000 STATX_ATTR_ENCRYPTED = 0x800 STATX_ATTR_IMMUTABLE = 0x10 STATX_ATTR_MOUNT_ROOT = 0x2000 STATX_ATTR_NODUMP = 0x40 STATX_ATTR_VERITY = 0x100000 STATX_BASIC_STATS = 0x7ff STATX_BLOCKS = 0x400 STATX_BTIME = 0x800 STATX_CTIME = 0x80 STATX_GID = 0x10 STATX_INO = 0x100 STATX_MNT_ID = 0x1000 STATX_MODE = 0x2 STATX_MTIME = 0x40 STATX_NLINK = 0x4 STATX_SIZE = 0x200 STATX_TYPE = 0x1 STATX_UID = 0x8 S_IFSOCK = 0xc000 S_IFBLK = 0x6000 S_IFCHR = 0x2000 S_IFDIR = 0x4000 S_IFIFO = 0x1000 S_IFLNK = 0xa000 S_IRUSR = 0x400 S_IWUSR = 0x200 S_IXUSR = 0x100 S_IRGRP = 0x040 S_IWGRP = 0x020 S_IXGRP = 0x010 S_IROTH = 0x004 S_IWOTH = 0x002 S_IXOTH = 0x001 S_ALLPERM = 0x777 ) func ClearStatFromBlobMetadata(metadata Metadata) { for _, v := range AllLinuxProperties { delete(metadata, v) } } func AddStatToBlobMetadata(s UnixStatAdapter, metadata Metadata) { if s == nil { return } applyMode := func(mode os.FileMode) { modes := map[uint32]string{ S_IFCHR: POSIXCharDeviceMeta, S_IFBLK: POSIXBlockDeviceMeta, S_IFSOCK: POSIXSocketMeta, S_IFIFO: POSIXFIFOMeta, S_IFDIR: POSIXFolderMeta, S_IFLNK: POSIXSymlinkMeta, } for modeToTest, metaToApply := range modes { if mode&os.FileMode(modeToTest) == os.FileMode(modeToTest) { TryAddMetadata(metadata, metaToApply, "true") } } } if s.Extended() { // try to poll the other properties mask := s.StatxMask() TryAddMetadata(metadata, LINUXStatxMaskMeta, strconv.FormatUint(uint64(mask), 10)) TryAddMetadata(metadata, LINUXAttributeMeta, strconv.FormatUint(s.Attribute()&s.AttributeMask(), 10)) // AttributesMask indicates what attributes are supported by the filesystem TryAddMetadata(metadata, LINUXAttributeMaskMeta, strconv.FormatUint(s.AttributeMask(), 10)) if StatXReturned(mask, STATX_BTIME) { TryAddMetadata(metadata, LINUXBTimeMeta, strconv.FormatInt(s.BTime().UnixNano(), 10)) } if StatXReturned(mask, STATX_NLINK) { TryAddMetadata(metadata, POSIXNlinkMeta, strconv.FormatUint(s.NLink(), 10)) } if StatXReturned(mask, STATX_UID) { TryAddMetadata(metadata, POSIXOwnerMeta, strconv.FormatUint(uint64(s.Owner()), 10)) } if StatXReturned(mask, STATX_GID) { TryAddMetadata(metadata, POSIXGroupMeta, strconv.FormatUint(uint64(s.Group()), 10)) } if StatXReturned(mask, STATX_MODE) { TryAddMetadata(metadata, POSIXModeMeta, strconv.FormatUint(uint64(s.FileMode()), 10)) applyMode(os.FileMode(s.FileMode())) } if StatXReturned(mask, STATX_INO) { TryAddMetadata(metadata, POSIXINodeMeta, strconv.FormatUint(s.INode(), 10)) } // This is not optional. TryAddMetadata(metadata, POSIXDevMeta, strconv.FormatUint(s.Device(), 10)) if StatXReturned(mask, STATX_MODE) && ((s.FileMode()&S_IFCHR) == S_IFCHR || (s.FileMode()&S_IFBLK) == S_IFBLK) { TryAddMetadata(metadata, POSIXRDevMeta, strconv.FormatUint(s.RDevice(), 10)) } // Sometimes, the filesystem will return ATime, but the vfs layer will overwrite it in the mask. It's still accurate, so we can use it. // e.g. ext4+noatime will still return & properly store atimes, but won't be included in the statx mask. if StatXReturned(mask, STATX_ATIME) || s.ATime().UnixNano() > 0 { TryAddMetadata(metadata, POSIXATimeMeta, strconv.FormatInt(s.ATime().UnixNano(), 10)) } if StatXReturned(mask, STATX_MTIME) { TryAddMetadata(metadata, POSIXModTimeMeta, strconv.FormatInt(s.MTime().UnixNano(), 10)) } if StatXReturned(mask, STATX_CTIME) { TryAddMetadata(metadata, POSIXCTimeMeta, strconv.FormatInt(s.CTime().UnixNano(), 10)) } } else { TryAddMetadata(metadata, POSIXNlinkMeta, strconv.FormatUint(s.NLink(), 10)) TryAddMetadata(metadata, POSIXOwnerMeta, strconv.FormatUint(uint64(s.Owner()), 10)) TryAddMetadata(metadata, POSIXGroupMeta, strconv.FormatUint(uint64(s.Group()), 10)) TryAddMetadata(metadata, POSIXModeMeta, strconv.FormatUint(uint64(s.FileMode()), 10)) applyMode(os.FileMode(s.FileMode())) TryAddMetadata(metadata, POSIXINodeMeta, strconv.FormatUint(s.INode(), 10)) TryAddMetadata(metadata, POSIXDevMeta, strconv.FormatUint(s.Device(), 10)) if (s.FileMode()&S_IFCHR) == S_IFCHR || (s.FileMode()&S_IFBLK) == S_IFBLK { // this is not relevant unless the file is a block or character device. TryAddMetadata(metadata, POSIXRDevMeta, strconv.FormatUint(s.RDevice(), 10)) } TryAddMetadata(metadata, POSIXATimeMeta, strconv.FormatInt(s.ATime().UnixNano(), 10)) TryAddMetadata(metadata, POSIXModTimeMeta, strconv.FormatInt(s.MTime().UnixNano(), 10)) TryAddMetadata(metadata, POSIXCTimeMeta, strconv.FormatInt(s.CTime().UnixNano(), 10)) } } func StatXReturned(mask uint32, want uint32) bool { return (mask & want) == want }