Linux 2.6.22 以前は、 シンボリックリンク (symbolic link) のタイムスタンプ (time stamp) を 変更することが出来なかった。 Linux (に限らず unix のほとんど全て) では、 シンボリックリンクも普通のファイルと同様、 アクセス日時と更新日時のタイムスタンプを保持している。 BSD な unix などでは、 lutimes システムコールを使ってシンボリックリンクのタイムスタンプを変更できる。
ところが、Linux には lutimes システムコールが存在しない。 したがってシンボリックリンクのタイムスタンプは、 そのシンボリックリンクを作成した時刻のままである。 tar などでアーカイブからリストアする場合や、 rsync などでディレクトリをコピーする場合などでも、 シンボリックリンクだけは元のタイムスタンプが復元されず、 復元した時刻のシンボリックリンクが作成されるので、 不便なことこのうえない。
Linux 2.6.22 になって、 utimensat システムコールが新設された。
Ulrich Drepper (glibc のメンテナ) は、 futimesat インターフェイスでは機能が足りていないということを理由に、 utimensat システムコールを提案しました。
futimesat は、inode のアクセス・変更時間を設定するためのシステムコールです。 struct timeval をパラメータとして受け取るため、 マイクロ秒単位で設定します。 一方、inode の各種情報を取得する stat というシステムコールでは、 ナノ秒単位で取得できるようになっています。 つまり、そもそも設定できない精度で情報を取得できるような仕組みに なっているわけです。
この問題を解決するために、 パラメータとして struct timespec (ナノ秒単位) を利用できる utimensat というシステムコールを用意することになりました。 このシステムコールは Linux カーネル 2.6.22-rc1 でマージされました。
この記事では、 ナノ秒単位で設定できることばかり強調しているが (私の感覚だとマイクロ秒がナノ秒になっても、あまり嬉しくない ;-)、 utimensat で機能拡張されたのはそれだけではない。
int utimensat(unsigned int dfd, char *filename, struct timespec *t, int flags);
引数が「struct timespec」になってナノ秒単位で設定できるようになったわけだが、 「utimensat」という名称から推測される通り、 他の *at (末尾に「at」がつく) システムコールと同様、 引数として与えたパス名 (char *filename) の扱いを指定できる。 /usr/include/fcntl.h には次のように書いてある。
#ifdef __USE_ATFILE # define AT_FDCWD -100 /* Special value used to indicate the *at functions should use the current working directory. */ # define AT_SYMLINK_NOFOLLOW 0x100 /* Do not follow symbolic links. */ # define AT_REMOVEDIR 0x200 /* Remove directory instead of unlinking file. */ # define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */ # define AT_EACCESS 0x200 /* Test access permitted for effective IDs, not real IDs. */ #endif
utimensat の第四引数 int flags に「AT_SYMLINK_NOFOLLOW」を与えれば、 シンボリックリンクを「たどらず」に、 シンボリックリンクそのものに対して、 タイムスタンプの変更を行なうことができそうである (ちなみに futimesat は第三引数までしかなく int flags を指定できない)。
さっそく実験してみる:
int utimensat(int dfd, char *filename, struct timespec *utimes, int flags) { register unsigned int ret; asm volatile ( "movl %1, %%eax\n\t" "call *%%gs:%P2\n\t" : "=a" (ret) : "i" (320), "i" (16), "b" (dfd), "c" (filename), "d" (utimes), "S" (flags) : "memory", "cc"); return (long)ret; }
手元の Linux マシンの glibc では、 utimensat を呼び出せるようにはなっていなかったので、 glibc のソースを参考にしながら utimensat システムコールを呼び出す関数を書いてみた。 コード中「320」などとハードコーディング (^^;) している数値は、 linux/include/asm-i386/unistd.h 中の、
#define __NR_utimensat 320
を意味している。 これで utimensat システムコールをユーザ空間から呼び出せるようになった。 もちろん、utimensat をサポートしている glibc であれば、 このような関数をデッチあげるまでもなく、 そのまま普通に utimensat を呼び出せばよい。
さっそく使ってみる:
#include <stdio.h> #include <stdlib.h> #define __USE_ATFILE #include <fcntl.h> #undef __USE_MISC #include <sys/stat.h> int main(int argc, char *argv[]) { int i; for (i=1; i < argc; i++) { struct stat lst, st; if (lstat(argv[i], &lst)) { printf("Can't find: %s\nUsage: %s <file>...\n", argv[i], argv[0]); exit(1); } if (S_ISLNK(lst.st_mode) && !stat(argv[i], &st)) { struct timespec ts[2]; ts[0].tv_sec = st.st_atime; ts[0].tv_nsec = st.st_atimensec; ts[1].tv_sec = st.st_mtime; ts[1].tv_nsec = st.st_mtimensec; if (utimensat(AT_FDCWD, argv[i], ts, AT_SYMLINK_NOFOLLOW) < 0) { printf("Failed to utimensat %s %ld.%09ld\n", argv[i], ts[1].tv_sec, ts[1].tv_nsec); } } } return 0; }
シンボリックリンクのタイムスタンプを、
シンボリックリンク先のファイル (or ディレクトリ、その他) のタイムスタンプと
一致させるプログラムである。
上記ソースプログラム (utimensat 関数と main 関数) を
ltouch.c という名前で保存し、
「gcc -o ltouch ltouch.c」などとコンパイルした後、
「ltouch シンボリックリンクのパス名」などと実行する。
senri # ls -lt /usr/i486-linuxaout/lib/ total 20000 lrwxrwxrwx 1 root root 16 Dec 22 2005 libPEX5.so.1 -> libPEX5.so.1.1.0* lrwxrwxrwx 1 root root 14 Dec 22 2005 libPEX5.so.6 -> libPEX5.so.6.0* lrwxrwxrwx 1 root root 15 Dec 22 2005 libX11.so.3 -> libX11.so.3.1.0* lrwxrwxrwx 1 root root 13 Dec 22 2005 libX11.so.6 -> libX11.so.6.0* lrwxrwxrwx 1 root root 13 Dec 22 2005 libXIE.so.6 -> libXIE.so.6.0* lrwxrwxrwx 1 root root 15 Dec 22 2005 libXaw.so.3 -> libXaw.so.3.1.0* lrwxrwxrwx 1 root root 13 Dec 22 2005 libXaw.so.6 -> libXaw.so.6.0* lrwxrwxrwx 1 root root 15 Dec 22 2005 libXpm.so.3 -> libXpm.so.3.3.0* lrwxrwxrwx 1 root root 13 Dec 22 2005 libXpm.so.4 -> libXpm.so.4.3* lrwxrwxrwx 1 root root 14 Dec 22 2005 libXt.so.3 -> libXt.so.3.1.0* lrwxrwxrwx 1 root root 12 Dec 22 2005 libXt.so.6 -> libXt.so.6.0* lrwxrwxrwx 1 root root 18 Dec 22 2005 libcurses.so.0 -> libcurses.so.0.1.2* lrwxrwxrwx 1 root root 15 Dec 22 2005 libdb.so.1 -> libdb.so.1.85.1* lrwxrwxrwx 1 root root 10 Dec 22 2005 libdbm.sa -> libgdbm.sa lrwxrwxrwx 1 root root 16 Dec 22 2005 libdosemu -> libdosemu-0.60.1 lrwxrwxrwx 1 root root 14 Dec 22 2005 libe2fs.so.1 -> libe2fs.so.1.0* lrwxrwxrwx 1 root root 13 Dec 22 2005 libe2p.so.1 -> libe2p.so.1.0* lrwxrwxrwx 1 root root 12 Dec 22 2005 libet.so.1 -> libet.so.1.0* lrwxrwxrwx 1 root root 12 Dec 22 2005 libgr.so.1 -> libgr.so.1.3* lrwxrwxrwx 1 root root 12 Dec 22 2005 libss.so.1 -> libss.so.1.0* lrwxrwxrwx 1 root root 14 Dec 22 2005 libtcl.so.3 -> libtcl.so.3.1j* lrwxrwxrwx 1 root root 13 Dec 22 2005 libtk.so.3 -> libtk.so.3.1j* lrwxrwxrwx 1 root root 16 Dec 22 2005 libvga.so.1 -> libvga.so.1.0.11* ...(後略)...
古いディレクトリだと、 このように同じタイムスタンプのシンボリックリンクばかり並んでしまって、 見にくいことこの上ない (おそらく 2005年12月22日に、ハードディスクを入れ替えたのだろう) のであるが、 「ltouch *」を実行すると、
senri # ltouch /usr/i486-linuxaout/lib/* senri # ls -lt /usr/i486-linuxaout/lib/ total 20000 lrwxrwxrwx 1 root root 14 Sep 11 1995 libPEX5.so.6 -> libPEX5.so.6.0* -r-xr-xr-x 1 root root 234500 Sep 11 1995 libPEX5.so.6.0* lrwxrwxrwx 1 root root 13 Sep 11 1995 libXIE.so.6 -> libXIE.so.6.0* -r-xr-xr-x 1 root root 58372 Sep 11 1995 libXIE.so.6.0* lrwxrwxrwx 1 root root 13 Sep 11 1995 libXaw.so.6 -> libXaw.so.6.0* -r-xr-xr-x 1 root root 209924 Sep 11 1995 libXaw.so.6.0* lrwxrwxrwx 1 root root 12 Sep 11 1995 libXt.so.6 -> libXt.so.6.0* -r-xr-xr-x 1 root root 320516 Sep 11 1995 libXt.so.6.0* lrwxrwxrwx 1 root root 13 Sep 11 1995 libX11.so.6 -> libX11.so.6.0* -r-xr-xr-x 1 root root 529412 Sep 11 1995 libX11.so.6.0* lrwxrwxrwx 1 root root 16 Jul 29 1995 libdosemu -> libdosemu-0.60.1 -rw-r--r-- 1 root root 630973 Jul 29 1995 libdosemu-0.60.1 -rw-r--r-- 1 root root 28912 Mar 19 1995 libdes.a lrwxrwxrwx 1 root root 14 Feb 28 1995 libe2fs.so.1 -> libe2fs.so.1.0* -rwxr-xr-x 1 root root 84035 Feb 28 1995 libe2fs.so.1.0* lrwxrwxrwx 1 root root 13 Feb 28 1995 libe2p.so.1 -> libe2p.so.1.0* -rwxr-xr-x 1 root root 51633 Feb 28 1995 libe2p.so.1.0* lrwxrwxrwx 1 root root 12 Feb 28 1995 libet.so.1 -> libet.so.1.0* -rwxr-xr-x 1 root root 56437 Feb 28 1995 libet.so.1.0* lrwxrwxrwx 1 root root 12 Feb 28 1995 libss.so.1 -> libss.so.1.0* -rwxr-xr-x 1 root root 63306 Feb 28 1995 libss.so.1.0* lrwxrwxrwx 1 root root 18 Feb 19 1995 libcurses.so.0 -> libcurses.so.0.1.2* -rwxr-xr-x 1 root root 49152 Feb 19 1995 libcurses.so.0.1.2* ...(後略)...
このように、 シンボリックリンクとリンク先ファイルが同じタイムスタンプになるので、 「ls -lt」などと更新日時でソートすれば、 リンクとリンク先が隣り合わせになって見やすくなる。