--- /dev/null
+#!/usr/bin/python3
+
+import sys, os, bsddb3, struct, getopt, pwd, time, hashlib
+bd = bsddb3.db
+pj = os.path.join
+deadlock = bd.DBLockDeadlockError
+notfound = bd.DBNotFoundError
+
+class txn(object):
+ def __init__(self, env, flags=bd.DB_TXN_WRITE_NOSYNC):
+ self.tx = env.txn_begin(None, flags)
+ self.env = env
+ self.done = False
+
+ def commit(self):
+ self.done = True
+ self.tx.commit(0)
+
+ def abort(self):
+ self.done = True
+ self.tx.abort()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, etype, exc, tb):
+ if not self.done:
+ self.abort()
+ return False
+
+class dbcursor(object):
+ def __init__(self, db, tx):
+ self.bk = db.cursor(txn=tx.tx)
+
+ def __enter__(self):
+ return self.bk
+
+ def __exit__(self, *args):
+ self.bk.close()
+ return False
+
+def txnfun(envfun):
+ def fxf(fun):
+ def wrapper(self, *args, tx=None, **kwargs):
+ if tx is None:
+ while True:
+ try:
+ with txn(envfun(self)) as ltx:
+ ret = fun(self, *args, tx=ltx, **kwargs)
+ ltx.commit()
+ return ret
+ except deadlock:
+ continue
+ else:
+ return fun(self, *args, tx=tx, **kwargs)
+ return wrapper
+ return fxf
+
+class prefix(object):
+ use = None
+
+ def __init__(self, root, envdir):
+ self.root = root
+ self.envdir = envdir
+ self._env = None
+ self.dbs = {}
+
+ @property
+ def env(self):
+ if self._env is None:
+ if not os.path.isdir(self.envdir):
+ sys.stderr.write("tpkg: creatings %s...\n" % (self.envdir))
+ os.makedirs(self.envdir)
+ env = bd.DBEnv()
+ env.set_lk_detect(bd.DB_LOCK_RANDOM)
+ fl = bd.DB_THREAD | bd.DB_INIT_MPOOL | bd.DB_INIT_LOCK | bd.DB_INIT_LOG | bd.DB_INIT_TXN | bd.DB_CREATE
+ mode = 0o666
+ env.open(self.envdir, fl, mode)
+ self._env = env
+ return self._env
+
+ def maint(self):
+ env = self._env
+ if env is not None:
+ env.txn_checkpoint(1024)
+ env.log_archive(bd.DB_ARCH_REMOVE)
+
+ @txnfun(lambda self: self.env)
+ def db(self, name, *, dup=False, tx):
+ if name not in self.dbs:
+ db = bd.DB(self.env)
+ if dup:
+ db.set_flags(bd.DB_DUPSORT)
+ fl = bd.DB_THREAD | bd.DB_CREATE
+ mode = 0o666
+ db.open(name, None, bd.DB_BTREE, fl, mode, txn=tx.tx)
+ self.dbs[name] = db
+ return self.dbs[name]
+
+ def close(self):
+ if self._env is not None:
+ for db in self.dbs.values():
+ db.close()
+ self._env.close()
+ self._env = None
+
+ def __del__(self):
+ self.close()
+
+ @txnfun(lambda self: self.env)
+ def unregfile(self, path, *, tx):
+ epath = path.encode("utf-8")
+ db = self.db("filedata")
+ if db.has_key(epath, txn=tx.tx):
+ db.delete(epath, txn=tx.tx)
+ db = self.db("file-pkg")
+ epkg = db.get(epath, None, txn=tx.tx)
+ if epkg is not None:
+ db.delete(epath, txn=tx.tx)
+ with dbcursor(self.db("pkg-file", dup=True), tx) as cur:
+ try:
+ cur.get_both(epkg, epath)
+ except notfound:
+ pass
+ else:
+ cur.delete()
+
+ @txnfun(lambda self: self.env)
+ def regfile(self, path, pkg, digest, *, tx):
+ epath, epkg = path.encode("utf-8"), pkg.encode("utf-8")
+ self.unregfile(path, tx=tx)
+ filedata = b"digest\0" + digest.encode("utf-8") + b"\0"
+ self.db("filedata").put(epath, filedata, txn=tx.tx)
+ self.db("file-pkg").put(epath, epkg, txn=tx.tx)
+ self.db("pkg-file", dup=True).put(epkg, epath, flags=bd.DB_NODUPDATA, txn=tx.tx)
+
+ @txnfun(lambda self: self.env)
+ def filedata(self, path, default=KeyError, *, tx):
+ epath = path.encode("utf-8")
+ data = self.db("filedata").get(epath, None, txn=tx.tx)
+ if data is None:
+ if default is KeyError:
+ raise KeyError(path)
+ else:
+ return default
+ data = data.split(b'\0')
+ if data[-1] != b"" or len(data) % 2 != 1:
+ raise Exception("invalid filedata")
+ ret = {}
+ for i in range(0, len(data) - 1, 2):
+ ret[data[i].decode("utf-8")] = data[i + 1].decode("utf-8")
+ return ret
+
+ @txnfun(lambda self: self.env)
+ def filepkg(self, path, default=KeyError, *, tx):
+ epath = path.encode("utf-8")
+ epkg = self.db("file-pkg").get(epath, None, txn=tx.tx)
+ if epkg is None:
+ if default is KeyError:
+ raise KeyError(path)
+ else:
+ return default
+ return epkg.decode("utf-8")
+
+ @txnfun(lambda self: self.env)
+ def pkgfiles(self, pkg, default=KeyError, *, tx):
+ epkg = pkg.encode("utf-8")
+ with dbcursor(self.db("pkg-file", dup=True), tx) as cur:
+ try:
+ edat = cur.set(epkg)
+ if edat is None:
+ raise notfound()
+ fpkg, epath = edat
+ assert fpkg == epkg
+ except notfound:
+ if default is KeyError:
+ raise KeyError(pkg)
+ else:
+ return default
+ ret = []
+ while fpkg == epkg:
+ ret.append(epath.decode("utf-8"))
+ edat = cur.next()
+ if edat is None:
+ break
+ fpkg, epath = edat
+ return ret
+
+ @classmethod
+ def home(cls):
+ home = pwd.getpwuid(os.getuid()).pw_dir
+ return cls(pj(home, "sys"), pj(home, ".tpkg/db"))
+
+ @classmethod
+ def test(cls):
+ home = pwd.getpwuid(os.getuid()).pw_dir
+ return cls(pj(home, "tpkgtest"), pj(home, ".tpkg/testdb"))
+
+ @classmethod
+ def local(cls):
+ return cls("/usr/local", "/usr/local/etc/tpkg/db")
+
+class vfsfile(object):
+ def __init__(self, path, fullpath):
+ self.path = path
+ self.fullpath = fullpath
+
+ def open(self):
+ return open(self.fullpath, "rb")
+
+ def stat(self):
+ return os.stat(self.fullpath)
+
+class vfspkg(object):
+ def __init__(self, root):
+ self.root = root
+
+ def __iter__(self):
+ def scan(lp, fp):
+ dpre = "" if (lp is "") else lp + "/"
+ for dent in os.scandir(fp):
+ dpath = dpre + dent.name
+ if dent.is_dir():
+ yield from scan(dpath, dent.path)
+ else:
+ yield vfsfile(dpath, dent.path)
+ return scan("", self.root)
+
+def copy(dst, src):
+ dig = hashlib.sha256()
+ while True:
+ buf = src.read(65536)
+ if buf == b"":
+ return dig.hexdigest().lower()
+ dst.write(buf)
+ dig.update(buf)
+
+def digest(fp):
+ dig = hashlib.sha256()
+ while True:
+ buf = fp.read(65536)
+ if buf == b"":
+ return dig.hexdigest().lower()
+ dig.update(buf)
+
+def install(pfx, pkg, pkgname):
+ for fl in pkg:
+ if os.path.exists(pj(pfx.root, fl.path)):
+ sys.stderr.write("tpkg: %s: already exists\n" % (fl.path))
+ sys.exit(1)
+ for fl in pkg:
+ tp = pj(pfx.root, fl.path)
+ tpdir = os.path.dirname(tp)
+ if not os.path.isdir(tpdir):
+ os.makedirs(tpdir)
+ tmpp = tp + ".tpkg-new"
+ sb = fl.stat()
+ with open(tmpp, "wb") as ofp:
+ os.fchmod(ofp.fileno(), sb.st_mode & 0o7777)
+ with fl.open() as ifp:
+ dig = copy(ofp, ifp)
+ pfx.regfile(fl.path, pkgname, dig)
+ os.rename(tmpp, tp)
+ os.utime(tp, ns=(time.time(), sb.st_mtime))
+
+def uninstall(pfx, pkg):
+ for fn in pfx.pkgfiles(pkg):
+ fpath = pj(pfx.root, fn)
+ if not os.path.exists(fpath):
+ sys.stderr.write("tpkg: warning: %s does not exist\n" % (fn))
+ else:
+ fdat = pfx.filedata(fn)
+ with open(fpath, "rb") as fp:
+ if digest(fp) != fdat.get("digest", ""):
+ sys.stderr.write("tpkg: %s does not match registered hash\n" % (fn))
+ sys.exit(1)
+ for fn in pfx.pkgfiles(pkg):
+ fpath = pj(pfx.root, fn)
+ try:
+ os.unlink(fpath)
+ except FileNotFoundError:
+ pass
+ pfx.unregfile(fn)
+
+cmds = {}
+
+def cmd_install(argv):
+ def usage(out):
+ out.write("usage: tpkg install [-n NAME] SOURCEDIR\n")
+ opts, args = getopt.getopt(argv, "n:")
+ pkgname = None
+ for o, a in opts:
+ if o == "-n":
+ pkgname = a
+ if len(args) < 1:
+ usage(sys.stderr)
+ sys.exit(1)
+ srcpath = args[0]
+ if not os.path.isdir(srcpath):
+ sys.stderr.write("tpkg: %s: not a directory\n" % (srcpath))
+ sys.exit(1)
+ if pkgname is None:
+ pkgname = os.path.basename(os.path.realpath(srcpath))
+ if not pkgname:
+ sys.stderr.write("tpkg: could not determine package name\n")
+ sys.exit(1)
+ install(prefix.use, vfspkg(srcpath), pkgname)
+cmds["install"] = cmd_install
+
+def cmd_uninstall(argv):
+ def usage(out):
+ out.write("usage: tpkg uninstall NAME\n")
+ opts, args = getopt.getopt(argv, "")
+ if len(args) < 1:
+ usage(sys.stderr)
+ sys.exit(1)
+ pkgname = args[0]
+ uninstall(prefix.use, pkgname)
+cmds["uninstall"] = cmd_uninstall
+
+def usage(file):
+ file.write("usage:\ttpkg help\n")
+cmds["help"] = lambda argv: usage(sys.stdout)
+
+def main(argv):
+ pfx = None
+ opts, args = getopt.getopt(argv, "hp:")
+ for o, a in opts:
+ if o == "-h":
+ usage(sys.stdout)
+ sys.exit(0)
+ elif o == "-p":
+ if a == "sys":
+ pfx = prefix.home()
+ elif a == "local":
+ pfx = prefix.local()
+ elif a == "test":
+ pfx = prefix.test()
+ else:
+ sys.stderr.write("tpkg: %s: undefined prefix\n" % (a))
+ sys.exit(1)
+ if pfx is None:
+ sys.stderr.write("tpkg: no prefix specified\n")
+ sys.exit(1)
+ prefix.use = pfx
+ try:
+ if len(args) > 0 and args[0] in cmds:
+ cmds[args[0]](args[1:])
+ pfx.maint()
+ sys.exit(0)
+ else:
+ usage(sys.stderr)
+ sys.exit(1)
+ finally:
+ pfx.close()
+
+if __name__ == "__main__":
+ main(sys.argv[1:])