493 lines
15 KiB
Python
493 lines
15 KiB
Python
#!/usr/bin/python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import glob
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
|
||
FSTAB = '/etc/fstab'
|
||
|
||
"""
|
||
Quick list is used for improving efficiency of the script.
|
||
Stores device names and uuids pairs for partitions so prevents multiple
|
||
communication with subprocesses.
|
||
"""
|
||
quickList = {}
|
||
|
||
FAIL_FSTAB = {
|
||
"en": "Unable to read '%s'.",
|
||
"tr": "'%s' okunamadı.",
|
||
"fr": "Impossible de lire '%s' .",
|
||
"es": "No posible leer '%s'.",
|
||
"de": "'%s' konnte nicht gelesen werden.",
|
||
"nl": "Kan '%s' niet lezen.",
|
||
}
|
||
|
||
FAIL_PATH = {
|
||
"en": "'%s' is not a valid mount point.",
|
||
"tr": "'%s' geçerli bir bağlama noktası değil.",
|
||
"fr": "'%s' n'est pas un point de montage valide.",
|
||
"es": "'%s' no es un punto de montaje válido.",
|
||
"de": "'%s' ist kein gültiger Mount-Punkt.",
|
||
"nl": "'%s' is geen geldig aankoppelpunt.",
|
||
}
|
||
|
||
FAIL_PATH_ALREADY_EXIST = {
|
||
"en": "'%s' is already being used by an entry.",
|
||
"tr": "'%s' zaten bir kayıt tarafından kullanılıyor.",
|
||
"fr": "'%s' est déjà utilisé par une entrée.",
|
||
"es": "Ya '%s' siendo utilizado por una entrada.",
|
||
"de": "'%s' wird bereits durch einen Eintrag verwendet.",
|
||
"nl": "'%s' bruges allerede af en post.",
|
||
}
|
||
|
||
FAIL_ENTRY = {
|
||
"en": "Device '%s' not found in entry list.",
|
||
"tr": "'%s' aygıtı listede bulunamadı.",
|
||
"fr": "Le matériel '%s' n'a pas été trouvé dans la liste.",
|
||
"es": "Dispositivo '%s' no encontrado en lista de entrada.",
|
||
"de": "Gerät '%s' ist nicht in der Liste eingetragen.",
|
||
"nl": "Apparaat '%s' is niet in invoerlijst gevonden.",
|
||
}
|
||
|
||
FAIL_ROOT = {
|
||
"en": "'%s' is mounted to root directory, operation cancelled.",
|
||
"tr": "'%s' diski kök dizine bağlı, işlem iptal edildi.",
|
||
"fr": "'%s' est monté sur le répertoire racine, opération annulée.",
|
||
"es": "'%s' está montado com root, operación cancelada.",
|
||
"de": "'%s' ist als Root gemounted, Vorgang abgebrochen.",
|
||
"nl": "'%s' is in root-map aangekoppeld, bewerking geannuleerd.",
|
||
}
|
||
|
||
FAIL_MOUNTED = {
|
||
"en": "'%s' is mounted to another directory, operation cancelled.",
|
||
"tr": "'%s' başka bir dizine bağlı, işlem iptal edildi.",
|
||
"fr": "'%s' est monté sur un autre répertoire, opération annulée.",
|
||
"es": "'%s' está montado en otro directorio, operación cancelada.",
|
||
"de": "'%s' ist schon auf einem anderen Mount-Punkt gemounted, Vorgang abgebrochen.",
|
||
"nl": "'%s' is in een andere map aangekoppeld, bewerking geannuleerd.",
|
||
}
|
||
|
||
FAIL_OPERATION = {
|
||
"en": "Operation failed:\n\n %s",
|
||
"tr": "İşlem başarısız:\n\n %s",
|
||
"fr": "L'opération a échoué : \n\n %s",
|
||
"es": "Operación fallada:\n\n %s",
|
||
"de": "Vorgang fehlerhaft:\n\n %s",
|
||
"nl": "Mislukte bewerking:\n\n %s",
|
||
}
|
||
|
||
class DMException(Exception):
|
||
pass
|
||
|
||
def parseFstab(fstab):
|
||
if not os.access(fstab, os.R_OK):
|
||
raise DMException, _(FAIL_FSTAB) % fstab
|
||
entries = []
|
||
for line in open(fstab):
|
||
line = line.strip()
|
||
# Check len(line) for empty lines.
|
||
if line.startswith('#') or len(line) == 0:
|
||
continue
|
||
line = line.replace('\t', ' ').split()
|
||
# Replace UUID value with device name after reading it from fstab
|
||
if line[0].startswith('UUID='):
|
||
line[0] = getPartitionNameByUUID(line[0])
|
||
entries.append(line)
|
||
elif line[0].startswith('LABEL='):
|
||
line[0] = getDeviceByLabel(line[0].replace('LABEL=', ''))
|
||
entries.append(line)
|
||
elif line[0].startswith('/dev/'):
|
||
entries.append(line)
|
||
return entries
|
||
|
||
def createPath(device, path):
|
||
real_path = path
|
||
if not os.path.exists(path) and os.path.exists(os.path.dirname(path)):
|
||
# Mount point does not exist, but parent directory does.
|
||
path = os.path.dirname(path)
|
||
if not os.path.ismount(path) and not os.path.islink(path) and os.path.isdir(path):
|
||
os.mkdir(real_path, 0755)
|
||
return True
|
||
else:
|
||
if os.path.ismount(path):
|
||
# Path is already mounted, allow user to use that mount point if it's already mounted
|
||
for _device, _path in getMounted():
|
||
if device == _device and _path == path:
|
||
return True
|
||
else:
|
||
if not os.path.islink(path) and os.path.isdir(path) and os.listdir(path) == []:
|
||
return True
|
||
return False
|
||
|
||
def getMounted():
|
||
parts = []
|
||
for line in open('/proc/mounts'):
|
||
if line.startswith('/dev/'):
|
||
device, path, other = line.split(" ", 2)
|
||
parts.append((device, path, ))
|
||
return parts
|
||
|
||
def getFSType(device):
|
||
if device.startswith('UUID='):
|
||
device = getPartitionNameByUUID(device)
|
||
cmd = "/sbin/blkid -s TYPE -o value %s" % device
|
||
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
|
||
return proc.communicate()[0].strip()
|
||
|
||
def getUUID(part):
|
||
"""
|
||
Finds UUID for the given partition.
|
||
"""
|
||
global quickList
|
||
if len(quickList) == 0:
|
||
fillQuickList()
|
||
return quickList[part]
|
||
|
||
def getPartitionNameByUUID(part):
|
||
"""
|
||
Finds name of the partition for the given UUId.
|
||
"""
|
||
global quickList
|
||
part = part.replace('UUID=', '')
|
||
if len(quickList) == 0:
|
||
fillQuickList()
|
||
for devName, uuid in quickList.items():
|
||
if uuid == part:
|
||
return devName
|
||
return ''
|
||
|
||
def fillQuickList():
|
||
"""
|
||
Fills the quick list.
|
||
"""
|
||
global quickList
|
||
cmd = "/sbin/blkid"
|
||
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
|
||
for line in proc.stdout:
|
||
line = line.replace(':', '').strip()
|
||
propList = line.split()
|
||
devName = label = uuid = fsType = ''
|
||
devName = propList[0]
|
||
for property in propList:
|
||
if property.startswith('UUID'):
|
||
uuid = property.replace('UUID=', '').replace('"', '')
|
||
quickList[devName] = uuid
|
||
|
||
def runCommand(cmd):
|
||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
err = proc.communicate()[1].strip()
|
||
if len(err):
|
||
fail(_(FAIL_OPERATION) % err)
|
||
|
||
# Disk.Manager methods
|
||
|
||
def isMounted(device):
|
||
"""
|
||
Searches the given partition in mounted devices. If partition is
|
||
mounted, returns mount point else returns none.
|
||
"""
|
||
for _device, _path in getMounted():
|
||
if device == _device:
|
||
return _path
|
||
return ''
|
||
|
||
def getDevices():
|
||
from pardus.diskutils import EDD
|
||
return EDD().blockDevices()
|
||
|
||
def getDeviceByLabel(label):
|
||
root = '/dev/disk/by-label'
|
||
path = os.path.join(root, label)
|
||
if os.access(path, os.R_OK):
|
||
return os.path.realpath(os.path.join(root, os.readlink(path)))
|
||
else:
|
||
return ''
|
||
|
||
def getDeviceParts(device):
|
||
if not os.path.exists(device):
|
||
return []
|
||
parts = []
|
||
for part in glob.glob("%s*" % device):
|
||
if not part == device and not getFSType(part) == "":
|
||
parts.append(part)
|
||
return parts
|
||
|
||
def getLabel(device):
|
||
"""
|
||
Finds label for the given partition.
|
||
"""
|
||
cmd = "/sbin/blkid -s LABEL -o value %s" % device
|
||
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
|
||
return proc.communicate()[0].strip()
|
||
|
||
def getPath(device):
|
||
"""
|
||
Gets partition name and determine a path for that partition.
|
||
"""
|
||
# If there is a entry record for this partition in fstab
|
||
# use path in there.
|
||
if device in listEntries():
|
||
path_, fsType_, options_ = getEntry(device)
|
||
return path_
|
||
path = '/media/'
|
||
label = getLabel(device)
|
||
# There may be partitions without a label
|
||
if not label:
|
||
if not os.path.exists(path+'disk'):
|
||
path = path+'disk'
|
||
elif not os.path.ismount(path+'disk'):
|
||
path = path+'disk'
|
||
else:
|
||
for i in range(1, len(getMounted())):
|
||
if not os.path.exists(path+'disk-'+str(i)):
|
||
path = path+'disk-'+str(i)
|
||
break
|
||
elif not os.path.ismount(path+'disk-'+str(i)):
|
||
path = path+'disk-'+str(i)
|
||
break
|
||
# Labels may be same
|
||
else:
|
||
if not os.path.exists(path+label):
|
||
path = path+label
|
||
elif not os.path.ismount(path+label):
|
||
path = path+label
|
||
else:
|
||
for i in range(1, len(getMounted())):
|
||
if not os.path.exists(path+label+'-'+str(i)):
|
||
path = path+label+'-'+str(i)
|
||
break
|
||
elif not os.path.ismount(path+label+'-'+str(i)):
|
||
path = path+label+'-'+str(i)
|
||
break
|
||
return path
|
||
|
||
def mount(device, path):
|
||
if not path:
|
||
path = getPath(device)
|
||
if not createPath(device, path):
|
||
# Can't create new path
|
||
fail(_(FAIL_PATH) % path)
|
||
elif device in [x[0] for x in getMounted()]:
|
||
# Device is mounted
|
||
fail(_(FAIL_MOUNTED) % device)
|
||
runCommand(['/bin/mount', device, path])
|
||
|
||
def umount(device):
|
||
for dev, path in getMounted():
|
||
if dev == device and path == "/":
|
||
fail(_(FAIL_ROOT) % device)
|
||
runCommand(['/bin/umount', device])
|
||
|
||
def listEntries():
|
||
try:
|
||
devices = [x[0] for x in parseFstab(FSTAB)]
|
||
return devices
|
||
except DMException:
|
||
return []
|
||
|
||
def listPaths():
|
||
"""
|
||
Return paths of the entries in fstab.
|
||
"""
|
||
try:
|
||
paths = [x[1] for x in parseFstab(FSTAB)]
|
||
return paths
|
||
except DMException:
|
||
return []
|
||
|
||
def isLabelRecord(device):
|
||
for line in open(FSTAB):
|
||
line = line.strip().replace('\t', ' ').split()
|
||
if line:
|
||
if line[0] == 'LABEL=' + getLabel(device):
|
||
return True
|
||
|
||
def addEntry(device, path, fsType, options):
|
||
path_own = False
|
||
if device in listEntries():
|
||
old_path, old_fsType, old_options = getEntry(device)
|
||
# Do not change root
|
||
if old_path == "/" and not old_path == path:
|
||
fail(_(FAIL_ROOT) % device)
|
||
# Who has that mount point? me?
|
||
if old_path == path:
|
||
path_own = True
|
||
else:
|
||
# If the path value already exist in fstab, cut the operation
|
||
if path in listPaths():
|
||
fail(_(FAIL_PATH_ALREADY_EXIST) % path)
|
||
old_path = None
|
||
if not createPath(device, path):
|
||
# Can't create new path
|
||
fail(_(FAIL_PATH) % path)
|
||
#elif device in [x[0] for x in getMounted()]:
|
||
# Device is mounted
|
||
#fail(_(FAIL_MOUNTED) % device)
|
||
partText = ''
|
||
# Before removing the entry check if it is a label record or uuid record
|
||
# Remove previous one to prevent duplicates
|
||
if old_path:
|
||
if isLabelRecord(device):
|
||
partText = 'LABEL='+getLabel(device)
|
||
else:
|
||
partText = 'UUID='+getUUID(device)
|
||
else:
|
||
partText = 'UUID='+getUUID(device)
|
||
# Add new entry
|
||
_options = []
|
||
for key, value in options.iteritems():
|
||
if value:
|
||
_options.append('%s=%s' % (key, value))
|
||
else:
|
||
_options.append(key)
|
||
if not file(FSTAB).read()[-1] == '\n':
|
||
file(FSTAB, 'a').write('\n')
|
||
_options = ','.join(_options)
|
||
|
||
addit = True
|
||
# If the partition is not already mounted the given path and,
|
||
# if the partition is not mounted anywhere, try to create a path
|
||
# and mount there.
|
||
if not path_own:
|
||
if not createPath(device, path):
|
||
addit = False
|
||
# Can't create new path
|
||
fail(_(FAIL_PATH) % path)
|
||
if not device in [x[0] for x in getMounted()]:
|
||
# Mount device
|
||
try:
|
||
mount(device, path)
|
||
except:
|
||
addit = False
|
||
raise
|
||
if addit:
|
||
# try to remove old entry before
|
||
if old_path:
|
||
removeEntry(device, silent=True)
|
||
if _options:
|
||
file(FSTAB, 'a').write('%s %s %s %s 0 0\n' % (partText, path, fsType, _options))
|
||
else:
|
||
file(FSTAB, 'a').write('%s %s %s defaults 0 0\n' % (partText, path, fsType))
|
||
# Notify clients
|
||
notify("Disk.Manager", "changed", ())
|
||
|
||
def getEntry(device):
|
||
entries = parseFstab(FSTAB)
|
||
for entry in entries:
|
||
if entry[0] == device:
|
||
_path, _fsType, _options, _dump, _pass = entry[1:]
|
||
options = {}
|
||
for part in _options.split(','):
|
||
if "=" in part:
|
||
key, value = part.split('=', 1)
|
||
options[key] = value
|
||
else:
|
||
options[part] = ""
|
||
return _path, _fsType, options
|
||
fail(_(FAIL_ENTRY) % device)
|
||
|
||
def removeEntry(device, silent=False):
|
||
if isMounted(device) == '/':
|
||
fail(_(FAIL_ROOT) % device)
|
||
if device not in listEntries():
|
||
return
|
||
newlines = []
|
||
for line in open(FSTAB):
|
||
line = line.strip()
|
||
if not len(line) == 0 and not (line.replace('\t', ' ').split()[0] == 'UUID='+getUUID(device) or line.replace('\t', ' ').split()[0] == 'LABEL='+getLabel(device) or line.replace('\t', ' ').split()[0] == device):
|
||
newlines.append(line)
|
||
file(FSTAB, 'w').write('\n'.join(newlines))
|
||
if not file(FSTAB).read()[-1] == '\n':
|
||
file(FSTAB, 'a').write('\n')
|
||
# Notify clients
|
||
if not silent:
|
||
notify("Disk.Manager", "changed", ())
|
||
|
||
|
||
import errno
|
||
from time import sleep
|
||
from fcntl import ioctl
|
||
|
||
# Path to sync executable
|
||
PATH_SYNC = '/bin/sync'
|
||
|
||
# Emulate required asm-generic/ioctl.h macros
|
||
_IOC_NRBITS = 8
|
||
_IOC_TYPEBITS = 8
|
||
_IOC_SIZEBITS = 14
|
||
_IOC_DIRBITS = 2
|
||
|
||
_IOC_NRMASK = ((1 << _IOC_NRBITS) - 1)
|
||
_IOC_TYPEMASK = ((1 << _IOC_TYPEBITS) - 1)
|
||
_IOC_SIZEMASK = ((1 << _IOC_SIZEBITS) - 1)
|
||
_IOC_DIRMASK = ((1 << _IOC_DIRBITS) - 1)
|
||
|
||
_IOC_NRSHIFT = 0
|
||
_IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS)
|
||
_IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS)
|
||
_IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS)
|
||
|
||
# Direction bits.
|
||
_IOC_NONE = 0
|
||
_IOC_WRITE = 1
|
||
_IOC_READ = 2
|
||
|
||
def _IOC(dir,type,nr,size):
|
||
return (((dir) << _IOC_DIRSHIFT) | \
|
||
(type << _IOC_TYPESHIFT) | \
|
||
((nr) << _IOC_NRSHIFT) | \
|
||
((size) << _IOC_SIZESHIFT))
|
||
|
||
def _IO(type, nr):
|
||
"""Note: type is specified in hex and nr in decimal."""
|
||
return _IOC(_IOC_NONE,(type),(nr),0)
|
||
|
||
def BLKRRPART():
|
||
"""Returns ioctl number for re-reading partition table."""
|
||
# Kernels >2.6.17 have BLKRRPART defined in include/linux/fs.h.
|
||
return _IO(0x12, 95)
|
||
# -------------------------------------------
|
||
|
||
def refreshPartitionTable(device):
|
||
"""Re-Read partition table on device."""
|
||
|
||
try:
|
||
fd = os.open(device, os.O_RDONLY)
|
||
except EnvironmentError, (error, strerror):
|
||
print 'Could not open device %s. Reason: %s.'%(device, strerror)
|
||
sys.exit(-1)
|
||
|
||
# Sync and wait for Sync to complete
|
||
os.system(PATH_SYNC)
|
||
sleep(2)
|
||
|
||
# Call required ioctl to re-read partition table
|
||
try:
|
||
ioctl(fd, BLKRRPART())
|
||
except EnvironmentError, (error, message):
|
||
# Attempt ioctl call twice in case an older kernel (1.2.x) is being used
|
||
os.system(PATH_SYNC)
|
||
sleep(2)
|
||
|
||
try:
|
||
ioctl(fd, BLKRRPART())
|
||
except EnvironmentError, (error, strerror):
|
||
print 'IOCTL Error: %s for device %s.'%(strerror, device)
|
||
sys.exit(-1)
|
||
|
||
print 'Successfully re-read partition table on device %s.'%(device)
|
||
# Sync file buffers
|
||
os.fsync(fd)
|
||
os.close(fd)
|
||
|
||
# Final sync
|
||
print "Syncing %s ... " % (device),
|
||
os.system(PATH_SYNC)
|
||
sleep(4) # for sync()
|
||
print "Done."
|
||
|
||
|
||
|