無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

ハードリンク


ハードリンクはlinkシステムコールからをAT_FDCWD(path検索をカレントパスとする)でlinkaシステムコールをコールします。oldnameがリンク元、newnameがリンク先となります。

user_path_at()でリンク元のstruct pathを取得し、user_path_create()でリンク先をキャッシュdentryとしてのpathを作成します。user_path_create()の返り値は、リンク先のdentryで、new_pathはリンク先ファイルの親ディレクトリです。リンク先が/tmp/hogeならnew_dentry=/tmp/hoge、new_path.dentry=/tmpと言う事です。リンク実装は、ディレクトリのinode_operationsコールバックで実装されているからです。

if (old_path.mnt != new_path.mnt)でリンク元/リンク先が同じファイルシステムでないとエラーです。ハードリンクは異なるファイルシステムに作成することはできません。物理inodeを共有するからです。vfs下のinodeは全ファイルシステムで一意的ですが、物理inodeはファイルシステム毎に一意的だからです。

mnt_want_write()でリンク先が書込み可でマウントされているかチェックし、vfs_link()でハードリンクを作成します。
SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname)
{
      return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}

SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
              int, newdfd, const char __user *, newname, int, flags)
{
      struct dentry *new_dentry;
      struct path old_path, new_path;
      int how = 0;
      int error;

      if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
              return -EINVAL;

      if (flags & AT_EMPTY_PATH) {
              if (!capable(CAP_DAC_READ_SEARCH))
                      return -ENOENT;
              how = LOOKUP_EMPTY;
      }

      if (flags & AT_SYMLINK_FOLLOW)
              how |= LOOKUP_FOLLOW;

      error = user_path_at(olddfd, oldname, how, &old_path);
      if (error)
              return error;

      new_dentry = user_path_create(newdfd, newname, &new_path, 0);
      error = PTR_ERR(new_dentry);
      if (IS_ERR(new_dentry))
              goto out;

      error = -EXDEV;
      if (old_path.mnt != new_path.mnt)
              goto out_dput;
      error = mnt_want_write(new_path.mnt);
      if (error)
              goto out_dput;
      error = security_path_link(old_path.dentry, &new_path, new_dentry);
      if (error)
              goto out_drop_write;
      error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry);
out_drop_write:
      mnt_drop_write(new_path.mnt);
out_dput:
      dput(new_dentry);
      mutex_unlock(&new_path.dentry->d_inode->i_mutex);
      path_put(&new_path);
out:
      path_put(&old_path);

      return error;
}
old_dentryはリンク元ファイル、dirはリンク先の親ディレクトリ、new_dentryはリンク先ファイルです。inodeレベルで書込み有無/ファイルシステムが同じかのチェックを行っています。ファイルシステムの相違は、linkat()でも同じチェックを行っていますが、vfs_link()はEXPORT_SYMBOLされており、他のlkmを含めたカーネル内からvfs_link()コールを想定してかの事と思います。(従って、linkat()でのファイルシステムの相違チェックは冗長的かと・・・)

may_create()でdirが書き込み可能か、またnew_dentryが存在していないか等、新規に親dir配下にファイルが作成できるかチェックし、またリンク元が、APPEND(追加のみ)/IMMUTABLE(変更不可)、およびディレクトリだとエラーです。

OKだとディレクトリコールバックのlinkをコールします。
int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
{
      struct inode *inode = old_dentry->d_inode;
      int error;

      if (!inode)
              return -ENOENT;

      error = may_create(dir, new_dentry);
      if (error)
              return error;

      if (dir->i_sb != inode->i_sb)
              return -EXDEV;

      if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
              return -EPERM;
      if (!dir->i_op->link)
              return -EPERM;
      if (S_ISDIR(inode->i_mode))
              return -EPERM;

      error = security_inode_link(old_dentry, dir, new_dentry);
      if (error)
              return error;

      mutex_lock(&inode->i_mutex);
      if (inode->i_nlink == 0)
              error =  -ENOENT;
      else
              error = dir->i_op->link(old_dentry, dir, new_dentry);
      mutex_unlock(&inode->i_mutex);
      if (!error)
              fsnotify_link(dir, inode, new_dentry);
      return error;
}
ハードリンクの実装はファイルシステム依存です。ここではramfsについては、ハードリンクはsimple_link()がコールされ、dentryキャッシュ配下のみの処理となります。
static const struct inode_operations ramfs_dir_inode_operations = {
      .create         = ramfs_create,
      .lookup         = simple_lookup,
      .link           = simple_link,
      .unlink         = simple_unlink,
      .symlink        = ramfs_symlink,
      .mkdir          = ramfs_mkdir,
      .rmdir          = simple_rmdir,
      .mknod          = ramfs_mknod,
      .rename         = simple_rename,
};
リンク元old_dentry(故にIMMUTABLEであってはなりません。)/リンク先親dirのタイムスタンプが更新され、d_instantiate()から_d_instantiate()をコールし、リンク元inode->i_dentryにリンク先dentryをリストし、dentry->d_inode = inodeとする事で、リンク元inodeをリンク先inodeとし(故に同じファイルシステムでなければなりません。)ハードリンクを構築します。従ってハードリンクは、リンク先もリンク元もありません。inodeレベルでは同等な物です。

inc_nlink()は、 inode->__i_nlink++とし、かかるinodeのファイルを削除する時、ディレクトリのdentry(ファイル名)は削除されますが(ただし、ramfsではその必要はありません。)、かかるファイルのブロックデータも削除するかどうか、inode->__i_nlink==0かによって決定され、1つでもハードリンクが有している限り、ファイルのデータブロックが削除されることはありません。
int simple_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
      struct inode *inode = old_dentry->d_inode;

      inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
      inc_nlink(inode);
      ihold(inode);
      dget(dentry);
      d_instantiate(dentry, inode);
      return 0;
}

static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
      spin_lock(&dentry->d_lock);
      if (inode) {
              if (unlikely(IS_AUTOMOUNT(inode)))
                      dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
              list_add(&dentry->d_alias, &inode->i_dentry);
      }
      dentry->d_inode = inode;
      dentry_rcuwalk_barrier(dentry);
      spin_unlock(&dentry->d_lock);
      fsnotify_d_instantiate(dentry, inode);
}
ramfsの実務上、ファイルシステムのリンク先親ディレクトリにリンク先ファイルを追加する必要はありません。ただし実デバイスを有するファイルシステムでは、リンク先親ディレクトリにリンク先ファイルを追加します。新規ファイル作成となるわけですが、新規にinodeを取得する必要はありません。

追記

リンク元がディレクトリだと、なぜダメなのでしょうか・・・? 実装から見る限り問題ないように思われるのですが・・・。


最終更新 2015/03/05 04:37:44 - north
(2015/03/01 18:01:54 作成)