mirror of
https://github.com/DarrylNixon/melamine.git
synced 2024-04-22 06:27:20 -07:00
101 lines
4.1 KiB
Python
101 lines
4.1 KiB
Python
import argparse
|
|
import asyncio
|
|
from collections import defaultdict
|
|
|
|
from aiopath import AsyncPath
|
|
|
|
from .classes import get_all_hardlinks
|
|
from .classes import ShredDir
|
|
from .classes import ShredFile
|
|
from .logs import logger
|
|
|
|
|
|
async def main(job: argparse.Namespace) -> bool:
|
|
"""
|
|
This is the main function for processing a shred request.
|
|
It is called by the CLI and builds a job queue based on the arguments passed.
|
|
"""
|
|
|
|
# Expand all directories and files, and collect mount point information
|
|
tasks = []
|
|
for path in job.paths:
|
|
if await path.is_file():
|
|
logger.info(f"Adding file: {path}")
|
|
tasks.append(ShredFile(path))
|
|
elif await path.is_dir():
|
|
logger.info(f"Adding directory: {path}")
|
|
tasks.append(ShredDir(path, recursive=job.recursive))
|
|
else:
|
|
raise TypeError(f"Not a file or directory: {path}")
|
|
new_paths = set(await asyncio.gather(*tasks))
|
|
|
|
# Try to delete hardlinks based on the filesystem type
|
|
job.paths = await get_all_hardlinks(new_paths)
|
|
|
|
tasks = [path.absolute() for path in job.ignoredir]
|
|
for path in ["/proc", "/dev", "/sys"]:
|
|
tasks.append(AsyncPath(path).absolute())
|
|
job.ignoredir = set(await asyncio.gather(*tasks))
|
|
|
|
# Shred all physical files including hardlinks
|
|
for path in job.paths:
|
|
tasks = []
|
|
if isinstance(path, ShredFile):
|
|
tasks.append(path.shred(hash=job.exhaustive, dryrun=job.dryrun, ignoredirs=job.ignoredir))
|
|
elif isinstance(path, ShredDir):
|
|
tasks.append(path.shred(hash=job.exhaustive, dryrun=job.dryrun, ignoredirs=job.ignoredir))
|
|
done, _ = await asyncio.wait(tasks)
|
|
for task in done:
|
|
e = task.exception()
|
|
if e:
|
|
logger.warning(f"Error raised while shredding: {e}")
|
|
|
|
# Just in case, use "find" to delete any remaining hardlinks
|
|
# from the mount point, so let's build a map of inodes to mount points
|
|
logger.info("Deleting remaining hardlinks using find")
|
|
inodes_in_mount_points = defaultdict(set)
|
|
for path in job.paths:
|
|
inodes_in_mount_points[path.mount_point].add(path.inode)
|
|
|
|
# We'll also limit concurrency to something reasonable since stat
|
|
# on an entire filesystem might be a bit burdensome
|
|
semaphore = asyncio.Semaphore(1024)
|
|
|
|
async def check_inode_and_unlink(item, inodes):
|
|
async with semaphore:
|
|
if await item.stat().st_ino in inodes:
|
|
log_buf = f"Deleting hardlink: {item.path}"
|
|
if not job.dryrun:
|
|
log_buf = "DRY RUN " + log_buf
|
|
await item.path.unlink()
|
|
logger.info(log_buf)
|
|
|
|
for mount_point, inodes in inodes_in_mount_points.items():
|
|
# checking for . and .. should not be neccessary w/ rglob
|
|
tasks = []
|
|
# scandir/glob/rglob doesn't play nice with FileNotFound errors,
|
|
# so let's avoid them entirely for now in /proc, /dev, and /sys
|
|
if any((mount_point / path).exists() for path in ("/proc", "/dev", "/sys")):
|
|
# Traverse every directory in mount_point recursively except /proc, /dev, and /sys
|
|
async for item in mount_point.glob("*"):
|
|
if await item.is_dir():
|
|
if str(item) in ("proc", "dev", "sys"):
|
|
continue
|
|
async for subitem in item.rglob("*"):
|
|
if any(str(subitem).startswith(str(path)) for path in job.ignoredir):
|
|
continue
|
|
tasks.append(check_inode_and_unlink(subitem, inodes))
|
|
else:
|
|
tasks.append(check_inode_and_unlink(item, inodes))
|
|
else:
|
|
async for item in mount_point.rglob("*"):
|
|
if any(str(item).startswith(str(path)) for path in job.ignoredir):
|
|
continue
|
|
tasks.append(check_inode_and_unlink(item, inodes))
|
|
done, _ = await asyncio.wait(tasks)
|
|
for task in done:
|
|
e = task.exception()
|
|
if e:
|
|
logger.warning(f"Unable to unlink hardlink: {e}")
|
|
|
|
logger.info("Done")
|