root/trunk/max-multiseat-storage/multiseat-udisks.py

Revision 910, 15.5 KB (checked in by mario.izquierdo, 13 months ago)

max-multiseat-storage (6.0.max7)

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2# -*- coding: UTF-8 -*-
3##########################################################################
4#
5# Multiseat UDisk inhibit daemon
6# Copyright 2011, Mario Izquierdo, mariodebian at gmail
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2, or (at your option)
11# any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21# 02111-1307, USA.
22###########################################################################
23
24"""
25Changelog
26  20110129 - First usable version
27
28"""
29
30import os
31import sys
32import pprint
33
34import gobject
35gobject.threads_init()
36
37from subprocess import Popen, PIPE, STDOUT
38
39import dbus
40import dbus.service
41if getattr(dbus, 'version', (0, 0, 0)) >= (0, 41, 0):
42    import dbus.glib
43
44PID_FILE="/var/run/multiseat-udisks.pid"
45
46
47#class DBusException(Exception):
48#    def __init__(self, *args, **kwargs):
49#        pass
50#    def __str__(self):
51#        pass
52#       
53#    def get_dbus_name(self):
54#        return self._dbus_error_name
55
56#dbus.exceptions.DBusException=DBusException
57
58class MultiSeatDeviceManager:
59    def __init__(self):
60        self.pid=os.getpid()
61        self.mainloop = gobject.MainLoop()
62        self.all_devices=[]
63        self.bus = dbus.SystemBus()
64        self.proxy = self.bus.get_object("org.freedesktop.UDisks",
65                                    "/org/freedesktop/UDisks")
66        self.iface = dbus.Interface(self.proxy, 'org.freedesktop.UDisks')
67        self.iface.connect_to_signal('DeviceAdded', self.device_added_callback)
68        self.iface.connect_to_signal('DeviceRemoved', self.device_removed_callback)
69
70    def device_added_callback(self, dev):
71        self.init_load()
72
73    def device_removed_callback(self, dev):
74        # search in self.all_devices for a dev.device present and umount it
75        for i  in range(len(self.all_devices)):
76            if not os.path.exists(self.all_devices[i]['device']):
77                print "device_removed_callback() NO EXISTS dev=%s"%self.all_devices[i]
78                if self.all_devices[i]['ismounted']:
79                    self.all_devices[i]['ismounted']=False
80                    self.remove_device(self.all_devices[i])
81                    self.umount_same_disk(self.all_devices[i]['device'])
82        self.init_load()
83
84    def umount_same_disk(self, devname):
85        # removed /dev/sdc3 but not removed /dev/sdc1 and /dev/sdc2
86        # force manual umount
87        if len(devname) < 9:
88            print "umount_same_disk() devname seems to be disk %s, no umounting..."%devname
89            # disk not partition
90            return
91        dev_disk=devname[0:8]
92        for dev in self.all_devices:
93            if dev_disk in dev['device']:
94                print "umount_same_disk() dev_disk=%s dev=%s"%(dev_disk, dev)
95                self.remove_device(dev)
96
97    def remove_device(self, dev):
98        if self.UmountDevice(dev):
99            # remove launcher, mountpoint is deleted in UmountDevice()
100            if os.path.isfile(dev['desktopfile']):
101                os.unlink(dev['desktopfile'])
102                print "remove_device() deleted %s"%dev['desktopfile']
103
104    def get(self, obj, prop):
105        return obj.Get("org.freedesktop.DBus.Properties", prop)
106
107    def init_load(self):
108        self.all_devices=[]
109        for sto in self.iface.EnumerateDevices():
110            sto_obj = self.bus.get_object ('org.freedesktop.UDisks', sto)
111            storage = dbus.Interface (sto_obj, 'org.freedesktop.DBus.Properties')
112            try:
113                storage.Get("org.freedesktop.DBus.Properties", 'DeviceIsRemovable')
114            except Exception, err:
115                print "Exception, perhaps removing device... err=%s"%err
116                continue
117            if bool(self.get(storage, 'DeviceIsPartition')) and \
118               str(self.get(storage, 'DriveConnectionInterface')) == 'usb':
119                fstype=str(self.get(storage, 'IdType'))
120                if fstype not in ['vfat', 'iso9660', 'ext2', 'ext3', 'ext4', 'ntfs']:
121                    continue
122                path=str(self.get(storage, 'NativePath'))
123                seat_id=self.getSeatID(path)
124                serial=str(self.get(storage, 'DriveSerial'))
125                partnumber=str(self.get(storage, 'PartitionNumber'))
126                label=str(self.get(storage, 'IdLabel'))
127                if label == '':
128                    label=serial
129                username=self.getUserfromSeat(seat_id)
130                if username != '':
131                    desktopfile=self.getUserDesktop(username) + "/%s-%s.desktop"%(serial,partnumber)
132                    useruid=int(self.getUserUID(username))
133                else:
134                    desktopfile='/dev/null'
135                    useruid=0
136                dev={
137                    "device": str(self.get(storage, 'DeviceFile')),
138                    "model": str(self.get(storage, 'DriveModel')),
139                    "vendor": str(self.get(storage, 'DriveVendor')),
140                    "serial": serial,
141                    "label": label,
142                    "fstype": fstype,
143                    "ismounted": bool(self.get(storage, 'DeviceIsMounted')),
144                    "partnumber":partnumber,
145                    "mountpoint": "/media/%s-%s"%(serial,partnumber),
146                    "desktopfile": desktopfile,
147                    "path":path,
148                    "seat_id":seat_id,
149                    "username":username,
150                    "useruid":useruid,
151                  }
152                print "init_load() device=%s"%dev
153                if not dev['ismounted'] and username != '' and self.MountDevice(dev):
154                    dev['ismounted']=True
155                    try:
156                        self.CreateLauncher(dev)
157                    except Exception, err:
158                        print "init_load() Exception creating launcher, err=%s"%err
159                self.all_devices.append(dev)
160        pprint.pprint(self.all_devices)
161
162    def getSeatID(self, path):
163        seat_id=0
164        # /sys/devices/pci0000:00/0000:00:02.1/usb1/1-2/1-2.4/1-2.4:1.0/host9/target9:0:0/9:0:0:0/block/sdc/sdc1
165        # /sys/devices/pci0000:00/0000:00:02.1/usb1/1-2/{devnum|busnum}
166        #
167        # /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.4/1-1.5.4:1.0/host7/target7:0:0/7:0:0:0/block/sdf/sdf1
168        # /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/{devnum|busnum}
169        #
170        #       basename(path) == sdf  => pendrive without partitions (don't work, busnum in wrong path)
171        #       basename(path) == sdf1 => first pendrive partition
172        #
173        parent_folder=8
174        if len(os.path.basename(path)) == 3:
175            # sdf
176            parent_folder=7
177        buspath="/".join(path.split('/')[0:len(path.split('/'))-parent_folder])
178        print "getSeatID() buspath=%s"%buspath
179        if not os.path.isfile(buspath+'/busnum'):
180            print "getSeatID() no busnum file in path=%s, return seat_id=0"%path
181            return seat_id
182        busnum=int(open(buspath+'/busnum', 'r').readline())
183        devnum=int(open(buspath+'/devnum', 'r').readline())
184        busnum="%03i"%busnum
185        devnum="%03i"%devnum
186        print "getSeatID() busnum=%s devnum=%s"%(busnum, devnum)
187        # read /dev/seat.db
188        if not os.path.isfile('/dev/seat.db'):
189            return seat_id
190        f=open('/dev/seat.db', 'r')
191        for line in f.readlines():
192            if "%s %s "%(busnum, devnum) in line:
193                print "getSeatID() found SEAT_ID in line=%s"%line.strip()
194                #return 3rd column
195                seat_id=line.split()[2]
196        f.close()
197        return seat_id
198
199    def getUserfromSeat(self, seat_id):
200        username=''
201        cmd="pidof gdm && gdmdynamic -l"
202        p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
203        for line in p.stdout.readlines():
204            print "getUserfromSeat() line=%s"%line.strip()
205            if ":" in line:
206                # :0,pc01,8;    :2,pc04,8;    :3,pc03,9
207                seats=line.strip().split(';')
208                for seat in seats:
209                    if ":%s"%seat_id in seat:
210                        username=seat.split(',')[1]
211        return username
212
213    def MountDevice(self, d):
214        if d['username'] == '':
215            # don't mount if no username is logged in SEAT_ID
216            return False
217        if not self.CreateMountPoint(d):
218            # can't create mountpoint
219            return False
220        # udisks is inhibited, we have to mount it
221        # iso9660 (rw,nosuid,nodev,uhelper=udisks,uid=0,gid=0,iocharset=utf8,mode=0400,dmode=0500)
222        #
223        # vfat: rw,nosuid,nodev,uhelper=udisks,uid=1000,gid=1000,shortname=mixed,dmask=0077,utf8=1,showexec,flush
224        #
225        # any_allow "exec", "noexec", "nodev", "nosuid", "atime", "noatime", "nodiratime", "ro", "rw", "sync", "dirsync"
226        if d['fstype'] == 'iso9660':
227            options="-o rw,nosuid,nodev,uhelper=multiseat-udisks,uid=%s,gid=0,iocharset=utf8,mode=0400,dmode=0500"%d['username']
228        elif d['fstype'] == 'vfat':
229            options="-o rw,nosuid,nodev,uhelper=multiseat-udisks,uid=%s,gid=0,shortname=mixed,dmask=0077,utf8=1,showexec,flush"%d['username']
230        elif d['fstype'] in ['ext2', 'ext3', 'ext4']:
231            options="-o rw,nosuid,nodev,uhelper=multiseat-udisks"
232            # no uid,gid support, in ext* filesystems => chown mountpoint
233        elif d['fstype'] == 'ntfs':
234            # mount with ntfs-3g???
235            options="-o rw,nosuid,nodev,uhelper=multiseat-udisks,uid=%s,gid=0,dmask=0077,default_permissions"%d['username']
236        else:
237            options="-o rw,nosuid,nodev,uhelper=multiseat-udisks"
238       
239        cmd="mount -t %s %s '%s' %s"%(d['fstype'], d['device'], d['mountpoint'], options)
240        print "MountDevice() cmd=%s"%cmd
241        p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
242        for line in p.stdout.readlines():
243            print line
244        p.communicate()
245        if p.returncode == 0:
246            os.chown(d['mountpoint'], d['useruid'], 0)
247            os.chmod(d['mountpoint'], 0700)
248            return True
249        return False
250
251    def UmountDevice(self, d):
252        cmd="umount %s"%(d['device'])
253        print "UmountDevice() cmd=%s"%cmd
254        p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
255        for line in p.stdout.readlines():
256            print line
257        p.communicate()
258        if p.returncode == 0:
259            self.RemoveMountPoint(d)
260            return True
261        return False
262
263    def CreateMountPoint(self, d):
264        if os.path.isdir(d['mountpoint']):
265            return True
266        try:
267            os.mkdir(d['mountpoint'])
268            os.chmod(d['mountpoint'], 0700)
269            return True
270        except Exception, err:
271            print "Exception creating mountpoint %s, err=%s"%(d['mountpoint'], err)
272        return False
273
274    def RemoveMountPoint(self, d):
275        try:
276            os.rmdir(d['mountpoint'])
277            return True
278        except Exception, err:
279            print "Exception removing mountpoint %s, err=%s"%(d['mountpoint'], err)
280        return False
281
282    def CreateLauncher(self, d):
283        txt="""#!/usr/bin/env xdg-open
284
285[Desktop Entry]
286Version=1.0
287Type=Link
288Name=%s
289Icon=drive-removable-media
290Dev=%s
291FSType=%s
292MountPoint=%s
293URL=%s
294X-multiseat-desktop=%s
295"""%(d['label'], d['device'], d['fstype'], d['mountpoint'], d['mountpoint'], d['seat_id'])
296        # create /home/user/Escritorio/XXXXXXXXXXX-x.desktop (serial,partnumber)
297        print txt
298        f=open(d['desktopfile'], 'w')
299        f.write(txt)
300        f.close()
301        os.chown(d['desktopfile'], d['useruid'], 0)
302        os.chmod(d['desktopfile'], 0700)
303
304    def getUserDesktop(self, username):
305        home=""
306        cmd="getent passwd %s"%username
307        p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
308        for line in p.stdout.readlines():
309            if "%s:"%username in line:
310                home=line.split(':')[5]
311        if os.path.isdir(home + "/Escritorio"):
312            return home + "/Escritorio"
313        elif os.path.isdir(home + "/Desktop"):
314            return home + "/Desktop"
315        # FIXME return ERROR???
316        return home
317
318    def getUserUID(self, username):
319        uid=0
320        cmd="getent passwd %s"%username
321        p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
322        for line in p.stdout.readlines():
323            if "%s:"%username in line:
324                uid=line.split(':')[2]
325        return uid
326
327    def run (self):
328        file(PID_FILE, 'w').write('%d'%self.pid)
329        try:
330            self.mainloop.run()
331        except KeyboardInterrupt:
332            self.quit()
333       
334    def quit(self, *args):
335        self.mainloop.quit()
336        sys.exit(0)
337
338def umount(dev, uid):
339    # FIXME if /dev/sdc2 is umounted try to search /dev/sdc? in /proc/mounts and umount it too
340    # print "umount() dev=%s uid=%s"%(dev, uid)
341    # read /proc/mounts searching device
342    if uid == '':
343        return "no-uid"
344    found=False
345    mountline=''
346    f=open('/proc/mounts', 'r')
347    for line in f.readlines():
348        if line.startswith(dev):
349            mountline=line
350            found=True
351    f.close()
352    if not found:
353        return "no-mounted"
354    # exec "getent passwd uid" and read username and home
355    username=''
356    home=''
357    cmd="getent passwd %s"%uid
358    p=Popen(cmd, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True)
359    for line in p.stdout.readlines():
360        if ":%s:"%uid in line:
361            username=line.split(':')[0]
362            home=line.split(':')[5]
363    if username == '' or home == '':
364        return "invalid-user"
365   
366    #if not "uid=%s"%uid in mountline:
367    #    return "not-yours"
368    # ntfs-3g don't put uid in mountline stat mountpoint
369    allowed=False
370    try:
371        if os.stat(mountline.split()[1]).st_uid == int(uid):
372            allowed=True
373    except Exception:
374        pass
375   
376    if not allowed:
377        return "not-yours"
378   
379    # get serial from mount point
380    #/media/serial-partnumber
381    serial_partnumber=mountline.split()[1].replace("/media/", '')
382    if serial_partnumber == "":
383        return "no-serial"
384   
385    # umount it
386    os.system("sync ; umount %s"%dev)
387   
388    # remove mount dir
389    try:
390        os.rmdir(mountline.split()[1])
391    except Exception:
392        pass
393   
394    # remove Desktop launcher
395    desktop=''
396    if os.path.isdir(home + "/Escritorio"):
397        desktop=home + "/Escritorio"
398    elif os.path.isdir(home + "/Desktop"):
399        desktop=home + "/Desktop"
400   
401    try:
402        os.unlink(desktop + "/%s.desktop"%serial_partnumber)
403    except Exception:
404        pass
405   
406    return "ok"
407
408
409
410if __name__ == '__main__':
411    if len(sys.argv) == 3:
412        #                              dev      user_uid
413        #/usr/sbin/multiseat-udisks /dev/sdc1    1000
414        print umount(sys.argv[1], sys.argv[2])
415        sys.exit(0)
416   
417    elif "--daemon" in sys.argv:
418        # run inhibitor daemon
419        app = MultiSeatDeviceManager()
420        app.run()
421        sys.exit(0)
422   
423    print """no options????
424
425try:
426    to run as inhibitor daemon:
427        multiseat-udisks --daemon
428
429    to umount a device (this is called by /sbin/umount.multiseat)
430        multiseat-udisks /dev/sdc1 1000
431
432"""
Note: See TracBrowser for help on using the browser.