File: mount-users.py

package info (click to toggle)
cockpit 355-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 311,568 kB
  • sloc: javascript: 774,787; python: 40,655; ansic: 35,157; cpp: 11,141; sh: 3,512; makefile: 580; xml: 261
file content (165 lines) | stat: -rwxr-xr-x 5,145 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#! /usr/bin/python3

import json
import os
import pwd
import signal
import subprocess
import sys
import time

# mount-users   --  Find and terminate processes that keep a mount busy


def fuser(mount_point):

    import dbus
    bus = dbus.SystemBus()
    systemd_manager = dbus.Interface(bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1'),
                                     dbus_interface='org.freedesktop.systemd1.Manager')

    results = {}

    def get_cmdline(pid):
        with open(f"/proc/{pid}/cmdline") as f:
            return " ".join(f.read().split("\0"))

    def get_stat(pid):
        with open(f"/proc/{pid}/stat") as f:
            stat = f.read()
        # Field two is everything between the first "(" and the last ")", the rest is space separated.
        comm_start = stat.index("(") + 1
        comm_end = stat.rindex(")")
        return ([stat[0:comm_start - 1].strip(), stat[comm_start:comm_end],
                 *list(filter(lambda f: f != '', stat[comm_end + 1:-1].split(' ')))])

    def get_loginuser(pid):
        uid = os.stat("/proc/%s" % pid).st_uid
        try:
            return pwd.getpwuid(uid).pw_name
        except OSError:
            return uid

    def check(path, pid):
        t = os.readlink(path)
        if t == mount_point or t.startswith(mount_point + "/"):
            unit = systemd_manager.GetUnitByPID(int(pid))
            if unit not in results:
                unit_obj = bus.get_object('org.freedesktop.systemd1', unit)
                unit_id = unit_obj.Get("org.freedesktop.systemd1.Unit", "Id",
                                       dbus_interface="org.freedesktop.DBus.Properties")
                if unit_id.endswith(".scope"):
                    stat = get_stat(pid)
                    start = int(stat[21]) / os.sysconf('SC_CLK_TCK')
                    results[pid] = {"pid": int(pid),
                                    "cmd": get_cmdline(pid),
                                    "comm": stat[1],
                                    "user": get_loginuser(pid),
                                    "since": time.clock_gettime(time.CLOCK_MONOTONIC) - start}
                else:
                    desc = unit_obj.Get("org.freedesktop.systemd1.Unit", "Description",
                                        dbus_interface="org.freedesktop.DBus.Properties")
                    timestamp = unit_obj.Get("org.freedesktop.systemd1.Unit", "ActiveEnterTimestamp",
                                             dbus_interface="org.freedesktop.DBus.Properties")
                    results[unit] = {"unit": unit_id,
                                     "cmd": get_cmdline(pid),
                                     "desc": desc,
                                     "user": get_loginuser(pid),
                                     "since": time.time() - timestamp / 1e6}
            return True
        return False

    def checkdir(path, pid):
        for f in os.listdir(path):
            if check(os.path.join(path, f), pid):
                return True
        return False

    my_pid = os.getpid()

    for p in os.listdir("/proc/"):
        if not p.isdigit():
            continue
        if int(p) == my_pid:
            continue
        proc = "/proc/%s/" % p
        try:
            if check(proc + "exe", p):
                continue
            if check(proc + "root", p):
                continue
            if check(proc + "cwd", p):
                continue
            if checkdir(proc + "fd", p):
                continue
            if checkdir(proc + "map_files", p):
                continue
        except OSError:
            pass

    return list(results.values())


def users(mount_point):
    data = fuser(mount_point)
    sys.stdout.write(json.dumps(data) + "\n")
    sys.stdout.flush()


def stop_pids(pids):
    def sendsig(pid, sig):
        try:
            os.kill(pid, sig)
            return True
        except Exception:
            return False

    def sendsigs(pids, sig):
        for p in pids:
            sendsig(p, sig)

    def pollpids(pids, count, interval):
        while count > 0:
            i = 0
            while i < len(pids):
                if not sendsig(pids[i], 0):
                    pids.pop(i)
                else:
                    i += 1
            if len(pids) == 0:
                break
            count -= 1
            time.sleep(interval)

    sendsigs(pids, signal.SIGTERM)
    sendsigs(pids, signal.SIGHUP)
    pollpids(pids, 10, 0.1)
    pollpids(pids, 19, 1)
    sendsigs(pids, signal.SIGKILL)
    pollpids(pids, 10, 0.1)


def stop_units(units):
    if len(units) > 0:
        subprocess.check_call(['systemctl', 'stop', *units])


def stop(users):
    stop_pids([u["pid"] for u in users if "pid" in u])
    stop_units([u["unit"] for u in users if "unit" in u])


def dispatch(argv):
    if argv[1] == "users":
        users(argv[2])
    elif argv[1] == "stop":
        stop(json.loads(argv[2]))


try:
    dispatch(sys.argv)
except subprocess.CalledProcessError as e:
    sys.exit(e.returncode)
except Exception as e:
    sys.stderr.write(str(e) + "\n")
    sys.exit(1)