renameシステムコールとmvコマンド
Rev.8を表示中。最新版はこちら。
システムコールにrenameというのがあり、それにもとづくrenameコマンドがありますが、通常ファイル名を変更する場合、mvコマンドを使つかいます。renameコマンドが直感的なコマンド体系でないからだと思います。[root@localhost a1]# man rename 書式 rename from to file renameは指定したファイル名においてfromに最初にマッチする部分をtoで置き換える。たとえばaaaのファイルをbbbに置き換える場合、以下のように使うことになります。
[root@localhost a1]# ls aaa [root@localhost a1]# rename aaa bbb aaa [root@localhost a1]# ls bbbmvコマンドだと
[root@localhost a1]# ls bbb [root@localhost a1]# mv bbb aaa [root@localhost a1]# ls aaarenameコマンドは、ディレクトリinodeのデータブロック内の名前を変更することのみで実現しています。従って変更前のファイルと変更後のファイル名が同じファイルシステム下のファイルでないといけない制約がでてきます。もし異なるファイルシステム下でも可能とするなら、ディレクトリ下のデータブロックの変更だけでなく、変更するファイルのデータブロックの移動処理が発生することになり、そうような機能をideno下で実現するとなると、vfs下でのrename処理が必要となります。vfsではrenameは定義されていません。
renameシステムコールが呼ばれると、sys_renameat(renameat)がコールされ、最初にファイルシステムが同じがどうかチェックしています。そしてファイルが書き込み可能とかの他のチェックをして、vfs_renameをコールします。
SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, int, newdfd, const char __user *, newname) { ・・・・ error = user_path_parent(olddfd, oldname, &oldnd, &from); if (error) goto exit; error = user_path_parent(newdfd, newname, &newnd, &to); if (error) goto exit1; error = -EXDEV; if (oldnd.path.mnt != newnd.path.mnt) <- ファイルシステムが違うとエラー goto exit2; ・・・・ error = vfs_rename(old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry); ・・・・
vfs_renameではファイル名の変更か、ディレクトリ名の変更かで処理がわかれます。前者は vfs_rename_otherを後者はvfs_rename_dirがコールされ、そこからファイルシステムに依存したrenameコールバック関数へとわたります。そこから ディレクトリへの変更はext3_add_entryを経由して、 add_dirent_to_bufで書き込み内容を編集しそれをディレクトリにアペンドしています。その時、変更前のファイルのiノードをそのまま使用しています。
static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { ・・・・ if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) error = -EBUSY; else error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); if (!error) { if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) d_move(old_dentry, new_dentry); } ・・・・
static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry, struct inode *inode, struct ext3_dir_entry_2 *de, struct buffer_head * bh) { ・・・・ de->file_type = EXT3_FT_UNKNOWN; if (inode) { de->inode = cpu_to_le32(inode->i_ino); <- 変更前のiノード番号で上書き ext3_set_de_type(dir->i_sb, de, inode->i_mode); } else de->inode = 0; de->name_len = namelen; memcpy (de->name, name, namelen); dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC; ext3_update_dx_flag(dir); dir->i_version++; ・・・・mvというシステムコールは無いようです。変更前のファイルを変更後のファイルとして複写し、変更前のファイルを削除すればよさそうです。そして、このコマンドはファイルシステムを超えて可能です。結果としては想像できそうなものですが、同じファイルシステム下でおこなうmvと、異なるファイルシステム下でおこなうmvを調べてみると、(straceでもいいのでしょうが・・・)
ファイルシステムが同じ
[root@localhost a1]# ls -i 103382 aaa [root@localhost a1]# mv aaa ../a2/bbb [root@localhost a1]# ls -i ../a2 103382 bbbファイルシステムが違う
[root@localhost a1]# ls -i 103388 aaa [root@localhost a1]# mv aaa /mnt [root@localhost a1]# ls -i /mnt/aaa 15 /mnt/aaa同じファイルシステムではiノードが同じで、異なるファイルシステムでは、iノードが異なっています。mvコマンドで変更前と変更後のファイルが同じファイルシステムの場合、renameシステムコールを使っていることが推測できます。
異なるファイルシステム間でmvを動作したstraceの内容です。まずrenameで名前変更ができるか試してみいます。もし同じファイルシステム間の移動ならば、ここで移動処理は完了します。この場合ファイルシステムが違うためエラーとなっています。そして移動元を読み込みでオープン、移動先を書き込みの新規作成でオープンして、移動元を移動先に書き込んでいます。
[root@KURO-BOXHG kitamura]# strace mv aaa /mnt/usbhd/ ・・・・・ geteuid() = 0 rename("aaa", "/mnt/usbhd/aaa") = -1 EXDEV (Invalid cross-device link) unlink("/mnt/usbhd/aaa") = 0 open("aaa", O_RDONLY|O_LARGEFILE|O_NOFOLLOW) = 3 fstat64(0x3, 0xbf9d6490) = 0 open("/mnt/usbhd/aaa", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4 fstat64(0x4, 0xbf9d64f8) = 0 read(3, "aaa\n", 32768) = 4 write(4, "aaa\n", 4) = 4 ・・・・・ unlinkat(AT_FDCWD, "aaa", 0) = 0