diff --git a/README.md b/README.md index d6746a0..c800480 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # chguard
- chguard logo + chguard logo
diff --git a/chguard/cli.py b/chguard/cli.py index fa4e1f3..e825d37 100644 --- a/chguard/cli.py +++ b/chguard/cli.py @@ -248,42 +248,49 @@ def main() -> None: root = normalize_root(args.save) - if state_exists(conn, args.name): - if not args.overwrite: - raise SystemExit( - f"State '{args.name}' already exists (use --overwrite)" - ) - delete_state(conn, args.name) + try: + with conn: # start transaction + if state_exists(conn, args.name): + if not args.overwrite: + raise SystemExit( + f"State '{args.name}' already exists (use --overwrite)" + ) + # if the new save fails, this delete_state step will also roll back + delete_state(conn, args.name, commit=False) - state_id = create_state(conn, args.name, str(root), os.getuid()) - - # Abort early if root-owned files exist and user is not root. - # This prevents creating snapshots that cannot be meaningfully restored. - for entry in scan_tree(root, excludes=args.exclude): - if entry.uid == 0 and not _is_root(): - raise SystemExit( - "This path contains root-owned files.\n" - "Saving this state requires sudo." + state_id = create_state( + conn, args.name, str(root), os.getuid(), commit=False ) - conn.execute( - """ - INSERT INTO entries (state_id, path, type, mode, uid, gid) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - state_id, - entry.path, - entry.type, - entry.mode, - entry.uid, - entry.gid, - ), - ) + # Abort early if root-owned files exist and user is not root. + # This prevents creating snapshots that cannot be meaningfully restored. + for entry in scan_tree(root, excludes=args.exclude): + if entry.uid == 0 and not _is_root(): + raise SystemExit( + "This path contains root-owned files.\n" + "Saving this state requires sudo." + ) - conn.commit() - console.print(f"Saved state '{args.name}' for {root}") - return + conn.execute( + """ + INSERT INTO entries (state_id, path, type, mode, uid, gid) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + state_id, + entry.path, + entry.type, + entry.mode, + entry.uid, + entry.gid, + ), + ) + + console.print(f"Saved state '{args.name}' for {root}") + return + + except SystemExit: + raise if args.restore: if not args.state: diff --git a/chguard/db.py b/chguard/db.py index 515b762..16989bd 100644 --- a/chguard/db.py +++ b/chguard/db.py @@ -61,19 +61,28 @@ def state_exists(conn: sqlite3.Connection, name: str) -> bool: def create_state( - conn: sqlite3.Connection, name: str, root_path: str, created_by_uid: int + conn: sqlite3.Connection, + name: str, + root_path: str, + created_by_uid: int, + *, + commit: bool = True, ) -> int: cur = conn.execute( "INSERT INTO states (name, root_path, created_at, created_by_uid) VALUES (?, ?, ?, ?)", (name, root_path, utc_now_iso(), created_by_uid), ) - conn.commit() + if commit: + conn.commit() return int(cur.lastrowid) -def delete_state(conn: sqlite3.Connection, name: str) -> int: +def delete_state( + conn: sqlite3.Connection, name: str, commit: bool = True +) -> int: cur = conn.execute("DELETE FROM states WHERE name = ?", (name,)) - conn.commit() + if commit: + conn.commit() return cur.rowcount diff --git a/pyproject.toml b/pyproject.toml index 8212e32..4fcebdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "chguard" -version = "0.2.1" +version = "0.2.2" description = "Safety-first tool to snapshot and restore filesystem ownership and permissions." authors = ["Marco D'Aleo "] license = "GPL-3.0-or-later"