Quick and dirty delete all top-level files in a directory not owned by any user with a process currently running

Handy for shared systems where you want to reap files in, for example, /tmp without affect currently running processes. There are some unhandled exceptions, such as files disappearing after the file list is built but before the file is deleted (which will throw an uncaught file not found exception) but as a quick and dirty first attempt I think it’s not too shabby.

#!/usr/bin/env python

import collections
import logging
import getopt
import os
import shutil
import sys

def usage():
        sys.stderr.write("Usage: {scriptname} [[-h | -? | --help] | [-d | --debug] path [path1 [path2...]]]\n".format(scriptname=sys.argv[0]))

def remove(path):
        if os.path.isdir(path):
                shutil.rmtree(path)
        else:
                os.remove(path)

def reap(directory):

        # Get a list of all files in the directory to consider for reaping, and group them by owner's uid
        user_files = collections.defaultdict(list)
        map(lambda file: user_files[os.lstat(os.path.join(directory, file)).st_uid].append(file), os.listdir(directory))

        # Get a list of users who have processes running on the box
        users_with_processes = [ os.lstat('/proc/{proc}'.format(proc=proc)).st_uid for proc in os.listdir('/proc') if proc.isdigit() ]

        # Now find the users who do not have running processes, as these are the users' whose files we are going to reap (always skip root)
        users_to_reap = [ user for user in user_files.keys() if user != 0 and user not in users_with_processes ]

        # remove the files
        if DEBUG:
                action=logging.debug
        else:
                action=remove
        map(action, [ os.path.join(directory, file) for file in [ file for user in users_to_reap for file in user_files[user] ] ])

try:
        optlist, args = getopt.getopt(sys.argv[1:], 'dh?', ['debug', 'help'])
except getopt.GetoptError as err:
        sys.stderr.write(str(err) + "\n")
        usage()
        sys.exit(2)

DEBUG=False

for opt, value in optlist:
        if opt in ('-d', '--debug'):
                DEBUG=True
        elif opt in ('-h', '-?', '--help'):
                usage()
                sys.exit()
        else:
                sys.stderr.write("Unhandled option: {opt}\n".format(opt=opt))
                usage()
                sys.exit(2)

if len(args) == 0:
        usage()
        sys.exit(1)

if DEBUG:
        logging.basicConfig(level=logging.DEBUG)

for dir in args:
        reap(dir)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>