シェルスクリプトの一部を排他制御する必要があり、調べたところ、CentOS 5 以降なら、util-linux に含まれる flock コマンドを使えることがわかりました。オンラインマニュアルに典型的な使い方が書いてあり、これは、なかなか素晴らしいと思いました。最初に考えた人は、すごいなと思います。オンラインマニュアルからの抜粋です。
FLOCK(1) H. Peter Anvin FLOCK(1)
NAME
flock - Manage locks from shell scripts
SYNOPSIS
flock [-sxon] [-w timeout] lockfile [-c] command...
flock [-sxon] [-w timeout] lockdir [-c] command...
flock [-sxun] [-w timeout] fd
DESCRIPTION
This utility manages flock(2) locks from within shell scripts or the command line.
The first and second forms wraps the lock around the executing a command, in a man・
er similar to su(1) or newgrp(1). It locks a specified file or directory, which
is created (assuming appropriate permissions), if it does not already exist.
The third form is convenient inside shell scripts, and is usually used the follow・
ng manner:
(
flock -s 200
# ... commands executed under lock ...
) 200>/var/lock/mylockfile
...
オンラインマニュアルの例では、サブシェルを起動していますが、次のようにすれば、シェルスクリプト全体をシリアライズすることも可能です。
#!/bin/bash
exec 9>>/var/lock/mylockfile
flock -n 9
if [ $? -ne 0 ] ; then
# ロックできなければ、失敗(exit 1)させるかどうかは、処理の内容次第です。
# ロック解放を待ち合わせるのなら、-n オプションを付けなければ良い。
fi
#
# 排他の必要な処理を記述。
#
flock -u 9 # スクリプトに対応するプロセスが終了すれば、
# カーネルが自動的にロック解放しますが、明示的に記述する。
exit 0
exec 9>>/var/lock/mylockfile という書き方をすると、/var/lock/mylockfile を追記モードで open して、その fd を 9 番に割り当てます。シェル内部では、dup2(2) が使われます。
CentOS 4 以下では、flock コマンドはまだ提供されておらず、lockfile という似たコマンドがあるようです。しかし、lockfile のほうは、原始的なテクニックが使われており、flock の方が信頼性は高いと思われます。
2011-09-04追加
flock コマンドは、flock(2) システムコールに依存しており、
NFS では動作しないので注意が必要なようです。オンラインマニュアル (man 2 flock) から抜粋です。
2011-09-08 実験してみたところでは動作しました。追記内容を参照してください。
NOTES
flock(2) does not lock files over NFS. Use fcntl(2) instead: that does
work over NFS, given a sufficiently recent version of Linux and a server
which supports locking.
flock はファイルシステム毎に実装 (struct file operations の flock メンバ) することができ、確かに NFS では、次のように定義されていました。
以下、2.6.18-194.el5 (CentOS 5.5) から抜粋です。
54 const struct file_operations nfs_file_operations = {
55 .llseek = nfs_file_llseek,
56 .read = do_sync_read,
57 .write = do_sync_write,
58 .aio_read = nfs_file_read,
59 .aio_write = nfs_file_write,
60 .mmap = nfs_file_mmap,
61 .open = nfs_file_open,
62 .flush = nfs_file_flush,
63 .release = nfs_file_release,
64 .fsync = nfs_fsync,
65 .lock = nfs_lock,
66 .flock = nfs_flock,
67 .sendfile = nfs_file_sendfile,
68 .check_flags = nfs_check_flags,
69 };
...
639 /*
640 * Lock a (portion of) a file
641 */
642 static int nfs_flock(struct file *filp, int cmd, struct file_lock *fl)
643 {
644 dprintk("NFS: nfs_flock(f=%s/%ld, t=%x, fl=%x)\n",
645 filp->f_dentry->d_inode->i_sb->s_id,
646 filp->f_dentry->d_inode->i_ino,
647 fl->fl_type, fl->fl_flags);
648
649 /*
650 * No BSD flocks over NFS allowed.
651 * Note: we could try to fake a POSIX lock request here by
652 * using ((u32) filp | 0x80000000) or some such as the pid.
653 * Not sure whether that would be unique, though, or whether
654 * that would break in other places.
655 */
656 if (!(fl->fl_flags & FL_FLOCK))
657 return -ENOLCK;
658
"fs/nfs/file.c"
flock(2) システムコールのコードは次のようになっています。
1590 /**
1591 * sys_flock: - flock() system call.
1592 * @fd: the file descriptor to lock.
1593 * @cmd: the type of lock to apply.
1594 *
1595 * Apply a %FL_FLOCK style lock to an open file descriptor.
1596 * The @cmd can be one of
1597 *
1598 * %LOCK_SH -- a shared lock.
1599 *
1600 * %LOCK_EX -- an exclusive lock.
1601 *
1602 * %LOCK_UN -- remove an existing lock.
1603 *
1604 * %LOCK_MAND -- a `mandatory' flock. This exists to emulate Windows Share Modes.
1605 *
1606 * %LOCK_MAND can be combined with %LOCK_READ or %LOCK_WRITE to allow other
1607 * processes read and write access respectively.
1608 */
1609 asmlinkage long sys_flock(unsigned int fd, unsigned int cmd)
1610 {
1611 struct file *filp;
1612 struct file_lock *lock;
1613 int can_sleep, unlock;
1614 int error;
1615
1616 error = -EBADF;
1617 filp = fget(fd);
1618 if (!filp)
1619 goto out;
1620
1621 can_sleep = !(cmd & LOCK_NB);
1622 cmd &= ~LOCK_NB;
1623 unlock = (cmd == LOCK_UN);
1624
1625 if (!unlock && !(cmd & LOCK_MAND) && !(filp->f_mode & 3))
1626 goto out_putf;
1627
1628 error = flock_make_lock(filp, &lock, cmd);
1629 if (error)
1630 goto out_putf;
1631 if (can_sleep)
1632 lock->fl_flags |= FL_SLEEP; 1633
1634 error = security_file_lock(filp, cmd);
1635 if (error)
1636 goto out_free;
1637
1638 if (filp->f_op && filp->f_op->flock)
1639 error = filp->f_op->flock(filp,
1640 (can_sleep) ? F_SETLKW : F_SETLK,
1641 lock);
1642 else
1643 error = flock_lock_file_wait(filp, lock);
...
"fs/locks.c"
NFS の場合は、f_op->flock が定義されているので、1639行目が実行され、
最終的に ENOLOCK エラーが返却されることになるようです。2011-09-08 読み誤っていましたので、追記内容を参照してください。
ext3 等の場合は、f_op->flock が未定義なので、1643行目が実行されます。
なお、lock->fl_flags に FL_FLOCK を設定しているのは、1628行目です。
278 /* Fill in a file_lock structure with an appropriate FLOCK lock. */
279 static int flock_make_lock(struct file *filp, struct file_lock **lock,
280 unsigned int cmd)
281 {
282 struct file_lock *fl;
283 int type = flock_translate_cmd(cmd);
284 if (type < 0)
285 return type;
286
287 fl = locks_alloc_lock();
288 if (fl == NULL)
289 return -ENOMEM;
290
291 fl->fl_file = filp;
292 fl->fl_pid = current->tgid;
293 fl->fl_flags = FL_FLOCK;
294 fl->fl_type = type;
295 fl->fl_end = OFFSET_MAX;
296
297 *lock = fl;
298 return 0;
299 }
"fs/locks.c"
2011-09-08追記
fs/nfs/file.c の 656 行目を条件を読み間違えていました。ここは、FL_FLOCK が設定されていないのに、ここに来た場合に ENOLCK を返すというだけでした。
それで、続きを読んでみると、POSIX のロックを使ってシミュレートするよ とあります。
639 /*
640 * Lock a (portion of) a file
641 */
642 static int nfs_flock(struct file *filp, int cmd, struct file_lock *fl)
643 {
644 dprintk("NFS: nfs_flock(f=%s/%ld, t=%x, fl=%x)\n",
645 filp->f_dentry->d_inode->i_sb->s_id,
646 filp->f_dentry->d_inode->i_ino,
647 fl->fl_type, fl->fl_flags);
648
649 /*
650 * No BSD flocks over NFS allowed.
651 * Note: we could try to fake a POSIX lock request here by
652 * using ((u32) filp | 0x80000000) or some such as the pid.
653 * Not sure whether that would be unique, though, or whether
654 * that would break in other places.
655 */
656 if (!(fl->fl_flags & FL_FLOCK))
657 return -ENOLCK;
658
659 /* We're simulating flock() locks using posix locks on the server */
660 fl->fl_owner = (fl_owner_t)filp;
661 fl->fl_start = 0;
662 fl->fl_end = OFFSET_MAX;
663
664 if (fl->fl_type == F_UNLCK)
665 return do_unlk(filp, cmd, fl);
666 return do_setlk(filp, cmd, fl);
667 }
"fs/nfs/file.c"
それじゃあ、flock コマンドが NFS を越えて正しく動作するのかというと、POSIX ロックと BSD ロック (flock) の違い (POSIX ロックは fork を越えられない) により正しく動作しないのでは? っと思い、テストプログラムで確かめてみました。CentOS 5 で確認しています。
#!/bin/bash
exec 9>>./flock.test.lock
flock -n 9
if [ $? -ne 0 ] ; then
echo "fail" 1>&2
exit 1
fi
sleep 30
flock -u 9
exit 0
# df -TP .
Filesystem Type 1024-blocks Used Available Capacity Mounted on
xx.xx.xx.xx:/ nfs 5921856 5455872 165176 98% /mnt_nfs
# /tmp/flock.sh & /tmp/flock.sh
[1] 6452
fail
2つ同時起動で、2つ目が失敗してますから、ちゃんとロック出来ているようです。
どうなっているのか理解出来ていませんが、NFS の開発者の方は、flock も視野に実装してくれたということでしょうかね。
2011-09-09追記
CentOS 6 を NFS サーバにして、flock を試すとうまく行かず、次のようなエラーになってしまいました。クライアントは CentOS 5 です。
# ./flock.sh
flock: 9: No locks available
fail
サーバ側を見てみると、次のエラーが出ていました。
Sep 8 23:39:14 centos6 rpc.statd[6061]: No canonical hostname found for xx.xx.xx.xx
Sep 8 23:39:14 centos6 rpc.statd[6061]: STAT_FAIL to centos6 for SM_MON of xx.xx.xx.xx
Sep 8 23:39:14 centos6 kernel: lockd: cannot monitor xx.xx.xx.xx
調べてみると、次の情報が見つかりました。
http://dfwarden.blogspot.com/2011/02/psa-nfs-locking-in-rhel6-needs-reverse.html
確かに、サーバ側 (CentOS 6) の /etc/hosts に、クライアントの IP アドレスを登録したら、flock 出来るようになりました。信頼性・セキュリティの強化が図られているということでしょうかね。
なお、lockd: cannot monitor のメッセージは、クライアント側で nfslock サービスが上がっていない場合にも出るメッセージなようです。メモ。