truncate/ftruncate


truncate/ftruncateシステムコールはファイルサイズの変更します。前者はファイル名を後者はファイルIDを引数とし、do_truncate()をコールします。

do_truncate()の主たる処理は、inode->i_sizeを変更サイズに更新し、縮小の場合掛かるメモリマップ/ページキャッシュを解放し、ファイルシステム依存ですが、通常実ブロックデバイスの解放を行います。拡張時はブロックの確保はいたしません。ファイルフォールとなります。

truncate/ftruncateの違いは、inode取得方法です。truncateはdentryを走査することで、ftruncateはFILEを走査することで行い、truncする条件処理が異なっています。FILEからのinodeだと、それがO_LARGEFILEがそうでないかで、truncする最大サイズが制限されますが、truncateだとその制限がなく、O_LARGEFILEとして処理されます。ただし設定するコールバック関数でファイルシステムに依存する各最大サイズを超えないようにチェックされます。

ftruncateはinodeのmtime/ctimeは更新されますが、truncateでは更新されません。データブロックを更新するものでない。という事でだと思います。ftruncateは、マルチスレッド下でのサイズ変更処理を伴う実装を想定してのことでないかと思います。

両者とも、CAP_FSETIDのケーパビリテのないプロセスでの動作なら、sgid/suidは削除されます。manの説明では、set-user-ID と set-group-ID の許可ビットがクリアされるかもしれない。との表現ですが、要はCAP_FSETIDのケーパビリテュ(suid/sgid設定の許可)のないプロセスが、suid/sgidを有するファイルを変更するというのは、セキュリティ上問題であり、従ってファイル変更を可能とするかわり、sgid/suid権限を削除するということです。


do_sys_truncate()は、truncateシステムコールからコールされます。user_path()でdentryを走査してinodeを取得します。そのinodeが通常ファイルかどうか、属するファイルシステムが書き込み可能か、またinode自身も書き込み可能かチェックします。

break_lease()でこのファイルをリースしているプロセスがあると、メッセージを送信し、またトランクする領域がロックされているならウエイトし、do_truncate()をコールします。この時第3パラメータが0であるというのが、mtime/ctimeを更新しない。ということです。また、lengthの制限はありません。
static long do_sys_truncate(const char __user *pathname, loff_t length)
{
       struct path path;
       struct inode *inode;
       int error;

       error = -EINVAL;
       if (length < 0) /* sorry, but loff_t says... */
               goto out;

       error = user_path(pathname, &path);
       if (error)
               goto out;
       inode = path.dentry->d_inode;

       error = -EISDIR;
       if (S_ISDIR(inode->i_mode))
               goto dput_and_out;

       error = -EINVAL;
       if (!S_ISREG(inode->i_mode))
               goto dput_and_out;

       error = mnt_want_write(path.mnt);
       if (error)
               goto dput_and_out;

       error = inode_permission(inode, MAY_WRITE);
       if (error)
               goto mnt_drop_write_and_out;

       error = -EPERM;
       if (IS_APPEND(inode))
               goto mnt_drop_write_and_out;

       error = get_write_access(inode);
       if (error)
               goto mnt_drop_write_and_out;

       error = break_lease(inode, O_WRONLY);
       if (error)
               goto put_write_and_out;

       error = locks_verify_truncate(inode, NULL, length);
       if (!error)
               error = security_path_truncate(&path);
       if (!error)
               error = do_truncate(path.dentry, length, 0, NULL);

put_write_and_out:
       put_write_access(inode);
mnt_drop_write_and_out:
       mnt_drop_write(path.mnt);
dput_and_out:
       path_put(&path);
out:
       return error;
}
do_sys_ftruncate()は、ftruncate/ftruncate64システムコールから、前者small=0、後者small=1でがコールされます。small=1ならファイル最大サイズはMAX_NON_LFSとなります。ただし、fdがO_LARGEFILEでオープンされているとsmall=0とします。

do_truncate()の第3引数が、ATTR_MTIME|ATTR_CTIMEとなっています。これでmtime/ctimeを更新されることになります。

break_lease()がコールされてないのは、open時に対応済みだからです。
static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
{
       struct inode * inode;
       struct dentry *dentry;
       struct file * file;
       int error;

       error = -EINVAL;
       if (length < 0)
               goto out;
       error = -EBADF;
       file = fget(fd);
       if (!file)
               goto out;

       if (file->f_flags & O_LARGEFILE)
               small = 0;

       dentry = file->f_path.dentry;
       inode = dentry->d_inode;
       error = -EINVAL;
       if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
               goto out_putf;

       error = -EINVAL;
       if (small && length > MAX_NON_LFS)
               goto out_putf;

       error = -EPERM;
       if (IS_APPEND(inode))
               goto out_putf;

       error = locks_verify_truncate(inode, file, length);
       if (!error)
               error = security_path_truncate(&file->f_path);
       if (!error)
               error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
out_putf:
       fput(file);
out:
       return error;
}
do_truncate()でstruct iattr newattrsに変更するinode情報設定し、notify_change()をコールします。この時ftruncateの時newattrs.ia_file = filpとします。nfs等のファイルシステムだとnewattrs.ia_fileがNULLかそうでないかで実装が異なります。
int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
       struct file *filp)
{
       int ret;
       struct iattr newattrs;

       if (length < 0)
               return -EINVAL;

       newattrs.ia_size = length;
       newattrs.ia_valid = ATTR_SIZE | time_attrs;
       if (filp) {
               newattrs.ia_file = filp;
               newattrs.ia_valid |= ATTR_FILE;
       }

       ret = should_remove_suid(dentry);
       if (ret)
               newattrs.ia_valid |= ret | ATTR_FORCE;

       mutex_lock(&dentry->d_inode->i_mutex);
       ret = notify_change(dentry, &newattrs);
       mutex_unlock(&dentry->d_inode->i_mutex);
       return ret;
}
suid/sgidを削除対象とします。なお、sgidはxgrpが設定されていないと強制ロックとしての意味合いとなり削除対象になりません。
int should_remove_suid(struct dentry *dentry)
{
       umode_t mode = dentry->d_inode->i_mode;
       int kill = 0;

       if (unlikely(mode & S_ISUID))
               kill = ATTR_KILL_SUID;

       if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
               kill |= ATTR_KILL_SGID;

       if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode)))
               return kill;

       return 0;
}
notify_change()はinodeの更新するための汎用的な関数で、各種変更にかかる条件(attr->ia_valid)をチェックして、inodeオペレーション関数のinode->i_op->setattr()またはsimple_setattr()がコールされます。
int notify_change(struct dentry * dentry, struct iattr * attr)
{
       struct inode *inode = dentry->d_inode;
       umode_t mode = inode->i_mode;
       int error;
       struct timespec now;
       unsigned int ia_valid = attr->ia_valid;

       if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_TIMES_SET)) {
               if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
                       return -EPERM;
       }

       if ((ia_valid & ATTR_MODE)) {
               umode_t amode = attr->ia_mode;
               if (is_sxid(amode))
                       inode->i_flags &= ~S_NOSEC;
       }

       now = current_fs_time(inode->i_sb);

       attr->ia_ctime = now;
       if (!(ia_valid & ATTR_ATIME_SET))
               attr->ia_atime = now;
       if (!(ia_valid & ATTR_MTIME_SET))
               attr->ia_mtime = now;
       if (ia_valid & ATTR_KILL_PRIV) {
               attr->ia_valid &= ~ATTR_KILL_PRIV;
               ia_valid &= ~ATTR_KILL_PRIV;
               error = security_inode_need_killpriv(dentry);
               if (error > 0)
                       error = security_inode_killpriv(dentry);
               if (error)
                       return error;
       }

       if ((ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) &&
           (ia_valid & ATTR_MODE))
               BUG();

       if (ia_valid & ATTR_KILL_SUID) {
               if (mode & S_ISUID) {
                       ia_valid = attr->ia_valid |= ATTR_MODE;
                       attr->ia_mode = (inode->i_mode & ~S_ISUID);
               }
       }
       if (ia_valid & ATTR_KILL_SGID) {
               if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
                       if (!(ia_valid & ATTR_MODE)) {
                               ia_valid = attr->ia_valid |= ATTR_MODE;
                               attr->ia_mode = inode->i_mode;
                       }
                       attr->ia_mode &= ~S_ISGID;
               }
       }
       if (!(attr->ia_valid & ~(ATTR_KILL_SUID | ATTR_KILL_SGID)))
               return 0;

       error = security_inode_setattr(dentry, attr);
       if (error)
               return error;

       if (inode->i_op->setattr)
               error = inode->i_op->setattr(dentry, attr);
       else
               error = simple_setattr(dentry, attr);

       if (!error) {
               fsnotify_change(dentry, ia_valid);
               evm_inode_post_setattr(dentry, ia_valid);
       }

       return error;
}

mtime/ctimeの更新

#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>

void    do_truncate(char* file, long size);
void    do_ftruncate(char* file, long size);
void    do_stat(const char *path);

int
main(int argc, char *argv[])
{

       printf("truncate\n");
       do_stat(argv[1]);
       sleep(1);
       do_truncate(argv[1], atol(argv[2]));
       do_stat(argv[1]);

       printf("ftruncate\n");
       do_stat(argv[1]);
       sleep(1);
       do_ftruncate(argv[1], atol(argv[2]));
       do_stat(argv[1]);
}

void    do_truncate(char* file, long size)
{
       syscall(SYS_ftruncate, file, size);
}

void    do_ftruncate(char* file, long size)
{
       int     fd;

       fd = open(file, O_WRONLY);
       syscall(SYS_ftruncate, fd, size);
       close(fd);
}

void    do_stat(const char *path)
{
       struct stat sb;

       stat(path, &sb);
       printf("ctime:%d atime:%d mtime:%d\n", sb.st_ctime, sb.st_atime, sb.st_mtime);
}

[root@localhost test]# ./a.out tmp/hoge 1
truncate
ctime:1404492715 atime:1403629447 mtime:1404492715
ctime:1404492715 atime:1403629447 mtime:1404492715
ftruncate
ctime:1404492715 atime:1403629447 mtime:1404492715
ctime:1404492755 atime:1403629447 mtime:1404492755

補足

カーネルとしてはO_LARGEFILEかどうかのチェックしかできません。ファイルサイズはファイルシステム依存だからです。また逆説的にいえば、ファイルシステムとして、システムの最大サイズ以下でありさえすればいいわけで、そのアクセスがO_LARGEFILEかどうかは、どうでもいいことなのです。


最終更新 2014/07/04 17:17:12 - north
(2014/07/04 17:17:12 作成)


検索

アクセス数
3689326
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。