USB硬盘的固定设备名和自动休眠

AirPort Extreme可以外接USB硬盘提供文件服务,但是和它的路由功能一样,都不够满足我的需求。而作为穿墙路由器的华硕小机器有4个USB口,我还用它兼作文件服务器使用,提供Samba和NFS。

但是Gentoo在这里遇到两个小问题。

第一个问题,USB硬盘是可以热拔插,这样由于载入顺序的问题,每个硬盘设备名称可能会变化。例如这个机器自带一个读卡器,在没有外挂USB硬盘时启动后候设备名为/dev/sdb。之后插入USB硬盘,USB硬盘的设备名为/dev/sdc。但是如果挂着USB硬盘启动,这两个设备的设备名就正好反过来了。我希望是固定的设备名。

第二个问题,USB硬盘长时间不访问的时候不会自动停下来,这样既不省电也不利延长硬盘的寿命。我希望在不用的时候它可以休眠。

设备名的问题可以用udev的rules解决。我加了一个udev配置文件/etc/udev/rules.d/99-external-storages.rules。

KERNEL=="sd*", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="0158", ATTRS{serial}=="20071114173400000", SYMLINK+="cardreader%n"
KERNEL=="sd*", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bcf", ATTRS{idProduct}=="0c31", ATTRS{serial}=="20090717090A", SYMLINK+="storage/neso%n"

这样给内置的读卡器添加了一个设备名的符号链接/dev/cardreader;给我的NESO 1T硬盘添加了设备名符号链接/dev/storage/neso。用固定名称访问设备的问题就解决了。

第二个问题稍微复杂些,一直没找到方便的方法。laptop-mode-tools也没能满足我的要求。一番搜索后找到sdparm可以用来控制USB硬盘,还有一段perl脚本用来调度sdparm。这样就可以满足我的需求了。脚本的原始出外我给忘了,这里我稍微修改了一点,增加对设备名符号链接的支持。

#!/usr/bin/perl -w
use File::Basename;
use Cwd 'abs_path';
 
$statfile = "/proc/diskstats";
die "$0: Cannot read $statfile\n" unless -r $statfile;
 
$| = 1;
($disk, $interval) = (@ARGV);
 
if (-l $disk) {
    $original_disk = $disk;
    while (-l $disk) {
        $disk = readlink $disk;
    }
    $disk = dirname($original_disk) . "/" . $disk;
    $disk = abs_path($disk);
}
 
$disk =~ s,/dev/,,;
print "$0: disk: $disk, interval: $interval\n";
 
$halted_data = $last_seen = '';
while (1) {
  open(STATUS, $statfile);
  ($_) = grep(/^\s+\d+\s+\d+\s+$disk\s/o, <STATUS>);
  close STATUS;
 
  if ($last_seen eq $_ && $halted_data ne $_) {
    print "Spinning down: $disk\n";
    system "sync";
    system "sdparm", "--command=stop", "/dev/$disk";
    $halted_data = $_;
  }
  $last_seen = $_;
  sleep $interval;
}

最后在启动脚本里加上

ebegin "    - Spindown USB HD"
nohup /usr/local/sbin/spindown-usbhd /dev/storage/neso 1200 >> /var/log/spindown-usbhd.log 2>&1 &
eend $?

好了,20分钟这块硬盘没有读写之后自动休眠。