モブ沢工房

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか3Dプリンタやら日曜大工等、色々。

dbusでマウントされているリムーバブルドライブを検出する

この記事はfc2から引っ越した記事です

ubuntu 14.04には先進的なバックアップアプリが標準装備のようですが…

実はおいらは普段より、自分のためだけに作った、MacのTimemachineを模した不完全なバックアップシステムを使っているのです。
中身はというとpythonスクリプトからrsyncをsubprocessで起動し、link-destオプションで、変更があればコピー、なければ「前回のバックアップ」からのhardlinkにするという、定番のアレです。バックアップした日の時間でサブディレクトリを掘り、そこにrsyncしていくというものです。
これをホーム全体等ではなく、あらかじめ設定した、「自分にとっての重要なファイル群」のディレクトリからのみ行います。

一連のバックアップ指定はすべてpythonで行い、対象が容量の少なめなUSBメモリだった場合とかは画像ファイルとかはバックアップしない等を指定(USBメモリ側に設定ファイルを置いておく方式)し、使う側はスクリプトをただ実行すればいいだけという、極めて柔軟かつ自動的でお手軽という…ただ、信頼性はあまりないかもですが。(自分としては役に立ったことはあれ、ひどい目に合ったことはないのですが、他人様に出せるような代物ではない)

それが今回、Ubuntu14.04に移行した結果、まともに動かなくなってしまいました。

何故か?と言うと、12.04と違い14.04ではUSB-HDDなどのリムーバブルデバイスが/mount/$USER/でユーザーごとにマウントされるようになっていたのです。いつ頃からこうなったのかは知りません。
まぁ、確かに安全なのでしょうが…

それで今まで/mount/以下にマウントポイントが出現することを予期していたため、/homeのHDDのデーターを/mount下、つまりSSDに日付ディレクトリを掘り、バックアップしてしまうという非常事態に…
いかんっ!SSDの寿命が減ってしまった!

まぁ、単に'/mount/%s' % os.environ['HOME']としてしまえば対応は済むのですが。

せっかくdbusが使えるようになったことですし。
あんないいかげんなコードのままではいかん。
勉強を兼ねて、ということで組んでみました。

以下、get_drive.pyという名前で使っています。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import dbus
import re
import sys
from BeautifulSoup import BeautifulSoup

DBUS='org.freedesktop.DBus'
UDISKS2='org.freedesktop.UDisks2'
UDISKS2_PATH='/org/freedesktop/UDisks2'

def conv_dbusarray_to_str(da):
ret=''
for cd in da:
c=chr(int(cd))
if int(cd)!=0:
ret+=c
return ret

class Drivefinder:

def __init__(self):
self.bus=dbus.SystemBus()
self.base_obj = self.bus.get_object(UDISKS2, UDISKS2_PATH)
self.objman = dbus.Interface(self.base_obj, '%s.ObjectManager' % DBUS)
self.usable_drives=[]

def do_introspect(self,obj):
xml=obj.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
return xml

def add_usable_drive(self,id):
self.usable_drives.append(id)

def get_removable_drives(self):
retlst=[]
blocks=self.bus.get_object(UDISKS2, "%s/drives" % UDISKS2_PATH)
xml=self.do_introspect(blocks)
#print xml
b=BeautifulSoup(xml)

# removable drive detection
for cn in b.findAll('node'):
if cn.has_key('name'):
id=self.examine_drive(cn['name'])
if id:
self.add_usable_drive(cn['name'])
# not adding id: because it is different from
# the Block-proxy's Drive property.


#print self.usable_drives

blocks=self.bus.get_object(UDISKS2, "%s/block_devices" % UDISKS2_PATH)
xml=self.do_introspect(blocks)
b=BeautifulSoup(xml)
ro_device=re.compile('(sd[a-z][0-9])$')
for cn in b.findAll('node'):
if cn.has_key('name'):
m=ro_device.search(cn['name'])
if m:
info=self.examine_device(cn['name'])
if info[0]!=None:
retlst.append(info)

return retlst

def examine_drive(self,name):
dp=self.bus.get_object(UDISKS2,"%s/drives/%s" % (UDISKS2_PATH,name))
fp=dbus.Interface(dp,dbus.PROPERTIES_IFACE) # propertyインターフェースの取得
try:
ifname="%s.Drive" % UDISKS2
id=fp.Get(ifname,"Id")
removable_flag=fp.Get(ifname,"Removable")
if removable_flag:
return str(id)
except dbus.exceptions.DBusException,e:
return None

return None

def examine_device(self,name):
dp=self.bus.get_object(UDISKS2,"%s/block_devices/%s" % (UDISKS2_PATH,name))
fp=dbus.Interface(dp,dbus.PROPERTIES_IFACE) # propertyインターフェースの取得

try:
mp=fp.Get("%s.Filesystem" % UDISKS2,"MountPoints")
try:
mountpoint=conv_dbusarray_to_str(mp[0])
d=fp.Get("%s.Block" % UDISKS2,"Drive")
this_drive=d.split('/')[-1]
if this_drive in self.usable_drives:
return (mountpoint,name,this_drive)
except IndexError:
# that device has not mounted yet
pass
except dbus.exceptions.DBusException,e:
pass

return (None,None,None)

def get_available_removable():
d=Drivefinder()
return d.get_removable_drives()


if __name__ == '__main__':
d=Drivefinder()
r=d.get_removable_drives()
print r



examine_driveでorg.freedesktop.UDisks2.DriveのRemovableプロパティを見て、リムーバブルストレージかどうかを判断。
そしてorg.freedesktop.UDisks2.block_devicesで得られる一覧より、デバイスファイル名を得てorg.freedesktop.UDisks2.Blockを作成。
それがマウントポイントを持っていればマウントされているとして合格とします。

モジュール関数「get_available_removable」で簡単に得られるようラップしてあり、返り値は
(マウントポイント、デバイスファイル名、ドライブ名)
のタプルを、リムーバブルストレージのマウントポイントぶん持った、リストです(ややこしい)

あ、一つのディスクで複数のマウントポイントがある可能性もあるのですが、これだと対応しきれてない気が?ううむ? まぁ習作ということで…っていきなりヘタレてどうするorz
↑よく考えたらデバイスファイル名を元にFilesystemを調査しているので問題ありませんでした。

このUDisks2を使ってさらに拡張していけば、デバイスの状況や種類に従い挙動を変えるのも自動化できそうですね。

そしてこれを応用すればAPTonCDのUDisks2化はもはや目前…!って誰も望んでないとは思いますがw単なる自己満足でw
ちなみに、APTonCDのサイト見たら2008年ごろから放置されてましたw

ubuntu 14.04には先進的なバックアップアプリが標準装備のようですが…

実はおいらは普段より、自分のためだけに作った、MacのTimemachineを模した不完全なバックアップシステムを使っているのです。
中身はというとpythonスクリプトからrsyncをsubprocessで起動し、link-destオプションで、変更があればコピー、なければ「前回のバックアップ」からのhardlinkにするという、定番のアレです。バックアップした日の時間でサブディレクトリを掘り、そこにrsyncしていくというものです。
これをホーム全体等ではなく、あらかじめ設定した、「自分にとっての重要なファイル群」のディレクトリからのみ行います。

一連のバックアップ指定はすべてpythonで行い、対象が容量の少なめなUSBメモリだった場合とかは画像ファイルとかはバックアップしない等を指定(USBメモリ側に設定ファイルを置いておく方式)し、使う側はスクリプトをただ実行すればいいだけという、極めて柔軟かつ自動的でお手軽という…ただ、信頼性はあまりないかもですが。(自分としては役に立ったことはあれ、ひどい目に合ったことはないのですが、他人様に出せるような代物ではない)

それが今回、Ubuntu14.04に移行した結果、まともに動かなくなってしまいました。

何故か?と言うと、12.04と違い14.04ではUSB-HDDなどのリムーバブルデバイスが/mount/$USER/でユーザーごとにマウントされるようになっていたのです。いつ頃からこうなったのかは知りません。
まぁ、確かに安全なのでしょうが…

それで今まで/mount/以下にマウントポイントが出現することを予期していたため、/homeのHDDのデーターを/mount下、つまりSSDに日付ディレクトリを掘り、バックアップしてしまうという非常事態に…
いかんっ!SSDの寿命が減ってしまった!

まぁ、単に'/mount/%s' % os.environ['HOME']としてしまえば対応は済むのですが。

せっかくdbusが使えるようになったことですし。
あんないいかげんなコードのままではいかん。
勉強を兼ねて、ということで組んでみました。

以下、get_drive.pyという名前で使っています。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import dbus
import re
import sys
from BeautifulSoup import BeautifulSoup

DBUS='org.freedesktop.DBus'
UDISKS2='org.freedesktop.UDisks2'
UDISKS2_PATH='/org/freedesktop/UDisks2'

def conv_dbusarray_to_str(da):
ret=''
for cd in da:
c=chr(int(cd))
if int(cd)!=0:
ret+=c
return ret

class Drivefinder:

def __init__(self):
self.bus=dbus.SystemBus()
self.base_obj = self.bus.get_object(UDISKS2, UDISKS2_PATH)
self.objman = dbus.Interface(self.base_obj, '%s.ObjectManager' % DBUS)
self.usable_drives=[]

def do_introspect(self,obj):
xml=obj.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
return xml

def add_usable_drive(self,id):
self.usable_drives.append(id)

def get_removable_drives(self):
retlst=[]
blocks=self.bus.get_object(UDISKS2, "%s/drives" % UDISKS2_PATH)
xml=self.do_introspect(blocks)
#print xml
b=BeautifulSoup(xml)

# removable drive detection
for cn in b.findAll('node'):
if cn.has_key('name'):
id=self.examine_drive(cn['name'])
if id:
self.add_usable_drive(cn['name'])
# not adding id: because it is different from
# the Block-proxy's Drive property.


#print self.usable_drives

blocks=self.bus.get_object(UDISKS2, "%s/block_devices" % UDISKS2_PATH)
xml=self.do_introspect(blocks)
b=BeautifulSoup(xml)
ro_device=re.compile('(sd[a-z][0-9])$')
for cn in b.findAll('node'):
if cn.has_key('name'):
m=ro_device.search(cn['name'])
if m:
info=self.examine_device(cn['name'])
if info[0]!=None:
retlst.append(info)

return retlst

def examine_drive(self,name):
dp=self.bus.get_object(UDISKS2,"%s/drives/%s" % (UDISKS2_PATH,name))
fp=dbus.Interface(dp,dbus.PROPERTIES_IFACE) # propertyインターフェースの取得
try:
ifname="%s.Drive" % UDISKS2
id=fp.Get(ifname,"Id")
removable_flag=fp.Get(ifname,"Removable")
if removable_flag:
return str(id)
except dbus.exceptions.DBusException,e:
return None

return None

def examine_device(self,name):
dp=self.bus.get_object(UDISKS2,"%s/block_devices/%s" % (UDISKS2_PATH,name))
fp=dbus.Interface(dp,dbus.PROPERTIES_IFACE) # propertyインターフェースの取得

try:
mp=fp.Get("%s.Filesystem" % UDISKS2,"MountPoints")
try:
mountpoint=conv_dbusarray_to_str(mp[0])
d=fp.Get("%s.Block" % UDISKS2,"Drive")
this_drive=d.split('/')[-1]
if this_drive in self.usable_drives:
return (mountpoint,name,this_drive)
except IndexError:
# that device has not mounted yet
pass
except dbus.exceptions.DBusException,e:
pass

return (None,None,None)

def get_available_removable():
d=Drivefinder()
return d.get_removable_drives()


if __name__ == '__main__':
d=Drivefinder()
r=d.get_removable_drives()
print r



examine_driveでorg.freedesktop.UDisks2.DriveのRemovableプロパティを見て、リムーバブルストレージかどうかを判断。
そしてorg.freedesktop.UDisks2.block_devicesで得られる一覧より、デバイスファイル名を得てorg.freedesktop.UDisks2.Blockを作成。
それがマウントポイントを持っていればマウントされているとして合格とします。

モジュール関数「get_available_removable」で簡単に得られるようラップしてあり、返り値は
(マウントポイント、デバイスファイル名、ドライブ名)
のタプルを、リムーバブルストレージのマウントポイントぶん持った、リストです(ややこしい)

あ、一つのディスクで複数のマウントポイントがある可能性もあるのですが、これだと対応しきれてない気が?ううむ? まぁ習作ということで…っていきなりヘタレてどうするorz
↑よく考えたらデバイスファイル名を元にFilesystemを調査しているので問題ありませんでした。

このUDisks2を使ってさらに拡張していけば、デバイスの状況や種類に従い挙動を変えるのも自動化できそうですね。

そしてこれを応用すればAPTonCDのUDisks2化はもはや目前…!って誰も望んでないとは思いますがw単なる自己満足でw
ちなみに、APTonCDのサイト見たら2008年ごろから放置されてましたw