Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

utimensat implementation

Implement utimensat(2) which is an extension to futimesat(2) in that it

a) supports nano-second resolution for the timestamps
b) allows to selectively ignore the atime/mtime value
c) allows to selectively use the current time for either atime or mtime
d) supports changing the atime/mtime of a symlink itself along the lines
of the BSD lutimes(3) functions

For this change the internally used do_utimes() functions was changed to
accept a timespec time value and an additional flags parameter.

Additionally the sys_utime function was changed to match compat_sys_utime
which already use do_utimes instead of duplicating the work.

Also, the completely missing futimensat() functionality is added. We have
such a function in glibc but we have to resort to using /proc/self/fd/* which
not everybody likes (chroot etc).

Test application (the syscall number will need per-arch editing):

#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>
#include <stddef.h>
#include <syscall.h>

#define __NR_utimensat 280

#define UTIME_NOW ((1l << 30) - 1l)
#define UTIME_OMIT ((1l << 30) - 2l)

int
main(void)
{
int status = 0;

int fd = open("ttt", O_RDWR|O_CREAT|O_EXCL, 0666);
if (fd == -1)
error (1, errno, "failed to create test file \"ttt\"");

struct stat64 st1;
if (fstat64 (fd, &st1) != 0)
error (1, errno, "fstat failed");

struct timespec t[2];
t[0].tv_sec = 0;
t[0].tv_nsec = 0;
t[1].tv_sec = 0;
t[1].tv_nsec = 0;
if (syscall(__NR_utimensat, AT_FDCWD, "ttt", t, 0) != 0)
error (1, errno, "utimensat failed");

struct stat64 st2;
if (fstat64 (fd, &st2) != 0)
error (1, errno, "fstat failed");

if (st2.st_atim.tv_sec != 0 || st2.st_atim.tv_nsec != 0)
{
puts ("atim not reset to zero");
status = 1;
}
if (st2.st_mtim.tv_sec != 0 || st2.st_mtim.tv_nsec != 0)
{
puts ("mtim not reset to zero");
status = 1;
}
if (status != 0)
goto out;

t[0] = st1.st_atim;
t[1].tv_sec = 0;
t[1].tv_nsec = UTIME_OMIT;
if (syscall(__NR_utimensat, AT_FDCWD, "ttt", t, 0) != 0)
error (1, errno, "utimensat failed");

if (fstat64 (fd, &st2) != 0)
error (1, errno, "fstat failed");

if (st2.st_atim.tv_sec != st1.st_atim.tv_sec
|| st2.st_atim.tv_nsec != st1.st_atim.tv_nsec)
{
puts ("atim not set");
status = 1;
}
if (st2.st_mtim.tv_sec != 0 || st2.st_mtim.tv_nsec != 0)
{
puts ("mtim changed from zero");
status = 1;
}
if (status != 0)
goto out;

t[0].tv_sec = 0;
t[0].tv_nsec = UTIME_OMIT;
t[1] = st1.st_mtim;
if (syscall(__NR_utimensat, AT_FDCWD, "ttt", t, 0) != 0)
error (1, errno, "utimensat failed");

if (fstat64 (fd, &st2) != 0)
error (1, errno, "fstat failed");

if (st2.st_atim.tv_sec != st1.st_atim.tv_sec
|| st2.st_atim.tv_nsec != st1.st_atim.tv_nsec)
{
puts ("mtim changed from original time");
status = 1;
}
if (st2.st_mtim.tv_sec != st1.st_mtim.tv_sec
|| st2.st_mtim.tv_nsec != st1.st_mtim.tv_nsec)
{
puts ("mtim not set");
status = 1;
}
if (status != 0)
goto out;

sleep (2);

t[0].tv_sec = 0;
t[0].tv_nsec = UTIME_NOW;
t[1].tv_sec = 0;
t[1].tv_nsec = UTIME_NOW;
if (syscall(__NR_utimensat, AT_FDCWD, "ttt", t, 0) != 0)
error (1, errno, "utimensat failed");

if (fstat64 (fd, &st2) != 0)
error (1, errno, "fstat failed");

struct timeval tv;
gettimeofday(&tv,NULL);

if (st2.st_atim.tv_sec <= st1.st_atim.tv_sec
|| st2.st_atim.tv_sec > tv.tv_sec)
{
puts ("atim not set to NOW");
status = 1;
}
if (st2.st_mtim.tv_sec <= st1.st_mtim.tv_sec
|| st2.st_mtim.tv_sec > tv.tv_sec)
{
puts ("mtim not set to NOW");
status = 1;
}

if (symlink ("ttt", "tttsym") != 0)
error (1, errno, "cannot create symlink");

t[0].tv_sec = 0;
t[0].tv_nsec = 0;
t[1].tv_sec = 0;
t[1].tv_nsec = 0;
if (syscall(__NR_utimensat, AT_FDCWD, "tttsym", t, AT_SYMLINK_NOFOLLOW) != 0)
error (1, errno, "utimensat failed");

if (lstat64 ("tttsym", &st2) != 0)
error (1, errno, "lstat failed");

if (st2.st_atim.tv_sec != 0 || st2.st_atim.tv_nsec != 0)
{
puts ("symlink atim not reset to zero");
status = 1;
}
if (st2.st_mtim.tv_sec != 0 || st2.st_mtim.tv_nsec != 0)
{
puts ("symlink mtim not reset to zero");
status = 1;
}
if (status != 0)
goto out;

t[0].tv_sec = 1;
t[0].tv_nsec = 0;
t[1].tv_sec = 1;
t[1].tv_nsec = 0;
if (syscall(__NR_utimensat, fd, NULL, t, 0) != 0)
error (1, errno, "utimensat failed");

if (fstat64 (fd, &st2) != 0)
error (1, errno, "fstat failed");

if (st2.st_atim.tv_sec != 1 || st2.st_atim.tv_nsec != 0)
{
puts ("atim not reset to one");
status = 1;
}
if (st2.st_mtim.tv_sec != 1 || st2.st_mtim.tv_nsec != 0)
{
puts ("mtim not reset to one");
status = 1;
}

if (status == 0)
puts ("all OK");

out:
close (fd);
unlink ("ttt");
unlink ("tttsym");

return status;
}

[akpm@linux-foundation.org: add missing i386 syscall table entry]
Signed-off-by: Ulrich Drepper <drepper@redhat.com>
Cc: Alexey Dobriyan <adobriyan@openvz.org>
Cc: Michael Kerrisk <mtk-manpages@gmx.net>
Cc: <linux-arch@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Ulrich Drepper and committed by
Linus Torvalds
1c710c89 ade5fb81

+172 -75
+12 -2
arch/alpha/kernel/osf_sys.c
··· 953 953 asmlinkage int 954 954 osf_utimes(char __user *filename, struct timeval32 __user *tvs) 955 955 { 956 - struct timeval ktvs[2]; 956 + struct timespec tv[2]; 957 957 958 958 if (tvs) { 959 + struct timeval ktvs[2]; 959 960 if (get_tv32(&ktvs[0], &tvs[0]) || 960 961 get_tv32(&ktvs[1], &tvs[1])) 961 962 return -EFAULT; 963 + 964 + if (ktvs[0].tv_usec < 0 || ktvs[0].tv_usec >= 1000000 || 965 + ktvs[1].tv_usec < 0 || ktvs[1].tv_usec >= 1000000) 966 + return -EINVAL; 967 + 968 + tv[0].tv_sec = ktvs[0].tv_sec; 969 + tv[0].tv_nsec = 1000 * ktvs[0].tv_usec; 970 + tv[1].tv_sec = ktvs[1].tv_sec; 971 + tv[1].tv_nsec = 1000 * ktvs[1].tv_usec; 962 972 } 963 973 964 - return do_utimes(AT_FDCWD, filename, tvs ? ktvs : NULL); 974 + return do_utimes(AT_FDCWD, filename, tvs ? tv : NULL, 0); 965 975 } 966 976 967 977 #define MAX_SELECT_SECONDS \
+1
arch/i386/kernel/syscall_table.S
··· 319 319 .long sys_move_pages 320 320 .long sys_getcpu 321 321 .long sys_epoll_pwait 322 + .long sys_utimensat /* 320 */
+12 -2
arch/sparc64/kernel/sys_sparc32.c
··· 775 775 asmlinkage long sys32_utimes(char __user *filename, 776 776 struct compat_timeval __user *tvs) 777 777 { 778 - struct timeval ktvs[2]; 778 + struct timespec tv[2]; 779 779 780 780 if (tvs) { 781 + struct timeval ktvs[2]; 781 782 if (get_tv32(&ktvs[0], tvs) || 782 783 get_tv32(&ktvs[1], 1+tvs)) 783 784 return -EFAULT; 785 + 786 + if (ktvs[0].tv_usec < 0 || ktvs[0].tv_usec >= 1000000 || 787 + ktvs[1].tv_usec < 0 || ktvs[1].tv_usec >= 1000000) 788 + return -EINVAL; 789 + 790 + tv[0].tv_sec = ktvs[0].tv_sec; 791 + tv[0].tv_nsec = 1000 * ktvs[0].tv_usec; 792 + tv[1].tv_sec = ktvs[1].tv_sec; 793 + tv[1].tv_nsec = 1000 * ktvs[1].tv_usec; 784 794 } 785 795 786 - return do_utimes(AT_FDCWD, filename, (tvs ? &ktvs[0] : NULL)); 796 + return do_utimes(AT_FDCWD, filename, tvs ? tv : NULL); 787 797 } 788 798 789 799 /* These are here just in case some old sparc32 binary calls it. */
+2 -1
arch/x86_64/ia32/ia32entry.S
··· 710 710 .quad compat_sys_get_robust_list 711 711 .quad sys_splice 712 712 .quad sys_sync_file_range 713 - .quad sys_tee 713 + .quad sys_tee /* 315 */ 714 714 .quad compat_sys_vmsplice 715 715 .quad compat_sys_move_pages 716 716 .quad sys_getcpu 717 717 .quad sys_epoll_pwait 718 + .quad compat_sys_utimensat /* 320 */ 718 719 ia32_syscall_end:
+35 -8
fs/compat.c
··· 77 77 */ 78 78 asmlinkage long compat_sys_utime(char __user *filename, struct compat_utimbuf __user *t) 79 79 { 80 - struct timeval tv[2]; 80 + struct timespec tv[2]; 81 81 82 82 if (t) { 83 83 if (get_user(tv[0].tv_sec, &t->actime) || 84 84 get_user(tv[1].tv_sec, &t->modtime)) 85 85 return -EFAULT; 86 - tv[0].tv_usec = 0; 87 - tv[1].tv_usec = 0; 86 + tv[0].tv_nsec = 0; 87 + tv[1].tv_nsec = 0; 88 88 } 89 - return do_utimes(AT_FDCWD, filename, t ? tv : NULL); 89 + return do_utimes(AT_FDCWD, filename, t ? tv : NULL, 0); 90 + } 91 + 92 + asmlinkage long compat_sys_utimensat(unsigned int dfd, char __user *filename, struct compat_timespec __user *t, int flags) 93 + { 94 + struct timespec tv[2]; 95 + 96 + if (t) { 97 + if (get_compat_timespec(&tv[0], &t[0]) || 98 + get_compat_timespec(&tv[1], &t[1])) 99 + return -EFAULT; 100 + 101 + if ((tv[0].tv_nsec == UTIME_OMIT || tv[0].tv_nsec == UTIME_NOW) 102 + && tv[0].tv_sec != 0) 103 + return -EINVAL; 104 + if ((tv[1].tv_nsec == UTIME_OMIT || tv[1].tv_nsec == UTIME_NOW) 105 + && tv[1].tv_sec != 0) 106 + return -EINVAL; 107 + 108 + if (tv[0].tv_nsec == UTIME_OMIT && tv[1].tv_nsec == UTIME_OMIT) 109 + return 0; 110 + } 111 + return do_utimes(dfd, filename, t ? tv : NULL, flags); 90 112 } 91 113 92 114 asmlinkage long compat_sys_futimesat(unsigned int dfd, char __user *filename, struct compat_timeval __user *t) 93 115 { 94 - struct timeval tv[2]; 116 + struct timespec tv[2]; 95 117 96 118 if (t) { 97 119 if (get_user(tv[0].tv_sec, &t[0].tv_sec) || 98 - get_user(tv[0].tv_usec, &t[0].tv_usec) || 120 + get_user(tv[0].tv_nsec, &t[0].tv_usec) || 99 121 get_user(tv[1].tv_sec, &t[1].tv_sec) || 100 - get_user(tv[1].tv_usec, &t[1].tv_usec)) 122 + get_user(tv[1].tv_nsec, &t[1].tv_usec)) 101 123 return -EFAULT; 124 + if (tv[0].tv_nsec >= 1000000 || tv[0].tv_nsec < 0 || 125 + tv[1].tv_nsec >= 1000000 || tv[1].tv_nsec < 0) 126 + return -EINVAL; 127 + tv[0].tv_nsec *= 1000; 128 + tv[1].tv_nsec *= 1000; 102 129 } 103 - return do_utimes(dfd, filename, t ? tv : NULL); 130 + return do_utimes(dfd, filename, t ? tv : NULL, 0); 104 131 } 105 132 106 133 asmlinkage long compat_sys_utimes(char __user *filename, struct compat_timeval __user *t)
+102 -60
fs/utimes.c
··· 1 1 #include <linux/compiler.h> 2 + #include <linux/file.h> 2 3 #include <linux/fs.h> 3 4 #include <linux/linkage.h> 4 5 #include <linux/namei.h> 5 6 #include <linux/sched.h> 7 + #include <linux/stat.h> 6 8 #include <linux/utime.h> 7 9 #include <asm/uaccess.h> 8 10 #include <asm/unistd.h> ··· 22 20 * must be owner or have write permission. 23 21 * Else, update from *times, must be owner or super user. 24 22 */ 25 - asmlinkage long sys_utime(char __user * filename, struct utimbuf __user * times) 23 + asmlinkage long sys_utime(char __user *filename, struct utimbuf __user *times) 26 24 { 27 - int error; 28 - struct nameidata nd; 29 - struct inode * inode; 30 - struct iattr newattrs; 25 + struct timespec tv[2]; 31 26 32 - error = user_path_walk(filename, &nd); 33 - if (error) 34 - goto out; 35 - inode = nd.dentry->d_inode; 36 - 37 - error = -EROFS; 38 - if (IS_RDONLY(inode)) 39 - goto dput_and_out; 40 - 41 - /* Don't worry, the checks are done in inode_change_ok() */ 42 - newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME; 43 27 if (times) { 44 - error = -EPERM; 45 - if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) 46 - goto dput_and_out; 47 - 48 - error = get_user(newattrs.ia_atime.tv_sec, &times->actime); 49 - newattrs.ia_atime.tv_nsec = 0; 50 - if (!error) 51 - error = get_user(newattrs.ia_mtime.tv_sec, &times->modtime); 52 - newattrs.ia_mtime.tv_nsec = 0; 53 - if (error) 54 - goto dput_and_out; 55 - 56 - newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; 57 - } else { 58 - error = -EACCES; 59 - if (IS_IMMUTABLE(inode)) 60 - goto dput_and_out; 61 - 62 - if (current->fsuid != inode->i_uid && 63 - (error = vfs_permission(&nd, MAY_WRITE)) != 0) 64 - goto dput_and_out; 28 + if (get_user(tv[0].tv_sec, &times->actime) || 29 + get_user(tv[1].tv_sec, &times->modtime)) 30 + return -EFAULT; 31 + tv[0].tv_nsec = 0; 32 + tv[1].tv_nsec = 0; 65 33 } 66 - mutex_lock(&inode->i_mutex); 67 - error = notify_change(nd.dentry, &newattrs); 68 - mutex_unlock(&inode->i_mutex); 69 - dput_and_out: 70 - path_release(&nd); 71 - out: 72 - return error; 34 + return do_utimes(AT_FDCWD, filename, times ? tv : NULL, 0); 73 35 } 74 36 75 37 #endif ··· 42 76 * must be owner or have write permission. 43 77 * Else, update from *times, must be owner or super user. 44 78 */ 45 - long do_utimes(int dfd, char __user *filename, struct timeval *times) 79 + long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags) 46 80 { 47 81 int error; 48 82 struct nameidata nd; 49 - struct inode * inode; 83 + struct dentry *dentry; 84 + struct inode *inode; 50 85 struct iattr newattrs; 86 + struct file *f = NULL; 51 87 52 - error = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW, &nd); 53 - 54 - if (error) 88 + error = -EINVAL; 89 + if (flags & ~AT_SYMLINK_NOFOLLOW) 55 90 goto out; 56 - inode = nd.dentry->d_inode; 91 + 92 + if (filename == NULL && dfd != AT_FDCWD) { 93 + error = -EINVAL; 94 + if (flags & AT_SYMLINK_NOFOLLOW) 95 + goto out; 96 + 97 + error = -EBADF; 98 + f = fget(dfd); 99 + if (!f) 100 + goto out; 101 + dentry = f->f_path.dentry; 102 + } else { 103 + error = __user_walk_fd(dfd, filename, (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW, &nd); 104 + if (error) 105 + goto out; 106 + 107 + dentry = nd.dentry; 108 + } 109 + 110 + inode = dentry->d_inode; 57 111 58 112 error = -EROFS; 59 113 if (IS_RDONLY(inode)) ··· 86 100 if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) 87 101 goto dput_and_out; 88 102 89 - newattrs.ia_atime.tv_sec = times[0].tv_sec; 90 - newattrs.ia_atime.tv_nsec = times[0].tv_usec * 1000; 91 - newattrs.ia_mtime.tv_sec = times[1].tv_sec; 92 - newattrs.ia_mtime.tv_nsec = times[1].tv_usec * 1000; 93 - newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; 103 + if (times[0].tv_nsec == UTIME_OMIT) 104 + newattrs.ia_valid &= ~ATTR_ATIME; 105 + else if (times[0].tv_nsec != UTIME_NOW) { 106 + newattrs.ia_atime.tv_sec = times[0].tv_sec; 107 + newattrs.ia_atime.tv_nsec = times[0].tv_nsec; 108 + newattrs.ia_valid |= ATTR_ATIME_SET; 109 + } 110 + 111 + if (times[1].tv_nsec == UTIME_OMIT) 112 + newattrs.ia_valid &= ~ATTR_MTIME; 113 + else if (times[1].tv_nsec != UTIME_NOW) { 114 + newattrs.ia_mtime.tv_sec = times[1].tv_sec; 115 + newattrs.ia_mtime.tv_nsec = times[1].tv_nsec; 116 + newattrs.ia_valid |= ATTR_MTIME_SET; 117 + } 94 118 } else { 95 119 error = -EACCES; 96 120 if (IS_IMMUTABLE(inode)) ··· 111 115 goto dput_and_out; 112 116 } 113 117 mutex_lock(&inode->i_mutex); 114 - error = notify_change(nd.dentry, &newattrs); 118 + error = notify_change(dentry, &newattrs); 115 119 mutex_unlock(&inode->i_mutex); 116 120 dput_and_out: 117 - path_release(&nd); 121 + if (f) 122 + fput(f); 123 + else 124 + path_release(&nd); 118 125 out: 119 126 return error; 127 + } 128 + 129 + asmlinkage long sys_utimensat(int dfd, char __user *filename, struct timespec __user *utimes, int flags) 130 + { 131 + struct timespec tstimes[2]; 132 + 133 + if (utimes) { 134 + if (copy_from_user(&tstimes, utimes, sizeof(tstimes))) 135 + return -EFAULT; 136 + if ((tstimes[0].tv_nsec == UTIME_OMIT || 137 + tstimes[0].tv_nsec == UTIME_NOW) && 138 + tstimes[0].tv_sec != 0) 139 + return -EINVAL; 140 + if ((tstimes[1].tv_nsec == UTIME_OMIT || 141 + tstimes[1].tv_nsec == UTIME_NOW) && 142 + tstimes[1].tv_sec != 0) 143 + return -EINVAL; 144 + 145 + /* Nothing to do, we must not even check the path. */ 146 + if (tstimes[0].tv_nsec == UTIME_OMIT && 147 + tstimes[1].tv_nsec == UTIME_OMIT) 148 + return 0; 149 + } 150 + 151 + return do_utimes(dfd, filename, utimes ? tstimes : NULL, flags); 120 152 } 121 153 122 154 asmlinkage long sys_futimesat(int dfd, char __user *filename, struct timeval __user *utimes) 123 155 { 124 156 struct timeval times[2]; 157 + struct timespec tstimes[2]; 125 158 126 - if (utimes && copy_from_user(&times, utimes, sizeof(times))) 127 - return -EFAULT; 128 - return do_utimes(dfd, filename, utimes ? times : NULL); 159 + if (utimes) { 160 + if (copy_from_user(&times, utimes, sizeof(times))) 161 + return -EFAULT; 162 + 163 + /* This test is needed to catch all invalid values. If we 164 + would test only in do_utimes we would miss those invalid 165 + values truncated by the multiplication with 1000. Note 166 + that we also catch UTIME_{NOW,OMIT} here which are only 167 + valid for utimensat. */ 168 + if (times[0].tv_usec >= 1000000 || times[0].tv_usec < 0 || 169 + times[1].tv_usec >= 1000000 || times[1].tv_usec < 0) 170 + return -EINVAL; 171 + 172 + tstimes[0].tv_sec = times[0].tv_sec; 173 + tstimes[0].tv_nsec = 1000 * times[0].tv_usec; 174 + tstimes[1].tv_sec = times[1].tv_sec; 175 + tstimes[1].tv_nsec = 1000 * times[1].tv_usec; 176 + } 177 + 178 + return do_utimes(dfd, filename, utimes ? tstimes : NULL, 0); 129 179 } 130 180 131 181 asmlinkage long sys_utimes(char __user *filename, struct timeval __user *utimes)
+2 -1
include/asm-i386/unistd.h
··· 325 325 #define __NR_move_pages 317 326 326 #define __NR_getcpu 318 327 327 #define __NR_epoll_pwait 319 328 + #define __NR_utimensat 320 328 329 329 330 #ifdef __KERNEL__ 330 331 331 - #define NR_syscalls 320 332 + #define NR_syscalls 321 332 333 333 334 #define __ARCH_WANT_IPC_PARSE_VERSION 334 335 #define __ARCH_WANT_OLD_READDIR
+2
include/asm-x86_64/unistd.h
··· 619 619 __SYSCALL(__NR_vmsplice, sys_vmsplice) 620 620 #define __NR_move_pages 279 621 621 __SYSCALL(__NR_move_pages, sys_move_pages) 622 + #define __NR_utimensat 280 623 + __SYSCALL(__NR_utimensat, sys_utimensat) 622 624 623 625 #ifndef __NO_STUBS 624 626 #define __ARCH_WANT_OLD_READDIR
+3
include/linux/stat.h
··· 53 53 #define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH) 54 54 #define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH) 55 55 56 + #define UTIME_NOW ((1l << 30) - 1l) 57 + #define UTIME_OMIT ((1l << 30) - 2l) 58 + 56 59 #include <linux/types.h> 57 60 #include <linux/time.h> 58 61
+1 -1
include/linux/time.h
··· 109 109 extern int do_settimeofday(struct timespec *tv); 110 110 extern int do_sys_settimeofday(struct timespec *tv, struct timezone *tz); 111 111 #define do_posix_clock_monotonic_gettime(ts) ktime_get_ts(ts) 112 - extern long do_utimes(int dfd, char __user *filename, struct timeval *times); 112 + extern long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags); 113 113 struct itimerval; 114 114 extern int do_setitimer(int which, struct itimerval *value, 115 115 struct itimerval *ovalue);