update-fstab.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # /etc/fstab updater/generator
  5. # Copyright (C) 2017, Suleyman POYRAZ (AquilaNipalensis)
  6. #
  7. # This program is free software; you can redistribute it and/or modify it under
  8. # the terms of the GNU General Public License as published by the Free
  9. # Software Foundation; either version 2 of the License, or (at your option)
  10. # any later version.
  11. #
  12. import os
  13. import sys
  14. import glob
  15. import parted
  16. # Default options
  17. default_options = {
  18. "vfat": ("quiet", "shortname=mixed", "dmask=007", "fmask=117", "utf8", "gid=6"),
  19. "ext3": ("noatime", ),
  20. "ext2": ("noatime", ),
  21. "ntfs-3g": ("dmask=007", "fmask=117", "gid=6"),
  22. "reiserfs": ("noatime", ),
  23. "xfs": ("noatime", ),
  24. "defaults": ("defaults", ),
  25. }
  26. default_mount_dir = "/mnt"
  27. excluded_file_systems = ("proc", "tmpfs", "sysfs", "linux-swap", "swap", "nfs", "nfs4", "cifs")
  28. sulin_labels = ("SULIN_ROOT", "SULIN_HOME", "SULIN_SWAP")
  29. # Utility functions
  30. def blockDevices():
  31. devices = []
  32. for dev_type in ["hd*", "sd*"]:
  33. sysfs_devs = glob.glob("/sys/block/" + dev_type)
  34. for sysfs_dev in sysfs_devs:
  35. if not int(open(sysfs_dev + "/removable").read().strip()):
  36. devlink = os.readlink(sysfs_dev + "/device")
  37. devlink = os.path.realpath(os.path.join(sysfs_dev, "device", devlink))
  38. if (not "/usb" in devlink) and (not "/fw-host" in devlink):
  39. devices.append("/dev/" + os.path.basename(sysfs_dev))
  40. devices.sort()
  41. return devices
  42. def blockPartitions(path):
  43. device = parted.Device(path)
  44. try:
  45. disk = parted.Disk(device)
  46. except:
  47. # FIXME: This is suck! Must be changed!
  48. disk = parted.freshDisk(device, parted.diskType['msdos'])
  49. partition = disk.getFirstPartition()
  50. while partition:
  51. if partition.fileSystem and partition.fileSystem.type != "linux-swap":
  52. yield partition.path, partition.fileSystem.type
  53. partition = partition.nextPartition()
  54. def blockNameByLabel(label):
  55. path = os.path.join("/dev/disk/by-label", label)
  56. if os.path.islink(path):
  57. return "/dev/%s" % os.readlink(path)[6:]
  58. else:
  59. return None
  60. def blockNameByUuid(uuid):
  61. path = os.path.join("/dev/disk/by-uuid", uuid)
  62. if os.path.islink(path):
  63. return "/dev/%s" % os.readlink(path)[6:]
  64. else:
  65. return None
  66. def getLocale():
  67. try:
  68. for line in file("/etc/env.d/03locale"):
  69. if "LC_ALL" in line:
  70. return line[7:].strip()
  71. except:
  72. pass
  73. return "tr_TR.UTF-8"
  74. # Fstab classes
  75. class FstabEntry:
  76. def __init__(self, line=None):
  77. defaults = [ None, None, "auto", "defaults", 0, 0 ]
  78. args = []
  79. if line:
  80. args = line.split()
  81. args = args[:len(args)] + defaults[len(args):]
  82. self.device_node = args[0]
  83. self.mount_point = args[1]
  84. self.file_system = args[2]
  85. self.options = args[3]
  86. self.dump_freq = args[4]
  87. self.pass_no = args[5]
  88. def __str__(self):
  89. return "%-23s %-23s %-7s %-15s %s %s" % (
  90. self.device_node,
  91. self.mount_point,
  92. self.file_system,
  93. self.options,
  94. self.dump_freq,
  95. self.pass_no
  96. )
  97. class Fstab:
  98. comment = """# See the manpage fstab(5) for more information.
  99. #
  100. # <fs> <mountpoint> <type> <opts> <dump/pass>
  101. """
  102. def __init__(self, path=None):
  103. if not path:
  104. path = "/etc/fstab"
  105. self.path = path
  106. self.entries = []
  107. self.partitions = None
  108. self.labels = {}
  109. self.uuids = {}
  110. for line in file(path):
  111. if line.strip() != "" and not line.startswith('#'):
  112. self.entries.append(FstabEntry(line))
  113. def __str__(self):
  114. return "\n".join(map(str, self.entries))
  115. def scan(self):
  116. self.partitions = {}
  117. for device in blockDevices():
  118. for partition, fstype in blockPartitions(device):
  119. self.partitions[partition] = fstype, device
  120. if os.path.exists("/dev/disk/by-label"):
  121. for label in os.listdir("/dev/disk/by-label/"):
  122. self.labels[blockNameByLabel(label)] = label
  123. elif os.path.exists("/dev/disk/by-uuid"):
  124. for uuid in os.listdir("/dev/disk/by-uuid/"):
  125. self.uuids[blockNameByUuid(uuid)] = uuid
  126. def write(self, path=None):
  127. if not path:
  128. path = self.path
  129. # Make sure mount points exist
  130. for entry in self.entries:
  131. if entry.mount_point != "none" and not os.path.exists(entry.mount_point):
  132. os.makedirs(entry.mount_point)
  133. f = open(path, "w")
  134. f.write(self.comment)
  135. f.write(str(self))
  136. f.write("\n")
  137. f.close()
  138. def removeEntry(self, device_node):
  139. for i, entry in enumerate(self.entries):
  140. if entry.device_node == device_node and entry.mount_point != "/":
  141. del self.entries[i]
  142. def addEntry(self, device_node, mount_point=None):
  143. if not self.partitions:
  144. self.scan()
  145. if not mount_point:
  146. mount_point = os.path.join(default_mount_dir, os.path.basename(device_node))
  147. file_system = self.partitions.get(device_node)[0]
  148. if file_system in ("fat16", "fat32"):
  149. file_system = "vfat"
  150. if file_system == "ntfs":
  151. file_system = "ntfs-3g"
  152. if file_system == "hfs+":
  153. file_system = "hfsplus"
  154. options = default_options.get(file_system, None)
  155. if not options:
  156. options = default_options.get("defaults")
  157. entry = FstabEntry()
  158. entry.device_node = device_node
  159. entry.mount_point = mount_point
  160. entry.file_system = file_system
  161. entry.options = ",".join(options)
  162. if file_system == "ntfs-3g":
  163. entry.options += ",locale=%s" % getLocale()
  164. self.entries.append(entry)
  165. return entry
  166. def refresh(self):
  167. if not self.partitions:
  168. self.scan()
  169. # Carefully remove non existing partitions
  170. removal = []
  171. for i, entry in enumerate(self.entries):
  172. node = entry.device_node
  173. if entry.mount_point == "/":
  174. # Root partition is never removed
  175. continue
  176. elif not entry.mount_point.startswith("/mnt"):
  177. # Only remove partitions that were added in /mnt
  178. continue
  179. elif entry.file_system in excluded_file_systems:
  180. # Virtual file systems are never removed
  181. continue
  182. elif node.startswith("UUID="):
  183. uuid = node.split("=", 1)[1]
  184. if blockNameByUuid(uuid) not in self.partitions:
  185. removal.append(node)
  186. elif node.startswith("LABEL="):
  187. label = node.split("=", 1)[1]
  188. if label in sulin_labels:
  189. # Labelled Pardus system partitions are never removed
  190. continue
  191. if blockNameByLabel(label) not in self.partitions:
  192. removal.append(node)
  193. elif node.startswith("UUID="):
  194. uuid = node.split("=", 1)[1]
  195. if blockNameByUuid(uuid) not in self.partitions:
  196. removal.append(node)
  197. else:
  198. if node not in self.partitions:
  199. removal.append(node)
  200. list(map(self.removeEntry, removal))
  201. # Append all other existing non-removable partitions
  202. mounted = set([x.device_node for x in self.entries])
  203. for part in self.partitions:
  204. if not part in mounted:
  205. if part in self.labels:
  206. if "LABEL=%s" % self.labels[part] in mounted:
  207. continue
  208. elif part in self.uuids:
  209. if "UUID=%s" % self.uuids[part] in mounted:
  210. continue
  211. self.addEntry(part)
  212. # Command line driver
  213. def refresh_fstab(path=None, debug=False):
  214. f = Fstab(path)
  215. if debug:
  216. print("Fstab file:", f.path)
  217. print("--- Current table ---")
  218. print(f)
  219. f.refresh()
  220. if debug:
  221. print("--- Refreshed table ---")
  222. print(f)
  223. else:
  224. f.write()
  225. def main(args):
  226. path = None
  227. debug = False
  228. if "--debug" in args:
  229. args.remove("--debug")
  230. debug = True
  231. if len(args) > 0:
  232. path = args[0]
  233. refresh_fstab(path, debug)
  234. if __name__ == "__main__":
  235. main(sys.argv[1:])