仙石浩明の日記

2007年9月27日

Linux でシンボリックリンクのタイムスタンプを変更する (utimensat システムコールを使って) hatena_b

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」などと更新日時でソートすれば、 リンクとリンク先が隣り合わせになって見やすくなる。

Filed under: プログラミングと開発環境 — hiroaki_sengoku @ 09:22

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment