Fork me on GitHub
  ▖     
▃▀▄▄▖▄▄▖
 ▀▖     
        


#!/usr/bin/env python3
# usage: plash clean
# Cleanup internal states
#
# there may be some abstraction leaks with cleaning up fuse mountpoints that
# will be handled better in the future. File a bug if this subcommand behaves
# unexpectedly.

import math
import os
import shutil
import signal
import subprocess
import sys
from collections import Counter
from time import time

from plash import utils
from plash.unshare import unshare_if_user

utils.handle_help_flag()
utils.assert_initialized()

plash_data = utils.get_plash_data()

print('output_stable: false')


def remove_broken_links(dir):
    count = 0
    for file in os.listdir(dir):
        full_path = os.path.join(dir, file)
        try:
            os.stat(full_path)
        except FileNotFoundError:
            try:
                os.unlink(full_path)
                count += 1
            except FileNotFoundError:
                pass  # race condition, link removed by another process
    return count


#
# Remove all broken links in $PLASH_DATA/index
#
sys.stdout.write('unlinked_indexes: ')
sys.stdout.flush()
index_dir = os.path.join(plash_data, 'index')
count = remove_broken_links(index_dir)
print(count)

#
# Remove all broken links in $PLASH_DATA/map
#
sys.stdout.write('unlinked_maps: ')
sys.stdout.flush()
maps_dir = os.path.join(plash_data, 'map')
count = remove_broken_links(maps_dir)
print(count)

#
# remove unused fuse mount processes
#
sys.stdout.write('killed_fuses: ')
sys.stdout.flush()
used_mountpoint = os.path.join(plash_data, 'mnt')
mnt_ns_counter = Counter()
running_unionfs = {}
killed = 0
for pid in os.listdir('/proc'):
    if not pid.isdigit():
        continue

    # read process mount namespace and command line arguments
    try:
        mntns = os.readlink(os.path.join('/proc', pid, 'ns', 'mnt'))
        cmdline_path = os.path.join('/proc', pid, 'cmdline')
        with open(cmdline_path) as f:
            cmd = f.read().split('\0')[:-1]
    except OSError:
        continue

    # collect unionfs processes mounted at this plash data
    if cmd and os.path.basename(
            cmd[0]) in ('unionfs',
                        'unionfs-fuse') and (cmd[-1] == used_mountpoint):
        running_unionfs[pid] = mntns

    mnt_ns_counter.update([mntns])
for unionfs_pid, mntns in running_unionfs.items():
    count = mnt_ns_counter[mntns]

    # check if this unionfs_pid instance is the only one using
    # it's mount namespace
    if count == 1:
        try:
            os.kill(int(unionfs_pid), signal.SIGINT)
            killed += 1
        except OSError:
            pass
print(killed)

#
# Remove unused tmp dirs in $PLASH_DATA/tmp
#
sys.stdout.write('removed_tmpdirs: ')
sys.stdout.flush()
unshare_if_user()  # map users for filesystem access
deleted_tmps = 0
tmp = os.path.join(utils.get_plash_data(), 'tmp')
for file in os.listdir(tmp):
    abs_file = os.path.join(tmp, file)

    try:
        _, sid, pid, *_ = file.split('_')
        pid = int(pid)
    except ValueError:
        continue

    try:
        real_sid = os.getsid(pid)
    except ProcessLookupError:
        # no such pid, delete its tmp data
        delete = True
    else:
        if str(sid) == str(real_sid):
            # process still alive, don't delete its tmp dir
            delete = False
        else:
            # sid mismatch, its another one, delete
            delete = True

    if delete:
        shutil.rmtree(abs_file)
        deleted_tmps += 1
print(deleted_tmps)