aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--configure.ac1
-rw-r--r--file.c79
-rw-r--r--test/ruby/test_file_exhaustive.rb30
4 files changed, 98 insertions, 13 deletions
diff --git a/NEWS b/NEWS
index 46bab4af1b..e313f3914b 100644
--- a/NEWS
+++ b/NEWS
@@ -46,6 +46,7 @@ with all sufficient information, see the ChangeLog file or Redmine
* File.stat, File.exist?, and other rb_stat()-using methods release GVL
[Bug #13941]
* File.rename releases GVL [Feature #13951]
+ * Add File.lutime [Feature #4052]
* Hash
diff --git a/configure.ac b/configure.ac
index 6c15929f05..f253cc61bb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2328,6 +2328,7 @@ AC_CHECK_FUNCS(llabs)
AC_CHECK_FUNCS(lockf)
AC_CHECK_FUNCS(log2)
AC_CHECK_FUNCS(lstat)
+AC_CHECK_FUNCS(lutime)
AC_CHECK_FUNCS(malloc_usable_size)
AC_CHECK_FUNCS(malloc_size)
AC_CHECK_FUNCS(mblen)
diff --git a/file.c b/file.c
index 31349de5df..a74aac69d3 100644
--- a/file.c
+++ b/file.c
@@ -2662,6 +2662,7 @@ rb_file_s_lchown(int argc, VALUE *argv)
struct utime_args {
const struct timespec* tsp;
VALUE atime, mtime;
+ int follow; /* Whether to act on symlinks (1) or their referent (0) */
};
#ifdef UTIME_EINVAL
@@ -2717,11 +2718,27 @@ utime_internal(const char *path, void *arg)
#if defined(HAVE_UTIMENSAT)
static int try_utimensat = 1;
+# ifdef AT_SYMLINK_NOFOLLOW
+ static int try_utimensat_follow = 1;
+# else
+ const int try_utimensat_follow = 0;
+# endif
+ int flags = 0;
+
+ if (v->follow ? try_utimensat_follow : try_utimensat) {
+# ifdef AT_SYMLINK_NOFOLLOW
+ if (v->follow) {
+ flags = AT_SYMLINK_NOFOLLOW;
+ }
+# endif
- if (try_utimensat) {
- if (utimensat(AT_FDCWD, path, tsp, 0) < 0) {
+ if (utimensat(AT_FDCWD, path, tsp, flags) < 0) {
if (errno == ENOSYS) {
- try_utimensat = 0;
+# ifdef AT_SYMLINK_NOFOLLOW
+ try_utimensat_follow = 0;
+# endif
+ if (!v->follow)
+ try_utimensat = 0;
goto no_utimensat;
}
return -1; /* calls utime_failed */
@@ -2738,6 +2755,9 @@ no_utimensat:
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
tvp = tvbuf;
}
+#ifdef HAVE_LUTIMES
+ if (v->follow) return lutimes(path, tvp);
+#endif
return utimes(path, tvp);
}
@@ -2766,17 +2786,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)
{
struct utime_args args;
struct timespec tss[2], *tsp = NULL;
@@ -2785,6 +2796,8 @@ rb_file_s_utime(int argc, VALUE *argv)
args.atime = *argv++;
args.mtime = *argv++;
+ args.follow = follow;
+
if (!NIL_P(args.atime) || !NIL_P(args.mtime)) {
tsp = tss;
tsp[0] = rb_time_timespec(args.atime);
@@ -2798,6 +2811,45 @@ rb_file_s_utime(int argc, VALUE *argv)
return apply2files(utime_internal, argc, argv, &args);
}
+/*
+ * 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 <code>File.lutime</code>. 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, FALSE);
+}
+
+#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 <code>File.utime</code>. 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, TRUE);
+}
+#else
+#define rb_file_s_lutime rb_f_notimplement
+#endif
+
#ifdef RUBY_FUNCTION_NAME_STRING
# define syserr_fail2(e, s1, s2) syserr_fail2_in(RUBY_FUNCTION_NAME_STRING, e, s1, s2)
#else
@@ -6259,6 +6311,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 4c5540eb4c..6f2fbb1bd2 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -647,6 +647,36 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(t + 2, File.mtime(zerofile))
end
+ def test_utime_symlinkfile
+ return unless symlinkfile
+ t = Time.local(2000)
+ stat = File.lstat(symlinkfile)
+ assert_equal(File.utime(t, t, symlinkfile), 1)
+ assert_equal(File.stat(regular_file).atime, t)
+ assert_equal(File.stat(regular_file).mtime, t)
+ assert_equal(File.lstat(symlinkfile).atime, stat.atime)
+ assert_equal(File.lstat(symlinkfile).mtime, stat.mtime)
+ end
+
+ def test_lutime
+ return unless File.respond_to?(:lutime)
+ return unless symlinkfile
+
+ r = File.stat(regular_file)
+ t = Time.local(2000)
+ File.lutime(t + 1, t + 2, symlinkfile)
+ rescue NotImplementedError => e
+ skip(e.message)
+ else
+ stat = File.stat(regular_file)
+ assert_equal(r.atime, stat.atime)
+ assert_equal(r.mtime, stat.mtime)
+
+ stat = File.lstat(symlinkfile)
+ assert_equal(t + 1, stat.atime)
+ assert_equal(t + 2, stat.mtime)
+ end
+
def test_hardlink
return unless hardlinkfile
assert_equal("file", File.ftype(hardlinkfile))