diff --git a/configure.in b/configure.in
index af5274d..d41d70b 100644
--- a/configure.in
+++ b/configure.in
@@ -1291,7 +1291,7 @@ else
fi
AC_CHECK_FUNCS(fmod killpg wait4 waitpid fork spawnv syscall chroot getcwd eaccess\
truncate ftruncate chsize times utimes utimensat fcntl lockf lstat\
- link symlink readlink readdir_r fsync fdatasync fchown\
+ link lutimes symlink readlink readdir_r fsync fdatasync fchown\
setitimer setruid seteuid setreuid setresuid setproctitle socketpair\
setrgid setegid setregid setresgid issetugid pause lchown lchmod\
getpgrp setpgrp getpgid setpgid initgroups getgroups setgroups\
diff --git a/file.c b/file.c
index dc9f830..0e80731 100644
--- a/file.c
+++ b/file.c
@@ -2195,6 +2195,7 @@ struct timespec rb_time_timespec(VALUE time);
struct utime_args {
const struct timespec* tsp;
VALUE atime, mtime;
+ int follow; /* Whether to act on symlinks (1) or their referent (0) */
};
#if defined DOSISH || defined __CYGWIN__
@@ -2246,11 +2247,24 @@ utime_internal(const char *path, void *arg)
#ifdef HAVE_UTIMENSAT
static int try_utimensat = 1;
+ static int try_utimensat_follow = 1;
+ int flags = 0;
- if (try_utimensat) {
- if (utimensat(AT_FDCWD, path, tsp, 0) < 0) {
+ if ((v->follow && try_utimensat_follow) || (!v->follow && try_utimensat)) {
+ if (v->follow) {
+#ifdef AT_SYMLINK_NOFOLLOW
+ flags = AT_SYMLINK_NOFOLLOW;
+#else
+ try_utimensat_follow = 0;
+ goto no_utimensat;
+#endif
+ }
+
+ if (utimensat(AT_FDCWD, path, tsp, flags) < 0) {
if (errno == ENOSYS) {
- try_utimensat = 0;
+ try_utimensat_follow = 0;
+ if (!v->follow)
+ try_utimensat = 0;
goto no_utimensat;
}
utime_failed(path, tsp, v->atime, v->mtime);
@@ -2267,7 +2281,16 @@ no_utimensat:
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
tvp = tvbuf;
}
- if (utimes(path, tvp) < 0)
+
+ if (v->follow) {
+#ifdef HAVE_LUTIMES
+ if (lutimes(path, tvp) < 0)
+ utime_failed(path, tsp, v->atime, v->mtime);
+#else
+ rb_notimplement();
+#endif
+ }
+ else if (utimes(path, tvp) < 0)
utime_failed(path, tsp, v->atime, v->mtime);
}
@@ -2297,17 +2320,8 @@ utime_internal(const char *path, void *arg)
#endif
-/*
- * call-seq:
- * File.utime(atime, mtime, file_name,...) -> integer
- *
- * Sets the access and modification times of each
- * named file to the first two arguments. Returns
- * the number of file names in the argument list.
- */
-
static VALUE
-rb_file_s_utime(int argc, VALUE *argv)
+utime_internal_i(int argc, VALUE *argv, int follow)
{
VALUE rest;
struct utime_args args;
@@ -2317,6 +2331,8 @@ rb_file_s_utime(int argc, VALUE *argv)
rb_secure(2);
rb_scan_args(argc, argv, "2*", &args.atime, &args.mtime, &rest);
+ args.follow = follow;
+
if (!NIL_P(args.atime) || !NIL_P(args.mtime)) {
tsp = tss;
tsp[0] = rb_time_timespec(args.atime);
@@ -2328,6 +2344,45 @@ rb_file_s_utime(int argc, VALUE *argv)
return LONG2FIX(n);
}
+/*
+ * call-seq:
+ * File.utime(atime, mtime, file_name,...) -> integer
+ *
+ * Sets the access and modification times of each named file to the
+ * first two arguments. If a file is a symlink, this method acts upon
+ * its referent rather than the link itself; for the inverse
+ * behavior see File.lutime
. Returns the number of file
+ * names in the argument list.
+ */
+
+static VALUE
+rb_file_s_utime(int argc, VALUE *argv)
+{
+ return utime_internal_i(argc, argv, 0);
+}
+
+#if defined(HAVE_UTIMES) && (defined(HAVE_LUTIMES) || (defined(HAVE_UTIMENSAT) && defined(AT_SYMLINK_NOFOLLOW)))
+
+/*
+ * call-seq:
+ * File.lutime(atime, mtime, file_name,...) -> integer
+ *
+ * Sets the access and modification times of each named file to the
+ * first two arguments. If a file is a symlink, this method acts upon
+ * the link itself as opposed to its referent; for the inverse
+ * behavior, see File.utime
. Returns the number of file
+ * names in the argument list.
+ */
+
+static VALUE
+rb_file_s_lutime(int argc, VALUE *argv)
+{
+ return utime_internal_i(argc, argv, 1);
+}
+#else
+#define rb_file_s_lutime rb_f_notimplement
+#endif
+
NORETURN(static void sys_fail2(VALUE,VALUE));
static void
sys_fail2(VALUE s1, VALUE s2)
@@ -5319,6 +5374,7 @@ Init_File(void)
rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1);
rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1);
+ rb_define_singleton_method(rb_cFile, "lutime", rb_file_s_lutime, -1);
rb_define_singleton_method(rb_cFile, "link", rb_file_s_link, 2);
rb_define_singleton_method(rb_cFile, "symlink", rb_file_s_symlink, 2);
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 1d45b97..1d16c3d 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -346,6 +346,41 @@ class TestFileExhaustive < Test::Unit::TestCase
File.utime(t + 1, t + 2, @zerofile)
assert_equal(t + 1, File.atime(@zerofile))
assert_equal(t + 2, File.mtime(@zerofile))
+ skip("File.symlink not implemented") unless (File.respond_to?(:symlink) and @symlinkfile)
+ stat_ln = File.lstat(@symlinkfile)
+ assert_equal(File.utime(t, t, @symlinkfile), 1)
+ assert_equal(File.stat(@file).atime, t)
+ assert_equal(File.stat(@file).mtime, t)
+ assert_equal(File.lstat(@symlinkfile).atime, stat_ln.atime)
+ assert_equal(File.lstat(@symlinkfile).mtime, stat_ln.mtime)
+ end
+
+ def test_lutime
+ skip("File.lutime not implemented") unless File.respond_to?(:lutime)
+ Dir.mktmpdir('rubytest-lutime') do |tmp|
+ Dir.chdir(tmp) do
+ open('f',?w){}
+ times = [Time.now - 24*100, Time.now - 24*1_000].map(&:round)
+ assert_equal(File.lutime(times[0], times[0], ?f), 1)
+ assert_equal(File.stat(?f).atime.round, times[0])
+ assert_equal(File.stat(?f).mtime.round, times[0])
+
+ # Assume that a platform supporting changing utimens on symlinks
+ # also supports symlinks
+ File.symlink(?f, ?l)
+ assert_equal(File.lutime(times[1], times[1], ?l), 1)
+ assert_equal(File.stat(?f).atime.round, times[0])
+ assert_equal(File.stat(?f).mtime.round, times[0])
+ assert_equal(File.lstat(?l).atime.round, times[1])
+ assert_equal(File.lstat(?l).mtime.round, times[1])
+
+ assert_equal(File.lutime(times[1], times[0], ?l, ?f), 2)
+ assert_equal(File.stat(?f).atime.round, times[1])
+ assert_equal(File.stat(?f).mtime.round, times[0])
+ assert_equal(File.lstat(?l).atime.round, times[1])
+ assert_equal(File.lstat(?l).mtime.round, times[0])
+ end
+ end
end
def test_hardlink