Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
diff --git a/configure.in b/configure.in
index a3d8a67..1bf0d29 100644
--- a/configure.in
+++ b/configure.in
@@ -1504,7 +1504,7 @@ else
AC_LIBOBJ([signbit])
fi
AC_CHECK_FUNCS(fmod killpg wait4 waitpid fork spawnv syscall __syscall chroot getcwd eaccess\
- truncate ftruncate ftello chsize times utimes utimensat fcntl lockf lstat\
+ truncate ftruncate ftello chsize times utimes lutimes utimensat fcntl lockf lstat\
truncate64 ftruncate64 ftello64 fseeko fseeko64 \
link symlink readlink readdir_r fsync fdatasync fchown posix_fadvise\
setitimer setruid seteuid setreuid setresuid socketpair\
diff --git a/file.c b/file.c
index 12622a3..3627a64 100644
--- a/file.c
+++ b/file.c
@@ -91,7 +91,9 @@ int flock(int, int);
#undef chown
#define chown(p, o, g) rb_w32_uchown((p), (o), (g))
#undef utime
-#define utime(p, t) rb_w32_uutime((p), (t))
+#define utimes(p, t) rb_w32_uutime((p), (t))
+#undef lutime
+#define lutimes(p, t) rb_w32_luutime((p), (t))
#undef link
#define link(f, t) rb_w32_ulink((f), (t))
#undef unlink
@@ -2226,11 +2228,23 @@ rb_file_s_lchown(int argc, VALUE *argv)
#define rb_file_s_lchown rb_f_notimplement
#endif
+typedef enum utime_mode utime_mode_t;
+
+enum utime_mode {
+ M_SYMLINK_FOLLOW = 0,
+ M_SYMLINK_NOFOLLOW
+};
+
struct utime_args {
const struct timespec* tsp;
VALUE atime, mtime;
+ utime_mode_t mode;
};
+#if !defined(FOLLOW_SYMLINK_P)
+# define FOLLOW_SYMLINK_P(x) ((x)->mode == M_SYMLINK_FOLLOW)
+#endif
+
#if defined DOSISH || defined __CYGWIN__
NORETURN(static void utime_failed(VALUE, const struct timespec *, VALUE, VALUE));
@@ -2272,7 +2286,7 @@ utime_failed(VALUE path, const struct timespec *tsp, VALUE atime, VALUE mtime)
#if defined(HAVE_UTIMES)
static void
-utime_internal(const char *path, VALUE pathv, void *arg)
+utime_helper(const char *path, VALUE pathv, void *arg)
{
struct utime_args *v = arg;
const struct timespec *tsp = v->tsp;
@@ -2280,9 +2294,10 @@ utime_internal(const char *path, VALUE pathv, void *arg)
#ifdef HAVE_UTIMENSAT
static int try_utimensat = 1;
+ const int flags = FOLLOW_SYMLINK_P(v) ? 0 : AT_SYMLINK_NOFOLLOW;
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;
goto no_utimensat;
@@ -2301,7 +2316,7 @@ no_utimensat:
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
tvp = tvbuf;
}
- if (utimes(path, tvp) < 0)
+ if ((FOLLOW_SYMLINK_P(v) ? utimes(path, tvp) : lutimes(path, tvp)) < 0)
utime_failed(pathv, tsp, v->atime, v->mtime);
}
@@ -2315,7 +2330,7 @@ struct utimbuf {
#endif
static void
-utime_internal(const char *path, VALUE pathv, void *arg)
+utime_helper(const char *path, VALUE pathv, void *arg)
{
struct utime_args *v = arg;
const struct timespec *tsp = v->tsp;
@@ -2325,23 +2340,14 @@ utime_internal(const char *path, VALUE pathv, void *arg)
utbuf.modtime = tsp[1].tv_sec;
utp = &utbuf;
}
- if (utime(path, utp) < 0)
+ if ((FOLLOW_SYMLINK_P(v) ? utimes(path, utp) : lutimes(path, utp)) < 0)
utime_failed(pathv, tsp, v->atime, v->mtime);
}
#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(int argc, VALUE *argv, utime_mode_t mode)
{
VALUE rest;
struct utime_args args;
@@ -2357,11 +2363,58 @@ rb_file_s_utime(int argc, VALUE *argv)
tsp[1] = rb_time_timespec(args.mtime);
}
args.tsp = tsp;
+#ifdef HAVE_LUTIMES
+ args.mode = mode;
+#else
+ args.mode = M_SYMLINK_FOLLOW;
+#endif
- n = apply2files(utime_internal, rest, &args);
+ n = apply2files(utime_helper, rest, &args);
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. Returns
+ * the number of file names in the argument list.
+ * See also File::lutime.
+ */
+
+static inline VALUE
+rb_file_s_utime(int argc, VALUE *argv)
+{
+ return utime_internal(argc, argv, M_SYMLINK_FOLLOW);
+}
+
+#ifdef HAVE_LUTIMES
+/*
+ * call-seq:
+ * File.lutime(atime, mtime, file_name,...) -> integer
+ *
+ * Equivalent to File::utime, but does not follow
+ * symbolic links (so it will change the +atime+
+ * and +mtime+ associated with the link, not the
+ * file referenced by the link). Often not available.
+ *
+ * See also File::utime.
+ */
+
+static VALUE
+rb_file_s_lutime(int argc, VALUE *argv)
+{
+ return utime_internal(argc, argv, M_SYMLINK_NOFOLLOW);
+}
+#else
+#define rb_file_s_lutime rb_f_notimplement
+#endif
+
+#if defined(FOLLOW_SYMLINK_P)
+# undef FOLLOW_SYMLINK_P
+#endif
+
NORETURN(static void sys_fail2(VALUE,VALUE));
static void
sys_fail2(VALUE s1, VALUE s2)
@@ -5463,6 +5516,7 @@ Init_File(void)
rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1);
rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1);
+ rb_define_singleton_method(rb_cFile, "lutime", rb_file_s_lutime, -1);
rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1);
rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1);
rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
diff --git a/include/ruby/win32.h b/include/ruby/win32.h
index 9645cc8..a6af09a 100644
--- a/include/ruby/win32.h
+++ b/include/ruby/win32.h
@@ -417,6 +417,9 @@ extern int rb_w32_fseeko(FILE *stream, off_t offset, int whence);
#define ftello rb_w32_ftello
#endif
+#undef HAVE_LUTIMES
+#define HAVE_LUTIMES 1
+
/*
* stubs
*/
@@ -718,6 +721,8 @@ ssize_t rb_w32_read(int, void *, size_t);
ssize_t rb_w32_write(int, const void *, size_t);
int rb_w32_utime(const char *, const struct utimbuf *);
int rb_w32_uutime(const char *, const struct utimbuf *);
+int rb_w32_lutime(const char *, const struct utimbuf *);
+int rb_w32_luutime(const char *, const struct utimbuf *);
long rb_w32_write_console(uintptr_t, int); /* use uintptr_t instead of VALUE because it's not defined yet here */
int WINAPI rb_w32_Sleep(unsigned long msec);
int rb_w32_wait_events_blocking(HANDLE *events, int num, DWORD timeout);
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 3d74eac..c81747e 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -1178,7 +1178,7 @@ private
public
#
- # Options: noop verbose
+ # Options: noop verbose mtime nocreate nofollow
#
# Updates modification time (mtime) and access time (atime) of file(s) in
# +list+. Files are created if they don't exist.
@@ -1198,7 +1198,11 @@ public
list.each do |path|
created = nocreate
begin
- File.utime(t, t, path)
+ if File.symlink?(path) and options[:nofollow]
+ File.lutime(t, t, path)
+ else
+ File.utime(t, t, path)
+ end
rescue Errno::ENOENT
raise if created
File.open(path, 'a') {
@@ -1210,7 +1214,7 @@ public
end
end
- define_command('touch', :noop, :verbose, :mtime, :nocreate)
+ define_command('touch', :noop, :verbose, :mtime, :nocreate, :nofollow)
private
@@ -1439,7 +1443,12 @@ private
def copy_metadata(path)
st = lstat()
- if !st.symlink?
+ if st.symlink?
+ begin
+ File.lutime st.atime, st.mtime, path
+ rescue NotImplementedError
+ end
+ else
File.utime st.atime, st.mtime, path
end
begin
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 94b02c4..46def9d 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -374,6 +374,19 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(t + 2, File.mtime(@zerofile))
end
+ def test_lutime
+ return unless @symlinkfile
+ t = Time.local(2000)
+ assert_equal(1, File.lutime(t + 1, t + 2, @symlinkfile))
+ assert_equal(2, File.lutime(t + 1, t + 2, @symlinkfile, @symlinkfile))
+ assert_equal(t + 1, File.lstat(@symlinkfile).atime)
+ assert_equal(t + 2, File.lstat(@symlinkfile).mtime)
+ assert_not_equal(t + 1, File.atime(@symlinkfile))
+ assert_not_equal(t + 2, File.mtime(@symlinkfile))
+ assert_raise(Errno::ENOENT) { File.lutime(t + 1, t + 2, @nofile) }
+ rescue NotImplementedError
+ end
+
def test_hardlink
return unless @hardlinkfile
assert_equal("file", File.ftype(@hardlinkfile))
diff --git a/win32/win32.c b/win32/win32.c
index 3446728..6a2eeb8 100644
--- a/win32/win32.c
+++ b/win32/win32.c
@@ -6196,6 +6196,14 @@ rb_w32_write_console(uintptr_t strarg, int fd)
}
/* License: Ruby's */
+typedef enum utime_mode utime_mode_t;
+
+enum utime_mode {
+ M_SYMLINK_FOLLOW = 0,
+ M_SYMLINK_NOFOLLOW
+};
+
+/* License: Ruby's */
static int
unixtime_to_filetime(time_t time, FILETIME *ft)
{
@@ -6209,11 +6217,12 @@ unixtime_to_filetime(time_t time, FILETIME *ft)
/* License: Ruby's */
static int
-wutime(const WCHAR *path, const struct utimbuf *times)
+wutime(const WCHAR *path, const struct utimbuf *times, utime_mode_t mode)
{
HANDLE hFile;
FILETIME atime, mtime;
struct stati64 stat;
+ int flags;
int ret = 0;
if (wstati64(path, &stat)) {
@@ -6233,12 +6242,16 @@ wutime(const WCHAR *path, const struct utimbuf *times)
mtime = atime;
}
+ flags = FILE_FLAG_BACKUP_SEMANTICS;
+ if (mode == M_SYMLINK_NOFOLLOW)
+ flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+
RUBY_CRITICAL({
const DWORD attr = GetFileAttributesW(path);
if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY))
SetFileAttributesW(path, attr & ~FILE_ATTRIBUTE_READONLY);
hFile = CreateFileW(path, GENERIC_WRITE, 0, 0, OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS, 0);
+ flags, 0);
if (hFile == INVALID_HANDLE_VALUE) {
errno = map_errno(GetLastError());
ret = -1;
@@ -6259,34 +6272,62 @@ wutime(const WCHAR *path, const struct utimbuf *times)
/* License: Ruby's */
int
-rb_w32_uutime(const char *path, const struct utimbuf *times)
+uutime_internal(const char *path, const struct utimbuf *times, utime_mode_t mode)
{
WCHAR *wpath;
int ret;
if (!(wpath = utf8_to_wstr(path, NULL)))
return -1;
- ret = wutime(wpath, times);
+ ret = wutime(wpath, times, mode);
free(wpath);
return ret;
}
/* License: Ruby's */
int
-rb_w32_utime(const char *path, const struct utimbuf *times)
+utime_internal(const char *path, const struct utimbuf *times, utime_mode_t mode)
{
WCHAR *wpath;
int ret;
if (!(wpath = filecp_to_wstr(path, NULL)))
return -1;
- ret = wutime(wpath, times);
+ ret = wutime(wpath, times, mode);
free(wpath);
return ret;
}
/* License: Ruby's */
int
+rb_w32_uutime(const char *path, const struct utimbuf *times)
+{
+ return uutime_internal(path, times, M_SYMLINK_FOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_utime(const char *path, const struct utimbuf *times)
+{
+ return utime_internal(path, times, M_SYMLINK_FOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_luutime(const char *path, const struct utimbuf *times)
+{
+ return uutime_internal(path, times, M_SYMLINK_NOFOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_lutime(const char *path, const struct utimbuf *times)
+{
+ return utime_internal(path, times, M_SYMLINK_NOFOLLOW);
+}
+
+/* License: Ruby's */
+int
rb_w32_uchdir(const char *path)
{
WCHAR *wpath;
@kwilczynski

@luislavena did the patch ever got added/accepted? I wonder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.