68c666c036144a4e78a6aa8380b4e7a68eec1ed5
[openblackhole/openblackhole-enigma2.git] / lib / python / Components / Harddisk.py
1 from os import system, listdir, statvfs, popen, makedirs, stat, major, minor, path, access
2 from Tools.Directories import SCOPE_HDD, resolveFilename, pathExists
3 from Tools.CList import CList
4 from SystemInfo import SystemInfo
5 import time
6 from Components.Console import Console
7
8 def readFile(filename):
9         file = open(filename)
10         data = file.read().strip()
11         file.close()
12         return data
13
14 def getProcMounts():
15         try:
16                 mounts = open("/proc/mounts")
17         except IOError, ex:
18                 print "[Harddisk] Failed to open /proc/mounts", ex 
19                 return []
20         return [line.strip().split(' ') for line in mounts]
21
22 class Harddisk:
23         DEVTYPE_UDEV = 0
24         DEVTYPE_DEVFS = 1
25
26         def __init__(self, device):
27                 self.device = device
28
29                 if access("/dev/.udev", 0):
30                         self.type = self.DEVTYPE_UDEV
31                 elif access("/dev/.devfsd", 0):
32                         self.type = self.DEVTYPE_DEVFS
33                 else:
34                         print "Unable to determine structure of /dev"
35
36                 self.max_idle_time = 0
37                 self.idle_running = False
38                 self.timer = None
39
40                 self.dev_path = ''
41                 self.disk_path = ''
42                 self.phys_path = path.realpath(self.sysfsPath('device'))
43
44                 if self.type == self.DEVTYPE_UDEV:
45                         self.dev_path = '/dev/' + self.device
46                         self.disk_path = self.dev_path
47
48                 elif self.type == self.DEVTYPE_DEVFS:
49                         tmp = readFile(self.sysfsPath('dev')).split(':')
50                         s_major = int(tmp[0])
51                         s_minor = int(tmp[1])
52                         for disc in listdir("/dev/discs"):
53                                 dev_path = path.realpath('/dev/discs/' + disc)
54                                 disk_path = dev_path + '/disc'
55                                 try:
56                                         rdev = stat(disk_path).st_rdev
57                                 except OSError:
58                                         continue
59                                 if s_major == major(rdev) and s_minor == minor(rdev):
60                                         self.dev_path = dev_path
61                                         self.disk_path = disk_path
62                                         break
63
64                 print "new Harddisk", self.device, '->', self.dev_path, '->', self.disk_path
65                 self.startIdle()
66
67         def __lt__(self, ob):
68                 return self.device < ob.device
69
70         def partitionPath(self, n):
71                 if self.type == self.DEVTYPE_UDEV:
72                         return self.dev_path + n
73                 elif self.type == self.DEVTYPE_DEVFS:
74                         return self.dev_path + '/part' + n
75
76         def sysfsPath(self, filename):
77                 return path.realpath('/sys/block/' + self.device + '/' + filename)
78
79         def stop(self):
80                 if self.timer:
81                         self.timer.stop()
82                         self.timer.callback.remove(self.runIdle)
83
84         def bus(self):
85                 # CF (7025 specific)
86                 if self.type == self.DEVTYPE_UDEV:
87                         ide_cf = False  # FIXME
88                 elif self.type == self.DEVTYPE_DEVFS:
89                         ide_cf = self.device[:2] == "hd" and "host0" not in self.dev_path
90
91                 internal = "pci" in self.phys_path
92
93                 if ide_cf:
94                         ret = "External (CF)"
95                 elif internal:
96                         ret = "Internal"
97                 else:
98                         ret = "External"
99                 return ret
100
101         def diskSize(self):
102                 line = readFile(self.sysfsPath('size'))
103                 try:
104                         cap = int(line)
105                 except:
106                         return 0;
107                 return cap / 1000 * 512 / 1000
108
109         def capacity(self):
110                 cap = self.diskSize()
111                 if cap == 0:
112                         return ""
113                 return "%d.%03d GB" % (cap/1000, cap%1000)
114
115         def model(self):
116                 if self.device[:2] == "hd":
117                         return readFile('/proc/ide/' + self.device + '/model')
118                 elif self.device[:2] == "sd":
119                         vendor = readFile(self.sysfsPath('device/vendor'))
120                         model = readFile(self.sysfsPath('device/model'))
121                         return vendor + '(' + model + ')'
122                 else:
123                         assert False, "no hdX or sdX"
124
125         def free(self):
126                 for parts in getProcMounts():
127                         if path.realpath(parts[0]).startswith(self.dev_path):
128                                 try:
129                                         stat = statvfs(parts[1])
130                                 except OSError:
131                                         continue
132                                 return stat.f_bfree/1000 * stat.f_bsize/1000
133                 return -1
134
135         def numPartitions(self):
136                 numPart = -1
137                 if self.type == self.DEVTYPE_UDEV:
138                         try:
139                                 devdir = listdir('/dev')
140                         except OSError:
141                                 return -1
142                         for filename in devdir:
143                                 if filename.startswith(self.device):
144                                         numPart += 1
145
146                 elif self.type == self.DEVTYPE_DEVFS:
147                         try:
148                                 idedir = listdir(self.dev_path)
149                         except OSError:
150                                 return -1
151                         for filename in idedir:
152                                 if filename.startswith("disc"):
153                                         numPart += 1
154                                 if filename.startswith("part"):
155                                         numPart += 1
156                 return numPart
157
158         def unmount(self):
159                 cmd = "umount"
160                 for parts in getProcMounts():
161                         if path.realpath(parts[0]).startswith(self.dev_path):
162                                 cmd = ' ' . join([cmd, parts[1]])
163                 res = system(cmd)
164                 return (res >> 8)
165
166         def createPartition(self):
167                 cmd = 'printf "0,\n;\n;\n;\ny\n" | sfdisk -f ' + self.disk_path
168                 res = system(cmd)
169                 return (res >> 8)
170
171         def mkfs(self):
172                 cmd = "mkfs.ext3 "
173                 size = self.diskSize()
174                 if size > 2 * 1024:
175                         cmd += "-T largefile -N %d " % (size * 32)
176                 elif size > 16 * 1024:
177                         cmd += "-T largefile -O sparse_super "
178                 cmd += "-m0 -O dir_index " + self.partitionPath("1")
179                 res = system(cmd)
180                 return (res >> 8)
181
182         def mount(self):
183                 try:
184                         fstab = open("/etc/fstab")
185                 except IOError:
186                         return -1
187
188                 lines = fstab.readlines()
189                 fstab.close()
190
191                 res = -1
192                 for line in lines:
193                         parts = line.strip().split(" ")
194                         if path.realpath(parts[0]) == self.partitionPath("1"):
195                                 cmd = "mount -t ext3 " + parts[0]
196                                 res = system(cmd)
197                                 return (res >> 8)
198
199                 # device is not in fstab
200                 if self.type == self.DEVTYPE_UDEV:
201                         # we can let udev do the job, re-read the partition table
202                         res = system('/sbin/sfdisk -R ' + self.disk_path)
203                         # give udev some time to make the mount, which it will do asynchronously
204                         from time import sleep
205                         sleep(3)
206
207                 return (res >> 8)
208
209         def createMovieFolder(self):
210                 if not pathExists(resolveFilename(SCOPE_HDD)):
211                         try:
212                                 makedirs(resolveFilename(SCOPE_HDD))
213                         except OSError:
214                                 return -1
215                 return 0
216
217         def fsck(self):
218                 # We autocorrect any failures
219                 # TODO: we could check if the fs is actually ext3
220                 cmd = "fsck.ext3 -f -p " + self.partitionPath("1")
221                 res = system(cmd)
222                 return (res >> 8)
223
224         def killPartition(self, n):
225                 part = self.partitionPath(n)
226
227                 if access(part, 0):
228                         cmd = 'dd bs=512 count=3 if=/dev/zero of=' + part
229                         res = system(cmd)
230                 else:
231                         res = 0
232
233                 return (res >> 8)
234
235         errorList = [ _("Everything is fine"), _("Creating partition failed"), _("Mkfs failed"), _("Mount failed"), _("Create movie folder failed"), _("Fsck failed"), _("Please Reboot"), _("Filesystem contains uncorrectable errors"), _("Unmount failed")]
236
237         def initialize(self):
238                 self.unmount()
239
240                 # Udev tries to mount the partition immediately if there is an
241                 # old filesystem on it when fdisk reloads the partition table.
242                 # To prevent that, we overwrite the first 3 sectors of the
243                 # partition, if the partition existed before. That's enough for
244                 # ext3 at least.
245                 self.killPartition("1")
246
247                 if self.createPartition() != 0:
248                         return -1
249
250                 if self.mkfs() != 0:
251                         return -2
252
253                 if self.mount() != 0:
254                         return -3
255
256                 if self.createMovieFolder() != 0:
257                         return -4
258
259                 return 0
260
261         def check(self):
262                 self.unmount()
263
264                 res = self.fsck()
265                 if res & 2 == 2:
266                         return -6
267
268                 if res & 4 == 4:
269                         return -7
270
271                 if res != 0 and res != 1:
272                         # A sum containing 1 will also include a failure
273                         return -5
274
275                 if self.mount() != 0:
276                         return -3
277
278                 return 0
279
280         def getDeviceDir(self):
281                 return self.dev_path
282
283         def getDeviceName(self):
284                 return self.disk_path
285
286         # the HDD idle poll daemon.
287         # as some harddrives have a buggy standby timer, we are doing this by hand here.
288         # first, we disable the hardware timer. then, we check every now and then if
289         # any access has been made to the disc. If there has been no access over a specifed time,
290         # we set the hdd into standby.
291         def readStats(self):
292                 try:
293                         l = open("/sys/block/%s/stat" % self.device).read()
294                 except IOError:
295                         return -1,-1
296                 (nr_read, _, _, _, nr_write) = l.split()[:5]
297                 return int(nr_read), int(nr_write)
298
299         def startIdle(self):
300                 self.last_access = time.time()
301                 self.last_stat = 0
302                 self.is_sleeping = False
303                 from enigma import eTimer
304
305                 # disable HDD standby timer
306                 Console().ePopen(("hdparm", "hdparm", "-S0", self.disk_path))
307                 self.timer = eTimer()
308                 self.timer.callback.append(self.runIdle)
309                 self.idle_running = True
310                 self.setIdleTime(self.max_idle_time) # kick the idle polling loop
311
312         def runIdle(self):
313                 if not self.max_idle_time:
314                         return
315                 t = time.time()
316
317                 idle_time = t - self.last_access
318
319                 stats = self.readStats()
320                 l = sum(stats)
321
322                 if l != self.last_stat and l >= 0: # access
323                         self.last_stat = l
324                         self.last_access = t
325                         idle_time = 0
326                         self.is_sleeping = False
327
328                 if idle_time >= self.max_idle_time and not self.is_sleeping:
329                         self.setSleep()
330                         self.is_sleeping = True
331
332         def setSleep(self):
333                 Console().ePopen(("hdparm", "hdparm", "-y", self.disk_path))
334
335         def setIdleTime(self, idle):
336                 self.max_idle_time = idle
337                 if self.idle_running:
338                         if not idle:
339                                 self.timer.stop()
340                         else:
341                                 self.timer.start(idle * 100, False)  # poll 10 times per period.
342
343         def isSleeping(self):
344                 return self.is_sleeping
345
346 class Partition:
347         def __init__(self, mountpoint, device = None, description = "", force_mounted = False):
348                 self.mountpoint = mountpoint
349                 self.description = description
350                 self.force_mounted = force_mounted
351                 self.is_hotplug = force_mounted # so far; this might change.
352                 self.device = device
353
354         def stat(self):
355                 return statvfs(self.mountpoint)
356
357         def free(self):
358                 try:
359                         s = self.stat()
360                         return s.f_bavail * s.f_bsize
361                 except OSError:
362                         return None
363
364         def total(self):
365                 try:
366                         s = self.stat()
367                         return s.f_blocks * s.f_bsize
368                 except OSError:
369                         return None
370
371         def mounted(self, mounts = None):
372                 # THANK YOU PYTHON FOR STRIPPING AWAY f_fsid.
373                 # TODO: can os.path.ismount be used?
374                 if self.force_mounted:
375                         return True
376                 if mounts is None:
377                         mounts = getProcMounts()
378                 for parts in mounts:
379                         if parts[1] == self.mountpoint:
380                                 return True
381                 return False
382
383         def filesystem(self):
384                 for fields in getProcMounts():
385                         if fields[1] == self.mountpoint:
386                                 return fields[2]
387                 return ''
388
389 DEVICEDB =  \
390         {"dm8000":
391                 {
392                         # dm8000:
393                         "/devices/platform/brcm-ehci.0/usb1/1-1/1-1.1/1-1.1:1.0": "Front USB Slot",
394                         "/devices/platform/brcm-ehci.0/usb1/1-1/1-1.2/1-1.2:1.0": "Back, upper USB Slot",
395                         "/devices/platform/brcm-ehci.0/usb1/1-1/1-1.3/1-1.3:1.0": "Back, lower USB Slot",
396                         "/devices/platform/brcm-ehci-1.1/usb2/2-1/2-1:1.0/host1/target1:0:0/1:0:0:0": "DVD Drive",
397                 },
398         "dm800":
399         {
400                 # dm800:
401                 "/devices/platform/brcm-ehci.0/usb1/1-2/1-2:1.0": "Upper USB Slot",
402                 "/devices/platform/brcm-ehci.0/usb1/1-1/1-1:1.0": "Lower USB Slot",
403         },
404         "dm7025":
405         {
406                 # dm7025:
407                 "/devices/pci0000:00/0000:00:14.1/ide1/1.0": "CF Card Slot", #hdc
408                 "/devices/pci0000:00/0000:00:14.1/ide0/0.0": "Internal Harddisk"
409         }
410         }
411
412 class HarddiskManager:
413         def __init__(self):
414                 self.hdd = [ ]
415                 self.cd = ""
416                 self.partitions = [ ]
417
418                 self.on_partition_list_change = CList()
419
420                 self.enumerateBlockDevices()
421
422                 # currently, this is just an enumeration of what's possible,
423                 # this probably has to be changed to support automount stuff.
424                 # still, if stuff is mounted into the correct mountpoints by
425                 # external tools, everything is fine (until somebody inserts
426                 # a second usb stick.)
427                 p = [
428                                         ("/media/hdd", _("Harddisk")),
429                                         ("/media/card", _("Card")),
430                                         ("/media/cf", _("Compact Flash")),
431                                         ("/media/mmc1", _("MMC Card")),
432                                         ("/media/net", _("Network Mount")),
433                                         ("/media/net1", _("Network Mount") + " 1"),
434                                         ("/media/net2", _("Network Mount") + " 2"),
435                                         ("/media/net3", _("Network Mount") + " 3"),
436                                         ("/media/ram", _("Ram Disk")),
437                                         ("/media/usb", _("USB Stick")),
438                                         ("/", _("Internal Flash"))
439                                 ]
440
441                 self.partitions.extend([ Partition(mountpoint = x[0], description = x[1]) for x in p ])
442
443         def getBlockDevInfo(self, blockdev):
444                 devpath = "/sys/block/" + blockdev
445                 error = False
446                 removable = False
447                 blacklisted = False
448                 is_cdrom = False
449                 partitions = []
450                 try:
451                         removable = bool(int(readFile(devpath + "/removable")))
452                         dev = int(readFile(devpath + "/dev").split(':')[0])
453                         if dev in (7, 31): # loop, mtdblock
454                                 blacklisted = True
455                         if blockdev[0:2] == 'sr':
456                                 is_cdrom = True
457                         if blockdev[0:2] == 'hd':
458                                 try:
459                                         media = readFile("/proc/ide/%s/media" % blockdev)
460                                         if "cdrom" in media:
461                                                 is_cdrom = True
462                                 except IOError:
463                                         error = True
464                         # check for partitions
465                         if not is_cdrom:
466                                 for partition in listdir(devpath):
467                                         if partition[0:len(blockdev)] != blockdev:
468                                                 continue
469                                         partitions.append(partition)
470                         else:
471                                 self.cd = blockdev
472                 except IOError:
473                         error = True
474                 # check for medium
475                 medium_found = True
476                 try:
477                         open("/dev/" + blockdev).close()
478                 except IOError, err:
479                         if err.errno == 159: # no medium present
480                                 medium_found = False
481
482                 return error, blacklisted, removable, is_cdrom, partitions, medium_found
483
484         def enumerateBlockDevices(self):
485                 print "enumerating block devices..."
486                 for blockdev in listdir("/sys/block"):
487                         error, blacklisted, removable, is_cdrom, partitions, medium_found = self.getBlockDevInfo(blockdev)
488                         print "found block device '%s':" % blockdev, 
489                         if error:
490                                 print "error querying properties"
491                         elif blacklisted:
492                                 print "blacklisted"
493                         elif not medium_found:
494                                 print "no medium"
495                         else:
496                                 print "ok, removable=%s, cdrom=%s, partitions=%s, device=%s" % (removable, is_cdrom, partitions, blockdev)
497
498                                 self.addHotplugPartition(blockdev)
499                                 for part in partitions:
500                                         self.addHotplugPartition(part)
501
502         def getAutofsMountpoint(self, device):
503                 return "/autofs/%s/" % (device)
504
505         def addHotplugPartition(self, device, physdev = None):
506                 if not physdev:
507                         dev, part = self.splitDeviceName(device)
508                         try:
509                                 physdev = path.realpath('/sys/block/' + dev + '/device')[4:]
510                         except OSError:
511                                 physdev = dev
512                                 print "couldn't determine blockdev physdev for device", device
513
514                 # device is the device name, without /dev
515                 # physdev is the physical device path, which we (might) use to determine the userfriendly name
516                 description = self.getUserfriendlyDeviceName(device, physdev)
517
518                 p = Partition(mountpoint = self.getAutofsMountpoint(device), description = description, force_mounted = True, device = device)
519                 self.partitions.append(p)
520                 self.on_partition_list_change("add", p)
521
522                 # see if this is a harddrive
523                 l = len(device)
524                 if l and not device[l-1].isdigit():
525                         error, blacklisted, removable, is_cdrom, partitions, medium_found = self.getBlockDevInfo(device)
526                         if not blacklisted and not is_cdrom and medium_found:
527                                 self.hdd.append(Harddisk(device))
528                                 self.hdd.sort()
529                                 SystemInfo["Harddisk"] = len(self.hdd) > 0
530
531         def removeHotplugPartition(self, device):
532                 mountpoint = self.getAutofsMountpoint(device)
533                 for x in self.partitions[:]:
534                         if x.mountpoint == mountpoint:
535                                 self.partitions.remove(x)
536                                 self.on_partition_list_change("remove", x)
537                 l = len(device)
538                 if l and not device[l-1].isdigit():
539                         for hdd in self.hdd:
540                                 if hdd.device == device:
541                                         hdd.stop()
542                                         self.hdd.remove(hdd)
543                                         break
544                         SystemInfo["Harddisk"] = len(self.hdd) > 0
545
546         def HDDCount(self):
547                 return len(self.hdd)
548
549         def HDDList(self):
550                 list = [ ]
551                 for hd in self.hdd:
552                         hdd = hd.model() + " - " + hd.bus()
553                         cap = hd.capacity()
554                         if cap != "":
555                                 hdd += " (" + cap + ")"
556                         list.append((hdd, hd))
557                 return list
558
559         def getCD(self):
560                 return self.cd
561
562         def getMountedPartitions(self, onlyhotplug = False):
563                 mounts = getProcMounts()
564                 parts = [x for x in self.partitions if (x.is_hotplug or not onlyhotplug) and x.mounted(mounts)]
565                 devs = set([x.device for x in parts])
566                 for devname in devs.copy():
567                         if not devname:
568                                 continue
569                         dev, part = self.splitDeviceName(devname)
570                         if part and dev in devs: # if this is a partition and we still have the wholedisk, remove wholedisk
571                                 devs.remove(dev)
572
573                 # return all devices which are not removed due to being a wholedisk when a partition exists
574                 return [x for x in parts if not x.device or x.device in devs]
575
576         def splitDeviceName(self, devname):
577                 # this works for: sdaX, hdaX, sr0 (which is in fact dev="sr0", part=""). It doesn't work for other names like mtdblock3, but they are blacklisted anyway.
578                 dev = devname[:3]
579                 part = devname[3:]
580                 for p in part:
581                         if not p.isdigit():
582                                 return devname, 0
583                 return dev, part and int(part) or 0
584
585         def getUserfriendlyDeviceName(self, dev, phys):
586                 dev, part = self.splitDeviceName(dev)
587                 description = "External Storage %s" % dev
588                 try:
589                         description = readFile("/sys" + phys + "/model")
590                 except IOError, s:
591                         print "couldn't read model: ", s
592                 from Tools.HardwareInfo import HardwareInfo
593                 for physdevprefix, pdescription in DEVICEDB.get(HardwareInfo().device_name,{}).items():
594                         if phys.startswith(physdevprefix):
595                                 description = pdescription
596
597                 # not wholedisk and not partition 1
598                 if part and part != 1:
599                         description += " (Partition %d)" % part
600                 return description
601
602         def addMountedPartition(self, device, desc):
603                 already_mounted = False
604                 for x in self.partitions[:]:
605                         if x.mountpoint == device:
606                                 already_mounted = True
607                 if not already_mounted:
608                         self.partitions.append(Partition(mountpoint = device, description = desc))
609
610         def removeMountedPartition(self, mountpoint):
611                 for x in self.partitions[:]:
612                         if x.mountpoint == mountpoint:
613                                 self.partitions.remove(x)
614                                 self.on_partition_list_change("remove", x)
615
616 harddiskmanager = HarddiskManager()