add options to set default chmod (#181)

the unix-permissions of new files/folders can now be changed

* global-option --chmod-f, volflag chmod_f for files
* global-option --chmod-d, volflag chmod_d for directories

the expected value is a standard three-digit octal value
(User/Group/Other) such as 755, 750, 644, 640, etc
This commit is contained in:
ed 2025-07-21 22:46:28 +00:00
parent 14fa369fae
commit 9921c43e3a
15 changed files with 180 additions and 43 deletions

View file

@ -863,6 +863,43 @@ def get_sects():
""" """
), ),
], ],
[
"chmod",
"file/folder permissions",
dedent(
"""
global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
the value is a three-digit octal number such as 755, 750, 644, etc.
first digit = "User"; permission for the unix-user
second digit = "Group"; permission for the unix-group
third digit = "Other"; permission for all other users/groups
for files:
0 = --- = no access
1 = --x = can execute the file as a program
2 = -w- = can write
3 = -wx = can write and execute
4 = r-- = can read
5 = r-x = can read and execute
6 = rw- = can read and write
7 = rwx = can read, write, execute
for directories/folders:
0 = --- = no access
1 = --x = can read files in folder but not list contents
2 = -w- = n/a
3 = -wx = can create files but not list
4 = r-- = can list, but not read/write
5 = r-x = can list and read files
6 = rw- = n/a
7 = rwx = can read, write, list
"""
),
],
[ [
"pwhash", "pwhash",
"password hashing", "password hashing",
@ -1013,6 +1050,8 @@ def add_upload(ap):
ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600") ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)") ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)") ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)") ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)") ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)") ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")

View file

@ -120,6 +120,8 @@ class Lim(object):
self.reg: Optional[dict[str, dict[str, Any]]] = None # up2k registry self.reg: Optional[dict[str, dict[str, Any]]] = None # up2k registry
self.chmod_d = 0o755
self.nups: dict[str, list[float]] = {} # num tracker self.nups: dict[str, list[float]] = {} # num tracker
self.bups: dict[str, list[tuple[float, int]]] = {} # byte tracker list self.bups: dict[str, list[tuple[float, int]]] = {} # byte tracker list
self.bupc: dict[str, int] = {} # byte tracker cache self.bupc: dict[str, int] = {} # byte tracker cache
@ -280,7 +282,7 @@ class Lim(object):
if not dirs: if not dirs:
# no branches yet; make one # no branches yet; make one
sub = os.path.join(path, "0") sub = os.path.join(path, "0")
bos.mkdir(sub) bos.mkdir(sub, self.chmod_d)
else: else:
# try newest branch only # try newest branch only
sub = os.path.join(path, str(dirs[-1])) sub = os.path.join(path, str(dirs[-1]))
@ -295,7 +297,7 @@ class Lim(object):
# make a branch # make a branch
sub = os.path.join(path, str(dirs[-1] + 1)) sub = os.path.join(path, str(dirs[-1] + 1))
bos.mkdir(sub) bos.mkdir(sub, self.chmod_d)
ret = self.dive(sub, lvs - 1) ret = self.dive(sub, lvs - 1)
if ret is None: if ret is None:
raise Pebkac(500, "rotation bug") raise Pebkac(500, "rotation bug")
@ -2074,6 +2076,7 @@ class AuthSrv(object):
all_mte = {} all_mte = {}
errors = False errors = False
free_umask = False
for vol in vfs.all_nodes.values(): for vol in vfs.all_nodes.values():
if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa: if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
vol.flags["e2ds"] = True vol.flags["e2ds"] = True
@ -2130,6 +2133,27 @@ class AuthSrv(object):
t = 'volume "/%s" has invalid %stry [%s]' t = 'volume "/%s" has invalid %stry [%s]'
raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try"))) raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
for k in ("chmod_d", "chmod_f"):
is_d = k == "chmod_d"
zs = vol.flags.get(k, "")
if not zs and is_d:
zs = "755"
if not zs:
vol.flags.pop(k, None)
continue
if not re.match("^[0-7]{3}$", zs):
t = "config-option '%s' must be a three-digit octal value such as [755] or [644] but the value was [%s]"
t = t % (k, zs)
self.log(t, 1)
raise Exception(t)
zi = int(zs, 8)
vol.flags[k] = zi
if (is_d and zi != 0o755) or not is_d:
free_umask = True
if vol.lim:
vol.lim.chmod_d = vol.flags["chmod_d"]
if vol.flags.get("og"): if vol.flags.get("og"):
self.args.uqe = True self.args.uqe = True
@ -2359,6 +2383,10 @@ class AuthSrv(object):
if errors: if errors:
sys.exit(1) sys.exit(1)
setattr(self.args, "free_umask", free_umask)
if free_umask:
os.umask(0)
vfs.bubble_flags() vfs.bubble_flags()
have_e2d = False have_e2d = False

View file

@ -25,14 +25,21 @@ def listdir(p: str = ".") -> list[str]:
def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> bool: def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> bool:
# os.makedirs does 777 for all but leaf; this does mode on all
todo = []
bname = fsenc(name) bname = fsenc(name)
try: while bname:
os.makedirs(bname, mode) if os.path.isdir(bname):
return True break
except: todo.append(bname)
if not exist_ok or not os.path.isdir(bname): bname = os.path.dirname(bname)
raise if not todo:
if not exist_ok:
os.mkdir(bname) # to throw
return False return False
for zb in todo[::-1]:
os.mkdir(zb, mode)
return True
def mkdir(p: str, mode: int = 0o755) -> None: def mkdir(p: str, mode: int = 0o755) -> None:

View file

@ -78,6 +78,8 @@ def vf_vmap() -> dict[str, str]:
} }
for k in ( for k in (
"bup_ck", "bup_ck",
"chmod_d",
"chmod_f",
"dbd", "dbd",
"forget_ip", "forget_ip",
"hsortn", "hsortn",
@ -169,6 +171,8 @@ flagcats = {
"safededup": "verify on-disk data before using it for dedup", "safededup": "verify on-disk data before using it for dedup",
"noclone": "take dupe data from clients, even if available on HDD", "noclone": "take dupe data from clients, even if available on HDD",
"nodupe": "rejects existing files (instead of linking/cloning them)", "nodupe": "rejects existing files (instead of linking/cloning them)",
"chmod_d=755": "unix-permission for new dirs/folders",
"chmod_f=644": "unix-permission for new files",
"sparse": "force use of sparse files, mainly for s3-backed storage", "sparse": "force use of sparse files, mainly for s3-backed storage",
"nosparse": "deny use of sparse files, mainly for slow storage", "nosparse": "deny use of sparse files, mainly for slow storage",
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files", "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",

View file

@ -229,7 +229,7 @@ class FtpFs(AbstractedFS):
r = "r" in mode r = "r" in mode
w = "w" in mode or "a" in mode or "+" in mode w = "w" in mode or "a" in mode or "+" in mode
ap = self.rv2a(filename, r, w)[0] ap, vfs, _ = self.rv2a(filename, r, w)
self.validpath(ap) self.validpath(ap)
if w: if w:
try: try:
@ -261,7 +261,11 @@ class FtpFs(AbstractedFS):
wunlink(self.log, ap, VF_CAREFUL) wunlink(self.log, ap, VF_CAREFUL)
return open(fsenc(ap), mode, self.args.iobuf) ret = open(fsenc(ap), mode, self.args.iobuf)
if w and "chmod_f" in vfs.flags:
os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
return ret
def chdir(self, path: str) -> None: def chdir(self, path: str) -> None:
nwd = join(self.cwd, path) nwd = join(self.cwd, path)
@ -292,8 +296,9 @@ class FtpFs(AbstractedFS):
) = avfs.can_access("", self.h.uname) ) = avfs.can_access("", self.h.uname)
def mkdir(self, path: str) -> None: def mkdir(self, path: str) -> None:
ap = self.rv2a(path, w=True)[0] ap, vfs, _ = self.rv2a(path, w=True)
bos.makedirs(ap) # filezilla expects this chmod = vfs.flags["chmod_d"]
bos.makedirs(ap, chmod) # filezilla expects this
def listdir(self, path: str) -> list[str]: def listdir(self, path: str) -> list[str]:
vpath = join(self.cwd, path) vpath = join(self.cwd, path)

View file

@ -2068,7 +2068,7 @@ class HttpCli(object):
fdir, fn = os.path.split(fdir) fdir, fn = os.path.split(fdir)
rem, _ = vsplit(rem) rem, _ = vsplit(rem)
bos.makedirs(fdir) bos.makedirs(fdir, vfs.flags["chmod_d"])
open_ka: dict[str, Any] = {"fun": open} open_ka: dict[str, Any] = {"fun": open}
open_a = ["wb", self.args.iobuf] open_a = ["wb", self.args.iobuf]
@ -2127,6 +2127,8 @@ class HttpCli(object):
fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip()) fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
params = {"suffix": suffix, "fdir": fdir} params = {"suffix": suffix, "fdir": fdir}
if "chmod_f" in vfs.flags:
params["chmod"] = vfs.flags["chmod_f"]
if self.args.nw: if self.args.nw:
params = {} params = {}
fn = os.devnull fn = os.devnull
@ -2175,7 +2177,7 @@ class HttpCli(object):
if self.args.nw: if self.args.nw:
fn = os.devnull fn = os.devnull
else: else:
bos.makedirs(fdir) bos.makedirs(fdir, vfs.flags["chmod_d"])
path = os.path.join(fdir, fn) path = os.path.join(fdir, fn)
if not nameless: if not nameless:
self.vpath = vjoin(self.vpath, fn) self.vpath = vjoin(self.vpath, fn)
@ -2307,7 +2309,7 @@ class HttpCli(object):
if self.args.hook_v: if self.args.hook_v:
log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem) log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
fdir, self.vpath, fn, (vfs, rem) = x fdir, self.vpath, fn, (vfs, rem) = x
bos.makedirs(fdir) bos.makedirs(fdir, vfs.flags["chmod_d"])
path2 = os.path.join(fdir, fn) path2 = os.path.join(fdir, fn)
atomic_move(self.log, path, path2, vfs.flags) atomic_move(self.log, path, path2, vfs.flags)
path = path2 path = path2
@ -2593,7 +2595,7 @@ class HttpCli(object):
dst = vfs.canonical(rem) dst = vfs.canonical(rem)
try: try:
if not bos.path.isdir(dst): if not bos.path.isdir(dst):
bos.makedirs(dst) bos.makedirs(dst, vfs.flags["chmod_d"])
except OSError as ex: except OSError as ex:
self.log("makedirs failed %r" % (dst,)) self.log("makedirs failed %r" % (dst,))
if not bos.path.isdir(dst): if not bos.path.isdir(dst):
@ -3028,7 +3030,7 @@ class HttpCli(object):
raise Pebkac(405, 'folder "/%s" already exists' % (vpath,)) raise Pebkac(405, 'folder "/%s" already exists' % (vpath,))
try: try:
bos.makedirs(fn) bos.makedirs(fn, vfs.flags["chmod_d"])
except OSError as ex: except OSError as ex:
if ex.errno == errno.EACCES: if ex.errno == errno.EACCES:
raise Pebkac(500, "the server OS denied write-access") raise Pebkac(500, "the server OS denied write-access")
@ -3070,6 +3072,8 @@ class HttpCli(object):
with open(fsenc(fn), "wb") as f: with open(fsenc(fn), "wb") as f:
f.write(b"`GRUNNUR`\n") f.write(b"`GRUNNUR`\n")
if "chmod_f" in vfs.flags:
os.fchmod(f.fileno(), vfs.flags["chmod_f"])
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
self.redirect(vpath, "?edit") self.redirect(vpath, "?edit")
@ -3143,7 +3147,7 @@ class HttpCli(object):
) )
upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/") upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
if not nullwrite: if not nullwrite:
bos.makedirs(fdir_base) bos.makedirs(fdir_base, vfs.flags["chmod_d"])
rnd, lifetime, xbu, xau = self.upload_flags(vfs) rnd, lifetime, xbu, xau = self.upload_flags(vfs)
zs = self.uparam.get("want") or self.headers.get("accept") or "" zs = self.uparam.get("want") or self.headers.get("accept") or ""
@ -3238,8 +3242,11 @@ class HttpCli(object):
else: else:
open_args["fdir"] = fdir open_args["fdir"] = fdir
if "chmod_f" in vfs.flags:
open_args["chmod"] = vfs.flags["chmod_f"]
if p_file and not nullwrite: if p_file and not nullwrite:
bos.makedirs(fdir) bos.makedirs(fdir, vfs.flags["chmod_d"])
# reserve destination filename # reserve destination filename
f, fname = ren_open(fname, "wb", fdir=fdir, suffix=suffix) f, fname = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
@ -3343,7 +3350,7 @@ class HttpCli(object):
if nullwrite: if nullwrite:
fdir = ap2 = "" fdir = ap2 = ""
else: else:
bos.makedirs(fdir) bos.makedirs(fdir, vfs.flags["chmod_d"])
atomic_move(self.log, abspath, ap2, vfs.flags) atomic_move(self.log, abspath, ap2, vfs.flags)
abspath = ap2 abspath = ap2
sz = bos.path.getsize(abspath) sz = bos.path.getsize(abspath)
@ -3464,6 +3471,8 @@ class HttpCli(object):
ft = "{}:{}".format(self.ip, self.addr[1]) ft = "{}:{}".format(self.ip, self.addr[1])
ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg) ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
f.write(ft.encode("utf-8")) f.write(ft.encode("utf-8"))
if "chmod_f" in vfs.flags:
os.fchmod(f.fileno(), vfs.flags["chmod_f"])
except Exception as ex: except Exception as ex:
suf = "\nfailed to write the upload report: {}".format(ex) suf = "\nfailed to write the upload report: {}".format(ex)
@ -3514,7 +3523,7 @@ class HttpCli(object):
lim = vfs.get_dbv(rem)[0].lim lim = vfs.get_dbv(rem)[0].lim
if lim: if lim:
fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker) fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker)
bos.makedirs(fp) bos.makedirs(fp, vfs.flags["chmod_d"])
fp = os.path.join(fp, fn) fp = os.path.join(fp, fn)
rem = "{}/{}".format(rp, fn).strip("/") rem = "{}/{}".format(rp, fn).strip("/")
@ -3582,13 +3591,15 @@ class HttpCli(object):
zs = ub64enc(zb).decode("ascii")[:24].lower() zs = ub64enc(zb).decode("ascii")[:24].lower()
dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs) dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
self.log("moving old version to %s/%s" % (dp, mfile2)) self.log("moving old version to %s/%s" % (dp, mfile2))
if bos.makedirs(dp): if bos.makedirs(dp, vfs.flags["chmod_d"]):
with open(os.path.join(dp, "dir.txt"), "wb") as f: with open(os.path.join(dp, "dir.txt"), "wb") as f:
f.write(afsenc(vrd)) f.write(afsenc(vrd))
if "chmod_f" in vfs.flags:
os.fchmod(f.fileno(), vfs.flags["chmod_f"])
elif hist_cfg == "s": elif hist_cfg == "s":
dp = os.path.join(mdir, ".hist") dp = os.path.join(mdir, ".hist")
try: try:
bos.mkdir(dp) bos.mkdir(dp, vfs.flags["chmod_d"])
hidedir(dp) hidedir(dp)
except: except:
pass pass
@ -3627,6 +3638,8 @@ class HttpCli(object):
wunlink(self.log, fp, vfs.flags) wunlink(self.log, fp, vfs.flags)
with open(fsenc(fp), "wb", self.args.iobuf) as f: with open(fsenc(fp), "wb", self.args.iobuf) as f:
if "chmod_f" in vfs.flags:
os.fchmod(f.fileno(), vfs.flags["chmod_f"])
sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp) sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp)
if lim: if lim:

View file

@ -320,7 +320,7 @@ class SMB(object):
self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2) self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
try: try:
bos.makedirs(ap2) bos.makedirs(ap2, vfs2.flags["chmod_d"])
except: except:
pass pass
@ -334,7 +334,7 @@ class SMB(object):
t = "blocked mkdir (no-write-acc %s): /%s @%s" t = "blocked mkdir (no-write-acc %s): /%s @%s"
yeet(t % (vfs.axs.uwrite, vpath, uname)) yeet(t % (vfs.axs.uwrite, vpath, uname))
return bos.mkdir(ap) return bos.mkdir(ap, vfs.flags["chmod_d"])
def _stat(self, vpath: str, *a: Any, **ka: Any) -> os.stat_result: def _stat(self, vpath: str, *a: Any, **ka: Any) -> os.stat_result:
try: try:

View file

@ -27,6 +27,7 @@ if True: # pylint: disable=using-constant-test
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
from .authsrv import BAD_CFG, AuthSrv from .authsrv import BAD_CFG, AuthSrv
from .bos import bos
from .cert import ensure_cert from .cert import ensure_cert
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
from .pwhash import HAVE_ARGON2 from .pwhash import HAVE_ARGON2
@ -1118,7 +1119,7 @@ class SvcHub(object):
fn = sel_fn fn = sel_fn
try: try:
os.makedirs(os.path.dirname(fn)) bos.makedirs(os.path.dirname(fn))
except: except:
pass pass
@ -1135,6 +1136,9 @@ class SvcHub(object):
lh = codecs.open(fn, "w", encoding="utf-8", errors="replace") lh = codecs.open(fn, "w", encoding="utf-8", errors="replace")
if getattr(self.args, "free_umask", False):
os.fchmod(lh.fileno(), 0o644)
argv = [pybin] + self.argv argv = [pybin] + self.argv
if hasattr(shlex, "quote"): if hasattr(shlex, "quote"):
argv = [shlex.quote(x) for x in argv] argv = [shlex.quote(x) for x in argv]

View file

@ -387,14 +387,18 @@ class Tftpd(object):
if not a: if not a:
a = (self.args.iobuf,) a = (self.args.iobuf,)
return open(ap, mode, *a, **ka) ret = open(ap, mode, *a, **ka)
if wr and "chmod_f" in vfs.flags:
os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
return ret
def _mkdir(self, vpath: str, *a) -> None: def _mkdir(self, vpath: str, *a) -> None:
vfs, _, ap = self._v2a("mkdir", vpath, [False, True]) vfs, _, ap = self._v2a("mkdir", vpath, [False, True])
if "*" not in vfs.axs.uwrite: if "*" not in vfs.axs.uwrite:
yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,)) yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
return bos.mkdir(ap) return bos.mkdir(ap, vfs.flags["chmod_d"])
def _unlink(self, vpath: str) -> None: def _unlink(self, vpath: str) -> None:
# return bos.unlink(self._v2a("stat", vpath, *a)[1]) # return bos.unlink(self._v2a("stat", vpath, *a)[1])

View file

@ -269,7 +269,8 @@ class ThumbSrv(object):
self.log("joined waiting room for %r" % (tpath,)) self.log("joined waiting room for %r" % (tpath,))
except: except:
thdir = os.path.dirname(tpath) thdir = os.path.dirname(tpath)
bos.makedirs(os.path.join(thdir, "w")) chmod = 0o700 if self.args.free_umask else 0o755
bos.makedirs(os.path.join(thdir, "w"), chmod)
inf_path = os.path.join(thdir, "dir.txt") inf_path = os.path.join(thdir, "dir.txt")
if not bos.path.exists(inf_path): if not bos.path.exists(inf_path):

View file

@ -915,7 +915,8 @@ class Up2k(object):
# only need to protect register_vpath but all in one go feels right # only need to protect register_vpath but all in one go feels right
for vol in vols: for vol in vols:
try: try:
bos.makedirs(vol.realpath) # gonna happen at snap anyways # mkdir gonna happen at snap anyways;
bos.makedirs(vol.realpath, vol.flags["chmod_d"])
dir_is_empty(self.log_func, not self.args.no_scandir, vol.realpath) dir_is_empty(self.log_func, not self.args.no_scandir, vol.realpath)
except Exception as ex: except Exception as ex:
self.volstate[vol.vpath] = "OFFLINE (cannot access folder)" self.volstate[vol.vpath] = "OFFLINE (cannot access folder)"
@ -1141,6 +1142,20 @@ class Up2k(object):
del fl[k1] del fl[k1]
else: else:
fl[k1] = ",".join(x for x in fl[k1]) fl[k1] = ",".join(x for x in fl[k1])
if fl["chmod_d"] == int(self.args.chmod_d, 8):
fl.pop("chmod_d")
try:
if fl["chmod_f"] == int(self.args.chmod_f or "-1", 8):
fl.pop("chmod_f")
except:
pass
for k in ("chmod_f", "chmod_d"):
try:
fl[k] = "%o" % (fl[k])
except:
pass
a = [ a = [
(ft if v is True else ff if v is False else fv).format(k, str(v)) (ft if v is True else ff if v is False else fv).format(k, str(v))
for k, v in fl.items() for k, v in fl.items()
@ -3290,7 +3305,7 @@ class Up2k(object):
reg, reg,
"up2k._get_volsize", "up2k._get_volsize",
) )
bos.makedirs(ap2) bos.makedirs(ap2, vfs.flags["chmod_d"])
vfs.lim.nup(cj["addr"]) vfs.lim.nup(cj["addr"])
vfs.lim.bup(cj["addr"], cj["size"]) vfs.lim.bup(cj["addr"], cj["size"])
@ -3397,11 +3412,11 @@ class Up2k(object):
self.log(t % (mts - mtc, mts, mtc, fp)) self.log(t % (mts - mtc, mts, mtc, fp))
ow = False ow = False
ptop = job["ptop"]
vf = self.flags.get(ptop) or {}
if ow: if ow:
self.log("replacing existing file at %r" % (fp,)) self.log("replacing existing file at %r" % (fp,))
cur = None cur = None
ptop = job["ptop"]
vf = self.flags.get(ptop) or {}
st = bos.stat(fp) st = bos.stat(fp)
try: try:
vrel = vjoin(job["prel"], fname) vrel = vjoin(job["prel"], fname)
@ -3421,8 +3436,13 @@ class Up2k(object):
else: else:
dip = self.hub.iphash.s(ip) dip = self.hub.iphash.s(ip)
suffix = "-%.6f-%s" % (ts, dip) f, ret = ren_open(
f, ret = ren_open(fname, "wb", fdir=fdir, suffix=suffix) fname,
"wb",
fdir=fdir,
suffix="-%.6f-%s" % (ts, dip),
chmod=vf.get("chmod_f", -1),
)
f.close() f.close()
return ret return ret
@ -4277,7 +4297,7 @@ class Up2k(object):
self.log(t, 1) self.log(t, 1)
raise Pebkac(405, t) raise Pebkac(405, t)
bos.makedirs(os.path.dirname(dabs)) bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
c1, w, ftime_, fsize_, ip, at = self._find_from_vpath( c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
svn_dbv.realpath, srem_dbv svn_dbv.realpath, srem_dbv
@ -4453,7 +4473,7 @@ class Up2k(object):
vp = vjoin(dvp, rem) vp = vjoin(dvp, rem)
try: try:
dvn, drem = self.vfs.get(vp, uname, False, True) dvn, drem = self.vfs.get(vp, uname, False, True)
bos.mkdir(dvn.canonical(drem)) bos.mkdir(dvn.canonical(drem), dvn.flags["chmod_d"])
except: except:
pass pass
@ -4523,7 +4543,7 @@ class Up2k(object):
is_xvol = svn.realpath != dvn.realpath is_xvol = svn.realpath != dvn.realpath
bos.makedirs(os.path.dirname(dabs)) bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
if is_dirlink: if is_dirlink:
dlabs = absreal(sabs) dlabs = absreal(sabs)
@ -5030,8 +5050,13 @@ class Up2k(object):
else: else:
dip = self.hub.iphash.s(job["addr"]) dip = self.hub.iphash.s(job["addr"])
suffix = "-%.6f-%s" % (job["t0"], dip) f, job["tnam"] = ren_open(
f, job["tnam"] = ren_open(tnam, "wb", fdir=pdir, suffix=suffix) tnam,
"wb",
fdir=pdir,
suffix="-%.6f-%s" % (job["t0"], dip),
chmod=vf.get("chmod_f", -1),
)
try: try:
abspath = djoin(pdir, job["tnam"]) abspath = djoin(pdir, job["tnam"])
sprs = job["sprs"] sprs = job["sprs"]

View file

@ -1585,6 +1585,7 @@ def ren_open(fname: str, *args: Any, **kwargs: Any) -> tuple[typing.IO[Any], str
fun = kwargs.pop("fun", open) fun = kwargs.pop("fun", open)
fdir = kwargs.pop("fdir", None) fdir = kwargs.pop("fdir", None)
suffix = kwargs.pop("suffix", None) suffix = kwargs.pop("suffix", None)
chmod = kwargs.pop("chmod", -1)
if fname == os.devnull: if fname == os.devnull:
return fun(fname, *args, **kwargs), fname return fun(fname, *args, **kwargs), fname
@ -1628,6 +1629,11 @@ def ren_open(fname: str, *args: Any, **kwargs: Any) -> tuple[typing.IO[Any], str
fp2 = os.path.join(fdir, fp2) fp2 = os.path.join(fdir, fp2)
with open(fsenc(fp2), "wb") as f2: with open(fsenc(fp2), "wb") as f2:
f2.write(orig_name.encode("utf-8")) f2.write(orig_name.encode("utf-8"))
if chmod >= 0:
os.fchmod(f2.fileno(), chmod)
if chmod >= 0:
os.fchmod(f.fileno(), chmod)
return f, fname return f, fname

View file

@ -7,7 +7,7 @@ import subprocess as sp
# to convert the copyparty --help to html, run this in xfce4-terminal @ 140x43: # to convert the copyparty --help to html, run this in xfce4-terminal @ 140x43:
_ = r"""" _ = r""""
echo; for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -pwhash -zm; do echo; for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -chmod -pwhash -zm; do
./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0139d\n\n\n'; done # xfce4-terminal @ 140x43 ./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0139d\n\n\n'; done # xfce4-terminal @ 140x43
""" """
# click [edit] => [select all] # click [edit] => [select all]

View file

@ -23,7 +23,7 @@ exit 0
# first open an infinitely wide console (this is why you own an ultrawide) and copypaste this into it: # first open an infinitely wide console (this is why you own an ultrawide) and copypaste this into it:
for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -pwhash -zm; do for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -chmod -pwhash -zm; do
./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0255d\n\n\n'; done ./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0255d\n\n\n'; done
# then copypaste all of the output by pressing ctrl-shift-a, ctrl-shift-c # then copypaste all of the output by pressing ctrl-shift-a, ctrl-shift-c

View file

@ -161,7 +161,7 @@ class Cfg(Namespace):
ex = "db_act forget_ip idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" ex = "db_act forget_ip idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
ka.update(**{k: 0 for k in ex.split()}) ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR" ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR"
ka.update(**{k: "" for k in ex.split()}) ka.update(**{k: "" for k in ex.split()})
ex = "ban_403 ban_404 ban_422 ban_pw ban_url spinner" ex = "ban_403 ban_404 ban_422 ban_pw ban_url spinner"
@ -181,6 +181,7 @@ class Cfg(Namespace):
c=c, c=c,
E=E, E=E,
bup_ck="sha512", bup_ck="sha512",
chmod_d="755",
dbd="wal", dbd="wal",
dk_salt="b" * 16, dk_salt="b" * 16,
fk_salt="a" * 16, fk_salt="a" * 16,