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の制限はありません。
do_truncate()の第3引数が、ATTR_MTIME|ATTR_CTIMEとなっています。これでmtime/ctimeを更新されることになります。
break_lease()がコールされてないのは、open時に対応済みだからです。
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