Skip to content

Instantly share code, notes, and snippets.

@simon816
Last active August 30, 2019 15:47
Show Gist options
  • Save simon816/5cbcb96d1a0cf7c0db5a4db9e7d81758 to your computer and use it in GitHub Desktop.
Save simon816/5cbcb96d1a0cf7c0db5a4db9e7d81758 to your computer and use it in GitHub Desktop.
Read GVFS Metadata files (~/.local/share/gvfs-metadata)
import struct
from collections import namedtuple
import datetime
# See https://github.com/GNOME/gvfs/blob/master/metadata/metatree.c
MAGIC = b'\xda\x1ameta'
MAJOR = 1
MINOR = 0
class StructFactory:
def __init__(self, cls, spec):
self.__cls = cls
self.__struct = struct.Struct(spec)
@property
def size(self):
return self.__struct.size
def read(self, buf, off):
return self.__cls._make(self.__struct.unpack_from(buf, off))
def StructTuple(name, spec):
names, types = zip(*spec)
structspec = '>' + ''.join(types)
return StructFactory(namedtuple(name, names), structspec)
KEY_IS_LIST_MASK = (1<<31)
MetaFileHeader = StructTuple('MetaFileHeader', (
('magic', '6s'),
('major', 'B'),
('minor', 'B'),
('rotated', 'I'),
('random_tag', 'I'),
('root', 'I'),
('attributes', 'I'),
('time_t_base', 'Q')
))
MetaFileDirEnt = StructTuple('MetaFileDirEnt', (
('name', 'I'),
('children', 'I'),
('metadata', 'I'),
('last_changed', 'I')
))
MetaFileDir = StructTuple('MetaFileDir', (
('num_children', 'I'),
))
MetaFileDataEnt = StructTuple('MetaFileDataEnt', (
('key', 'I'),
('value', 'I')
))
MetaFileData = StructTuple('MetaFileData', (
('num_keys', 'I'),
))
MetaFileStringv = StructTuple('MetaFileStringv', (
('num_strings', 'I'),
))
class GVFSReader:
def __init__(self, filename):
with open(filename, 'rb') as f:
self.data = f.read()
self.attributes = []
self.parse()
def deref(self, ptr, type):
return struct.unpack_from('>' + type, self.data, ptr)[0]
def read_str(self, pos):
start = pos
while self.data[pos] != 0:
pos += 1
return self.data[start : pos]
def parse(self):
header = MetaFileHeader.read(self.data, 0)
assert header.magic == MAGIC
assert header.major == MAJOR
assert header.minor == MINOR
self.time_base = header.time_t_base
print(header)
num_attrs = self.deref(header.attributes, 'I')
attributes = header.attributes + 4
for i in range(num_attrs):
ptr = self.deref(attributes + (i * 4), 'I')
attr = self.read_str(ptr)
self.attributes.append(attr)
root = MetaFileDirEnt.read(self.data, header.root)
self.read_dir_entry(root)
def get_time(self, tm):
if tm == 0:
return 0
return tm + self.time_base
def str_time(self, tm):
return datetime.datetime.utcfromtimestamp(self.get_time(tm))
def read_dir_entry(self, dirent):
name = self.read_str(dirent.name)
print(name, "Last Changed:", self.str_time(dirent.last_changed))
if dirent.metadata != 0:
meta = MetaFileData.read(self.data, dirent.metadata)
off = dirent.metadata + MetaFileData.size
for i in range(meta.num_keys):
metaent = MetaFileDataEnt.read(self.data, off)
off += MetaFileDataEnt.size
key_id = metaent.key & ~KEY_IS_LIST_MASK
if metaent.key & KEY_IS_LIST_MASK != 0:
strv = MetaFileStringv.read(self.data, metaent.value)
self.ptr = metaent.value + MetaFileStringv.size
value = []
for n in range(strv.num_strings):
ptr = self.deref(self.ptr + (n * 4), 'I')
value.append(self.read_str(ptr))
else:
value = self.read_str(metaent.value)
attr = self.attributes[key_id]
print(attr, value)
if dirent.children == 0:
return
dir = MetaFileDir.read(self.data, dirent.children)
off = dirent.children + MetaFileDir.size
for i in range(dir.num_children):
child = MetaFileDirEnt.read(self.data, off)
off += MetaFileDirEnt.size
self.read_dir_entry(child)
if __name__ == '__main__':
import sys
reader = GVFSReader(sys.argv[1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment