mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
enforce single-instance for session/shares db
use file-locking to detect and prevent misconfigurations which could lead to subtle unexpected behavior
This commit is contained in:
parent
8e0364efad
commit
acfaacbd46
|
@ -2135,7 +2135,9 @@ buggy feature? rip it out by setting any of the following environment variables
|
|||
|
||||
| env-var | what it does |
|
||||
| -------------------- | ------------ |
|
||||
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
|
||||
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
||||
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
|
||||
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
|
||||
| `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
|
||||
| `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
|
||||
|
@ -2676,7 +2678,6 @@ set any of the following environment variables to disable its associated optiona
|
|||
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
|
||||
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
|
||||
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
|
||||
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
|
||||
| `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
|
||||
| `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
|
||||
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
||||
|
|
|
@ -64,6 +64,7 @@ from .util import (
|
|||
expat_ver,
|
||||
gzip,
|
||||
load_ipu,
|
||||
lock_file,
|
||||
min_ex,
|
||||
mp,
|
||||
odfusion,
|
||||
|
@ -419,6 +420,7 @@ class SvcHub(object):
|
|||
# the sessions-db is whatever, if something looks broken then just nuke it
|
||||
|
||||
db_path = self.args.ses_db
|
||||
db_lock = db_path + ".lock"
|
||||
try:
|
||||
create = not os.path.getsize(db_path)
|
||||
except:
|
||||
|
@ -444,7 +446,11 @@ class SvcHub(object):
|
|||
raise
|
||||
sver = 1
|
||||
self._create_session_db(cur)
|
||||
self._verify_session_db(cur, sver)
|
||||
err = self._verify_session_db(cur, sver, db_path)
|
||||
if err:
|
||||
tries = 99
|
||||
self.args.no_ses = True
|
||||
self.log("root", err, 3)
|
||||
break
|
||||
|
||||
except Exception as ex:
|
||||
|
@ -460,6 +466,10 @@ class SvcHub(object):
|
|||
db.close() # type: ignore
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.unlink(db_lock)
|
||||
except:
|
||||
pass
|
||||
os.unlink(db_path)
|
||||
|
||||
def _create_session_db(self, cur: "sqlite3.Cursor") -> None:
|
||||
|
@ -476,7 +486,7 @@ class SvcHub(object):
|
|||
cur.execute(cmd)
|
||||
self.log("root", "created new sessions-db")
|
||||
|
||||
def _verify_session_db(self, cur: "sqlite3.Cursor", sver: int) -> None:
|
||||
def _verify_session_db(self, cur: "sqlite3.Cursor", sver: int, db_path: str) -> str:
|
||||
# ensure writable (maybe owned by other user)
|
||||
db = cur.connection
|
||||
|
||||
|
@ -488,6 +498,10 @@ class SvcHub(object):
|
|||
except:
|
||||
owner = 0
|
||||
|
||||
if not lock_file(db_path + ".lock"):
|
||||
t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
|
||||
return t % (db_path, owner)
|
||||
|
||||
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
|
||||
if owner:
|
||||
# wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
|
||||
|
@ -505,6 +519,7 @@ class SvcHub(object):
|
|||
db.commit()
|
||||
cur.close()
|
||||
db.close()
|
||||
return ""
|
||||
|
||||
def setup_share_db(self) -> None:
|
||||
al = self.args
|
||||
|
@ -513,7 +528,7 @@ class SvcHub(object):
|
|||
al.shr = ""
|
||||
return
|
||||
|
||||
import sqlite3
|
||||
assert sqlite3 # type: ignore # !rm
|
||||
|
||||
al.shr = al.shr.strip("/")
|
||||
if "/" in al.shr or not al.shr:
|
||||
|
@ -528,6 +543,7 @@ class SvcHub(object):
|
|||
# the shares-db is important, so panic if something is wrong
|
||||
|
||||
db_path = self.args.shr_db
|
||||
db_lock = db_path + ".lock"
|
||||
try:
|
||||
create = not os.path.getsize(db_path)
|
||||
except:
|
||||
|
@ -560,6 +576,12 @@ class SvcHub(object):
|
|||
except:
|
||||
owner = 0
|
||||
|
||||
if not lock_file(db_lock):
|
||||
t = "the shares-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --shr-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now panic."
|
||||
t = t % (db_path, owner)
|
||||
self.log("root", t, 1)
|
||||
raise Exception(t)
|
||||
|
||||
sch1 = [
|
||||
r"create table kv (k text, v int)",
|
||||
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
||||
|
|
|
@ -114,8 +114,14 @@ IP6ALL = "0:0:0:0:0:0:0:0"
|
|||
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
import fcntl
|
||||
|
||||
HAVE_FCNTL = True
|
||||
except:
|
||||
HAVE_FCNTL = False
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
import termios
|
||||
except:
|
||||
pass
|
||||
|
@ -3940,6 +3946,73 @@ def hidedir(dp) -> None:
|
|||
pass
|
||||
|
||||
|
||||
_flocks = {}
|
||||
|
||||
|
||||
def _lock_file_noop(ap: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _lock_file_ioctl(ap: str) -> bool:
|
||||
assert fcntl # type: ignore # !rm
|
||||
try:
|
||||
fd = _flocks.pop(ap)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
|
||||
fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
|
||||
# NOTE: the fcntl.lockf identifier is (pid,node);
|
||||
# the lock will be dropped if os.close(os.open(ap))
|
||||
# is performed anywhere else in this thread
|
||||
|
||||
try:
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
_flocks[ap] = fd
|
||||
return True
|
||||
except Exception as ex:
|
||||
eno = getattr(ex, "errno", -1)
|
||||
try:
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
if eno in (errno.EAGAIN, errno.EACCES):
|
||||
return False
|
||||
print("WARNING: unexpected errno %d from fcntl.lockf; %r" % (eno, ex))
|
||||
return True
|
||||
|
||||
|
||||
def _lock_file_windows(ap: str) -> bool:
|
||||
try:
|
||||
import msvcrt
|
||||
|
||||
try:
|
||||
fd = _flocks.pop(ap)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
|
||||
fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
|
||||
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
||||
return True
|
||||
except Exception as ex:
|
||||
eno = getattr(ex, "errno", -1)
|
||||
if eno == errno.EACCES:
|
||||
return False
|
||||
print("WARNING: unexpected errno %d from msvcrt.locking; %r" % (eno, ex))
|
||||
return True
|
||||
|
||||
|
||||
if os.environ.get("PRTY_NO_DB_LOCK"):
|
||||
lock_file = _lock_file_noop
|
||||
elif ANYWIN:
|
||||
lock_file = _lock_file_windows
|
||||
elif HAVE_FCNTL:
|
||||
lock_file = _lock_file_ioctl
|
||||
else:
|
||||
lock_file = _lock_file_noop
|
||||
|
||||
|
||||
try:
|
||||
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
|
||||
# py3.8 doesn't have .files
|
||||
|
|
Loading…
Reference in a new issue