3 import sys, os, bsddb3, struct, getopt, pwd, time, hashlib
6 deadlock = bd.DBLockDeadlockError
7 notfound = bd.DBNotFoundError
10 def __init__(self, env, flags=bd.DB_TXN_WRITE_NOSYNC):
11 self.tx = env.txn_begin(None, flags)
26 def __exit__(self, etype, exc, tb):
31 class dbcursor(object):
32 def __init__(self, db, tx):
33 self.bk = db.cursor(txn=tx.tx)
38 def __exit__(self, *args):
44 def wrapper(self, *args, tx=None, **kwargs):
48 with txn(envfun(self)) as ltx:
49 ret = fun(self, *args, tx=ltx, **kwargs)
55 return fun(self, *args, tx=tx, **kwargs)
62 def __init__(self, root, envdir):
71 if not os.path.isdir(self.envdir):
72 sys.stderr.write("tpkg: creatings %s...\n" % (self.envdir))
73 os.makedirs(self.envdir)
75 env.set_lk_detect(bd.DB_LOCK_RANDOM)
76 fl = bd.DB_THREAD | bd.DB_INIT_MPOOL | bd.DB_INIT_LOCK | bd.DB_INIT_LOG | bd.DB_INIT_TXN | bd.DB_CREATE
78 env.open(self.envdir, fl, mode)
85 env.txn_checkpoint(1024)
86 env.log_archive(bd.DB_ARCH_REMOVE)
88 @txnfun(lambda self: self.env)
89 def db(self, name, *, dup=False, tx):
90 if name not in self.dbs:
93 db.set_flags(bd.DB_DUPSORT)
94 fl = bd.DB_THREAD | bd.DB_CREATE
96 db.open(name, None, bd.DB_BTREE, fl, mode, txn=tx.tx)
101 if self._env is not None:
102 for db in self.dbs.values():
110 @txnfun(lambda self: self.env)
111 def unregfile(self, path, *, tx):
112 epath = path.encode("utf-8")
113 db = self.db("filedata")
114 if db.has_key(epath, txn=tx.tx):
115 db.delete(epath, txn=tx.tx)
116 db = self.db("file-pkg")
117 epkg = db.get(epath, None, txn=tx.tx)
119 db.delete(epath, txn=tx.tx)
120 with dbcursor(self.db("pkg-file", dup=True), tx) as cur:
122 cur.get_both(epkg, epath)
128 @txnfun(lambda self: self.env)
129 def regfile(self, path, pkg, digest, *, tx):
130 epath, epkg = path.encode("utf-8"), pkg.encode("utf-8")
131 self.unregfile(path, tx=tx)
132 filedata = b"digest\0" + digest.encode("utf-8") + b"\0"
133 self.db("filedata").put(epath, filedata, txn=tx.tx)
134 self.db("file-pkg").put(epath, epkg, txn=tx.tx)
135 self.db("pkg-file", dup=True).put(epkg, epath, flags=bd.DB_NODUPDATA, txn=tx.tx)
137 @txnfun(lambda self: self.env)
138 def filedata(self, path, default=KeyError, *, tx):
139 epath = path.encode("utf-8")
140 data = self.db("filedata").get(epath, None, txn=tx.tx)
142 if default is KeyError:
146 data = data.split(b'\0')
147 if data[-1] != b"" or len(data) % 2 != 1:
148 raise Exception("invalid filedata")
150 for i in range(0, len(data) - 1, 2):
151 ret[data[i].decode("utf-8")] = data[i + 1].decode("utf-8")
154 @txnfun(lambda self: self.env)
155 def filepkg(self, path, default=KeyError, *, tx):
156 epath = path.encode("utf-8")
157 epkg = self.db("file-pkg").get(epath, None, txn=tx.tx)
159 if default is KeyError:
163 return epkg.decode("utf-8")
165 @txnfun(lambda self: self.env)
166 def pkgfiles(self, pkg, default=KeyError, *, tx):
167 epkg = pkg.encode("utf-8")
168 with dbcursor(self.db("pkg-file", dup=True), tx) as cur:
176 if default is KeyError:
182 ret.append(epath.decode("utf-8"))
191 home = pwd.getpwuid(os.getuid()).pw_dir
192 return cls(pj(home, "sys"), pj(home, ".tpkg/db"))
196 home = pwd.getpwuid(os.getuid()).pw_dir
197 return cls(pj(home, "tpkgtest"), pj(home, ".tpkg/testdb"))
201 return cls("/usr/local", "/usr/local/etc/tpkg/db")
203 class vfsfile(object):
204 def __init__(self, path, fullpath):
206 self.fullpath = fullpath
209 return open(self.fullpath, "rb")
212 return os.stat(self.fullpath)
214 class vfspkg(object):
215 def __init__(self, root):
220 dpre = "" if (lp is "") else lp + "/"
221 for dent in os.scandir(fp):
222 dpath = dpre + dent.name
224 yield from scan(dpath, dent.path)
226 yield vfsfile(dpath, dent.path)
227 return scan("", self.root)
230 dig = hashlib.sha256()
232 buf = src.read(65536)
234 return dig.hexdigest().lower()
239 dig = hashlib.sha256()
243 return dig.hexdigest().lower()
246 def install(pfx, pkg, pkgname):
248 if os.path.exists(pj(pfx.root, fl.path)):
249 sys.stderr.write("tpkg: %s: already exists\n" % (fl.path))
252 tp = pj(pfx.root, fl.path)
253 tpdir = os.path.dirname(tp)
254 if not os.path.isdir(tpdir):
256 tmpp = tp + ".tpkg-new"
258 with open(tmpp, "wb") as ofp:
259 os.fchmod(ofp.fileno(), sb.st_mode & 0o7777)
260 with fl.open() as ifp:
262 pfx.regfile(fl.path, pkgname, dig)
264 os.utime(tp, ns=(time.time(), sb.st_mtime))
266 def uninstall(pfx, pkg):
267 for fn in pfx.pkgfiles(pkg):
268 fpath = pj(pfx.root, fn)
269 if not os.path.exists(fpath):
270 sys.stderr.write("tpkg: warning: %s does not exist\n" % (fn))
272 fdat = pfx.filedata(fn)
273 with open(fpath, "rb") as fp:
274 if digest(fp) != fdat.get("digest", ""):
275 sys.stderr.write("tpkg: %s does not match registered hash\n" % (fn))
277 for fn in pfx.pkgfiles(pkg):
278 fpath = pj(pfx.root, fn)
281 except FileNotFoundError:
287 def cmd_install(argv):
289 out.write("usage: tpkg install [-n NAME] SOURCEDIR\n")
290 opts, args = getopt.getopt(argv, "n:")
299 if not os.path.isdir(srcpath):
300 sys.stderr.write("tpkg: %s: not a directory\n" % (srcpath))
303 pkgname = os.path.basename(os.path.realpath(srcpath))
305 sys.stderr.write("tpkg: could not determine package name\n")
307 install(prefix.use, vfspkg(srcpath), pkgname)
308 cmds["install"] = cmd_install
310 def cmd_uninstall(argv):
312 out.write("usage: tpkg uninstall NAME\n")
313 opts, args = getopt.getopt(argv, "")
318 uninstall(prefix.use, pkgname)
319 cmds["uninstall"] = cmd_uninstall
323 out.write("usage: tpkg list NAME\n")
324 opts, args = getopt.getopt(argv, "")
330 files = prefix.use.pkgfiles(pkgname)
332 sys.stderr.write("tpkg: %s: no such package\n" % (pkgname))
335 sys.stdout.write("%s\n" % pj(prefix.use.root, fn))
336 cmds["list"] = cmd_list
339 file.write("usage:\ttpkg help\n")
340 cmds["help"] = lambda argv: usage(sys.stdout)
344 opts, args = getopt.getopt(argv, "hp:")
357 sys.stderr.write("tpkg: %s: undefined prefix\n" % (a))
360 sys.stderr.write("tpkg: no prefix specified\n")
364 if len(args) > 0 and args[0] in cmds:
365 cmds[args[0]](args[1:])
374 if __name__ == "__main__":