diff options
Diffstat (limited to 'file.c')
| -rw-r--r-- | file.c | 2133 |
1 files changed, 1380 insertions, 753 deletions
@@ -12,6 +12,7 @@ **********************************************************************/ #include "ruby/internal/config.h" +#include "ruby/internal/attr/nonstring.h" #ifdef _WIN32 # include "missing/file.h" @@ -129,6 +130,14 @@ int flock(int, int); # endif #else # define STAT(p, s) stat((p), (s)) +#endif /* _WIN32 */ + +#ifdef HAVE_STRUCT_STATX_STX_BTIME +# define ST_(name) stx_ ## name +typedef struct statx_timestamp stat_timestamp; +#else +# define ST_(name) st_ ## name +typedef struct timespec stat_timestamp; #endif #if defined _WIN32 || defined __APPLE__ @@ -160,6 +169,7 @@ int flock(int, int); #include "internal.h" #include "internal/compilers.h" #include "internal/dir.h" +#include "internal/encoding.h" #include "internal/error.h" #include "internal/file.h" #include "internal/io.h" @@ -169,10 +179,16 @@ int flock(int, int); #include "internal/thread.h" #include "internal/vm.h" #include "ruby/encoding.h" -#include "ruby/io.h" #include "ruby/thread.h" #include "ruby/util.h" +#define UIANY2NUM(x) \ + ((sizeof(x) <= sizeof(unsigned int)) ? \ + UINT2NUM((unsigned)(x)) : \ + (sizeof(x) <= sizeof(unsigned long)) ? \ + ULONG2NUM((unsigned long)(x)) : \ + ULL2NUM((unsigned LONG_LONG)(x))) + VALUE rb_cFile; VALUE rb_mFileTest; VALUE rb_cStat; @@ -198,15 +214,16 @@ file_path_convert(VALUE name) return name; } -static rb_encoding * +static void check_path_encoding(VALUE str) { - rb_encoding *enc = rb_enc_get(str); - if (!rb_enc_asciicompat(enc)) { - rb_raise(rb_eEncCompatError, "path name must be ASCII-compatible (%s): %"PRIsVALUE, - rb_enc_name(enc), rb_str_inspect(str)); + if (RB_UNLIKELY(!rb_str_enc_fastpath(str))) { + rb_encoding *enc = rb_str_enc_get(str); + if (!rb_enc_asciicompat(enc)) { + rb_raise(rb_eEncCompatError, "path name must be ASCII-compatible (%s): %"PRIsVALUE, + rb_enc_name(enc), rb_str_inspect(str)); + } } - return enc; } VALUE @@ -234,7 +251,7 @@ rb_get_path_check_convert(VALUE obj) rb_raise(rb_eArgError, "path name contains null byte"); } - return rb_str_new4(obj); + return rb_str_new_frozen(obj); } VALUE @@ -249,6 +266,19 @@ rb_get_path(VALUE obj) return rb_get_path_check_convert(rb_get_path_check_to_string(obj)); } +static inline VALUE +check_path(VALUE obj, const char **cstr) +{ + VALUE str = rb_get_path_check_convert(rb_get_path_check_to_string(obj)); +#if RUBY_DEBUG + str = rb_str_new_frozen(str); +#endif + *cstr = RSTRING_PTR(str); + return str; +} + +#define CheckPath(str, cstr) RB_GC_GUARD(str) = check_path(str, &cstr); + VALUE rb_str_encode_ospath(VALUE path) { @@ -264,7 +294,7 @@ rb_str_encode_ospath(VALUE path) rb_encoding *utf8 = rb_utf8_encoding(); path = rb_str_conv_enc(path, enc, utf8); } -#endif +#endif /* USE_OSPATH */ return path; } @@ -272,6 +302,18 @@ rb_str_encode_ospath(VALUE path) # define NORMALIZE_UTF8PATH 1 # ifdef HAVE_WORKING_FORK +static CFMutableStringRef +mutable_CFString_new(CFStringRef *s, const char *ptr, long len) +{ + const CFAllocatorRef alloc = kCFAllocatorDefault; + *s = CFStringCreateWithBytesNoCopy(alloc, (const UInt8 *)ptr, len, + kCFStringEncodingUTF8, FALSE, + kCFAllocatorNull); + return CFStringCreateMutableCopy(alloc, len, *s); +} + +# define mutable_CFString_release(m, s) (CFRelease(m), CFRelease(s)) + static void rb_CFString_class_initialize_before_fork(void) { @@ -298,28 +340,27 @@ rb_CFString_class_initialize_before_fork(void) /* Enough small but non-empty ASCII string to fit in NSTaggedPointerString. */ const char small_str[] = "/"; long len = sizeof(small_str) - 1; - - const CFAllocatorRef alloc = kCFAllocatorDefault; - CFStringRef s = CFStringCreateWithBytesNoCopy(alloc, - (const UInt8 *)small_str, - len, kCFStringEncodingUTF8, - FALSE, kCFAllocatorNull); - CFMutableStringRef m = CFStringCreateMutableCopy(alloc, len, s); - CFRelease(m); - CFRelease(s); + CFStringRef s; + /* + * Touch `CFStringCreateWithBytesNoCopy` *twice* because the implementation + * shipped with macOS 15.0 24A5331b does not return `NSTaggedPointerString` + * instance for the first call (totally not sure why). CoreFoundation + * shipped with macOS 15.1 does not have this issue. + */ + for (int i = 0; i < 2; i++) { + CFMutableStringRef m = mutable_CFString_new(&s, small_str, len); + mutable_CFString_release(m, s); + } } -# endif +# endif /* HAVE_WORKING_FORK */ static VALUE rb_str_append_normalized_ospath(VALUE str, const char *ptr, long len) { CFIndex buflen = 0; CFRange all; - CFStringRef s = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, - (const UInt8 *)ptr, len, - kCFStringEncodingUTF8, FALSE, - kCFAllocatorNull); - CFMutableStringRef m = CFStringCreateMutableCopy(kCFAllocatorDefault, len, s); + CFStringRef s; + CFMutableStringRef m = mutable_CFString_new(&s, ptr, len); long oldlen = RSTRING_LEN(str); CFStringNormalize(m, kCFStringNormalizationFormC); @@ -329,8 +370,7 @@ rb_str_append_normalized_ospath(VALUE str, const char *ptr, long len) CFStringGetBytes(m, all, kCFStringEncodingUTF8, '?', FALSE, (UInt8 *)(RSTRING_PTR(str) + oldlen), buflen, &buflen); rb_str_set_len(str, oldlen + buflen); - CFRelease(m); - CFRelease(s); + mutable_CFString_release(m, s); return str; } @@ -349,7 +389,7 @@ rb_str_normalize_ospath(const char *ptr, long len) int r = rb_enc_precise_mbclen(p, e, enc); if (!MBCLEN_CHARFOUND_P(r)) { /* invalid byte shall not happen but */ - static const char invalid[3] = "\xEF\xBF\xBD"; + RBIMPL_ATTR_NONSTRING() static const char invalid[3] = "\xEF\xBF\xBD"; rb_str_append_normalized_ospath(str, p1, p-p1); rb_str_cat(str, invalid, sizeof(invalid)); p += 1; @@ -409,9 +449,9 @@ ignored_char_p(const char *p, const char *e, rb_encoding *enc) } return 0; } -#else +#else /* !__APPLE__ */ # define NORMALIZE_UTF8PATH 0 -#endif +#endif /* __APPLE__ */ #define apply2args(n) (rb_check_arity(argc, n, UNLIMITED_ARGUMENTS), argc-=n) @@ -469,7 +509,7 @@ apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg) aa->fn[aa->i].path = path; } - rb_thread_call_without_gvl(no_gvl_apply2files, aa, RUBY_UBF_IO, 0); + IO_WITHOUT_GVL(no_gvl_apply2files, aa); if (aa->errnum) { #ifdef UTIME_EINVAL if (func == utime_internal) { @@ -484,70 +524,135 @@ apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg) return LONG2FIX(argc); } -static size_t -stat_memsize(const void *p) -{ - return sizeof(struct stat); -} +static stat_timestamp stat_atimespec(const struct stat *st); +static stat_timestamp stat_mtimespec(const struct stat *st); +static stat_timestamp stat_ctimespec(const struct stat *st); static const rb_data_type_t stat_data_type = { "stat", - {NULL, RUBY_TYPED_DEFAULT_FREE, stat_memsize,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + { + NULL, + RUBY_TYPED_DEFAULT_FREE, + NULL, // No external memory to report + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; -static VALUE -stat_new_0(VALUE klass, const struct stat *st) +struct rb_stat { + rb_io_stat_data stat; + bool initialized; +}; + +static struct rb_stat * +stat_alloc(VALUE klass, VALUE *obj) { - struct stat *nst = 0; - VALUE obj = TypedData_Wrap_Struct(klass, &stat_data_type, 0); + struct rb_stat *rb_st; + *obj = TypedData_Make_Struct(klass, struct rb_stat, &stat_data_type, rb_st); + return rb_st; +} +VALUE +rb_stat_new(const struct stat *st) +{ + VALUE obj; + struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj); if (st) { - nst = ALLOC(struct stat); - *nst = *st; - RTYPEDDATA_DATA(obj) = nst; +#if RUBY_USE_STATX +# define CP(m) .stx_ ## m = st->st_ ## m +# define CP_32(m) .stx_ ## m = (uint32_t)st->st_ ## m +# define CP_TS(m) .stx_ ## m = stat_ ## m ## spec(st) + rb_st->stat = (struct statx){ + .stx_mask = STATX_BASIC_STATS, + CP(mode), + CP_32(nlink), + CP(uid), + CP(gid), + CP_TS(atime), + CP_TS(mtime), + CP_TS(ctime), + CP(ino), + CP(size), + CP(blocks), + }; +# undef CP +# undef CP_TS +#else + rb_st->stat = *st; +#endif + rb_st->initialized = true; } + return obj; } +#ifndef rb_statx_new VALUE -rb_stat_new(const struct stat *st) +rb_statx_new(const rb_io_stat_data *st) { - return stat_new_0(rb_cStat, st); + VALUE obj; + struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj); + if (st) { + rb_st->stat = *st; + rb_st->initialized = true; + } + return obj; } +#endif -static struct stat* +static rb_io_stat_data* get_stat(VALUE self) { - struct stat* st; - TypedData_Get_Struct(self, struct stat, &stat_data_type, st); - if (!st) rb_raise(rb_eTypeError, "uninitialized File::Stat"); - return st; + struct rb_stat* rb_st; + TypedData_Get_Struct(self, struct rb_stat, &stat_data_type, rb_st); + if (!rb_st->initialized) rb_raise(rb_eTypeError, "uninitialized File::Stat"); + return &rb_st->stat; } -static struct timespec stat_mtimespec(const struct stat *st); +#if RUBY_USE_STATX +static stat_timestamp +statx_mtimespec(const rb_io_stat_data *st) +{ + return st->stx_mtime; +} +#else +# define statx_mtimespec stat_mtimespec +#endif /* * call-seq: - * stat <=> other_stat -> -1, 0, 1, nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+, by comparing their modification times; + * that is, by comparing <tt>self.mtime</tt> and <tt>other.mtime</tt>. + * + * Returns: * - * Compares File::Stat objects by comparing their respective modification - * times. + * - +-1+, if <tt>self.mtime</tt> is earlier. + * - +0+, if the two values are equal. + * - +1+, if <tt>self.mtime</tt> is later. + * - +nil+, if +other+ is not a File::Stat object. * - * +nil+ is returned if +other_stat+ is not a File::Stat object + * Examples: * - * f1 = File.new("f1", "w") - * sleep 1 - * f2 = File.new("f2", "w") - * f1.stat <=> f2.stat #=> -1 + * stat0 = File.stat('README.md') + * stat1 = File.stat('NEWS.md') + * stat0.mtime # => 2025-12-20 15:33:05.6972341 -0600 + * stat1.mtime # => 2025-12-20 16:02:08.2672945 -0600 + * stat0 <=> stat1 # => -1 + * stat0 <=> stat0.dup # => 0 + * stat1 <=> stat0 # => 1 + * stat0 <=> :foo # => nil + * + * \Class \File::Stat includes module Comparable, + * each of whose methods uses File::Stat#<=> for comparison. */ static VALUE rb_stat_cmp(VALUE self, VALUE other) { if (rb_obj_is_kind_of(other, rb_obj_class(self))) { - struct timespec ts1 = stat_mtimespec(get_stat(self)); - struct timespec ts2 = stat_mtimespec(get_stat(other)); + stat_timestamp ts1 = statx_mtimespec(get_stat(self)); + stat_timestamp ts2 = statx_mtimespec(get_stat(other)); if (ts1.tv_sec == ts2.tv_sec) { if (ts1.tv_nsec == ts2.tv_nsec) return INT2FIX(0); if (ts1.tv_nsec < ts2.tv_nsec) return INT2FIX(-1); @@ -584,7 +689,11 @@ rb_stat_cmp(VALUE self, VALUE other) static VALUE rb_stat_dev(VALUE self) { -#if SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T +#if RUBY_USE_STATX + unsigned int m = get_stat(self)->stx_dev_major; + unsigned int n = get_stat(self)->stx_dev_minor; + return ULL2NUM(makedev(m, n)); +#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T return DEVT2NUM(get_stat(self)->st_dev); #elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_LONG return ULONG2NUM(get_stat(self)->st_dev); @@ -607,7 +716,9 @@ rb_stat_dev(VALUE self) static VALUE rb_stat_dev_major(VALUE self) { -#if defined(major) +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_dev_major); +#elif defined(major) return UINT2NUM(major(get_stat(self)->st_dev)); #else return Qnil; @@ -628,7 +739,9 @@ rb_stat_dev_major(VALUE self) static VALUE rb_stat_dev_minor(VALUE self) { -#if defined(minor) +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_dev_minor); +#elif defined(minor) return UINT2NUM(minor(get_stat(self)->st_dev)); #else return Qnil; @@ -648,16 +761,15 @@ rb_stat_dev_minor(VALUE self) static VALUE rb_stat_ino(VALUE self) { + rb_io_stat_data *ptr = get_stat(self); #ifdef HAVE_STRUCT_STAT_ST_INOHIGH /* assume INTEGER_PACK_LSWORD_FIRST and st_inohigh is just next of st_ino */ - return rb_integer_unpack(&get_stat(self)->st_ino, 2, + return rb_integer_unpack(&ptr->st_ino, 2, SIZEOF_STRUCT_STAT_ST_INO, 0, INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER| INTEGER_PACK_2COMP); -#elif SIZEOF_STRUCT_STAT_ST_INO > SIZEOF_LONG - return ULL2NUM(get_stat(self)->st_ino); #else - return ULONG2NUM(get_stat(self)->st_ino); + return UIANY2NUM(ptr->ST_(ino)); #endif } @@ -677,7 +789,7 @@ rb_stat_ino(VALUE self) static VALUE rb_stat_mode(VALUE self) { - return UINT2NUM(ST2UINT(get_stat(self)->st_mode)); + return UINT2NUM(ST2UINT(get_stat(self)->ST_(mode))); } /* @@ -696,20 +808,9 @@ static VALUE rb_stat_nlink(VALUE self) { /* struct stat::st_nlink is nlink_t in POSIX. Not the case for Windows. */ - const struct stat *ptr = get_stat(self); + const rb_io_stat_data *ptr = get_stat(self); - if (sizeof(ptr->st_nlink) <= sizeof(int)) { - return UINT2NUM((unsigned)ptr->st_nlink); - } - else if (sizeof(ptr->st_nlink) == sizeof(long)) { - return ULONG2NUM((unsigned long)ptr->st_nlink); - } - else if (sizeof(ptr->st_nlink) == sizeof(LONG_LONG)) { - return ULL2NUM((unsigned LONG_LONG)ptr->st_nlink); - } - else { - rb_bug(":FIXME: don't know what to do"); - } + return UIANY2NUM(ptr->ST_(nlink)); } /* @@ -725,7 +826,7 @@ rb_stat_nlink(VALUE self) static VALUE rb_stat_uid(VALUE self) { - return UIDT2NUM(get_stat(self)->st_uid); + return UIDT2NUM(get_stat(self)->ST_(uid)); } /* @@ -741,7 +842,7 @@ rb_stat_uid(VALUE self) static VALUE rb_stat_gid(VALUE self) { - return GIDT2NUM(get_stat(self)->st_gid); + return GIDT2NUM(get_stat(self)->ST_(gid)); } /* @@ -759,16 +860,18 @@ rb_stat_gid(VALUE self) static VALUE rb_stat_rdev(VALUE self) { -#ifdef HAVE_STRUCT_STAT_ST_RDEV -# if SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T - return DEVT2NUM(get_stat(self)->st_rdev); -# elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG - return ULONG2NUM(get_stat(self)->st_rdev); -# else - return ULL2NUM(get_stat(self)->st_rdev); -# endif -#else +#if RUBY_USE_STATX + unsigned int m = get_stat(self)->stx_rdev_major; + unsigned int n = get_stat(self)->stx_rdev_minor; + return ULL2NUM(makedev(m, n)); +#elif !defined(HAVE_STRUCT_STAT_ST_RDEV) return Qnil; +#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T + return DEVT2NUM(get_stat(self)->ST_(rdev)); +#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG + return ULONG2NUM(get_stat(self)->ST_(rdev)); +#else + return ULL2NUM(get_stat(self)->ST_(rdev)); #endif } @@ -786,8 +889,10 @@ rb_stat_rdev(VALUE self) static VALUE rb_stat_rdev_major(VALUE self) { -#if defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major) - return UINT2NUM(major(get_stat(self)->st_rdev)); +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_rdev_major); +#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major) + return UINT2NUM(major(get_stat(self)->ST_(rdev))); #else return Qnil; #endif @@ -807,8 +912,10 @@ rb_stat_rdev_major(VALUE self) static VALUE rb_stat_rdev_minor(VALUE self) { -#if defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor) - return UINT2NUM(minor(get_stat(self)->st_rdev)); +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_rdev_minor); +#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor) + return UINT2NUM(minor(get_stat(self)->ST_(rdev))); #else return Qnil; #endif @@ -826,7 +933,7 @@ rb_stat_rdev_minor(VALUE self) static VALUE rb_stat_size(VALUE self) { - return OFFT2NUM(get_stat(self)->st_size); + return OFFT2NUM(get_stat(self)->ST_(size)); } /* @@ -844,7 +951,7 @@ static VALUE rb_stat_blksize(VALUE self) { #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - return ULONG2NUM(get_stat(self)->st_blksize); + return ULONG2NUM(get_stat(self)->ST_(blksize)); #else return Qnil; #endif @@ -866,34 +973,44 @@ rb_stat_blocks(VALUE self) { #ifdef HAVE_STRUCT_STAT_ST_BLOCKS # if SIZEOF_STRUCT_STAT_ST_BLOCKS > SIZEOF_LONG - return ULL2NUM(get_stat(self)->st_blocks); + return ULL2NUM(get_stat(self)->ST_(blocks)); # else - return ULONG2NUM(get_stat(self)->st_blocks); + return ULONG2NUM(get_stat(self)->ST_(blocks)); # endif #else return Qnil; #endif } -static struct timespec +static stat_timestamp stat_atimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_atime; #if defined(HAVE_STRUCT_STAT_ST_ATIM) - ts.tv_nsec = st->st_atim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_atim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) - ts.tv_nsec = st->st_atimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_atimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) - ts.tv_nsec = (long)st->st_atimensec; + ts.tv_nsec = (uint32_t)st->st_atimensec; #else - ts.tv_nsec = 0; + ts.tv_nsec = 0 #endif return ts; } +#if RUBY_USE_STATX +static stat_timestamp +statx_atimespec(const rb_io_stat_data *st) +{ + return st->stx_atime; +} +#else +# define statx_atimespec stat_atimespec +#endif + static VALUE -stat_time(const struct timespec ts) +stat_time(const stat_timestamp ts) { return rb_time_nano_new(ts.tv_sec, ts.tv_nsec); } @@ -904,17 +1021,17 @@ stat_atime(const struct stat *st) return stat_time(stat_atimespec(st)); } -static struct timespec +static stat_timestamp stat_mtimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_mtime; #if defined(HAVE_STRUCT_STAT_ST_MTIM) - ts.tv_nsec = st->st_mtim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_mtim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC) - ts.tv_nsec = st->st_mtimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_mtimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC) - ts.tv_nsec = (long)st->st_mtimensec; + ts.tv_nsec = (uint32_t)st->st_mtimensec; #else ts.tv_nsec = 0; #endif @@ -927,23 +1044,33 @@ stat_mtime(const struct stat *st) return stat_time(stat_mtimespec(st)); } -static struct timespec +static stat_timestamp stat_ctimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_ctime; #if defined(HAVE_STRUCT_STAT_ST_CTIM) - ts.tv_nsec = st->st_ctim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_ctim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC) - ts.tv_nsec = st->st_ctimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_ctimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_CTIMENSEC) - ts.tv_nsec = (long)st->st_ctimensec; + ts.tv_nsec = (uint32_t)st->st_ctimensec; #else ts.tv_nsec = 0; #endif return ts; } +#if RUBY_USE_STATX +static stat_timestamp +statx_ctimespec(const rb_io_stat_data *st) +{ + return st->stx_ctime; +} +#else +# define statx_ctimespec stat_ctimespec +#endif + static VALUE stat_ctime(const struct stat *st) { @@ -952,19 +1079,19 @@ stat_ctime(const struct stat *st) #define HAVE_STAT_BIRTHTIME #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) -typedef struct stat statx_data; static VALUE -stat_birthtime(const struct stat *st) +statx_birthtime(const rb_io_stat_data *st) { - const struct timespec *ts = &st->st_birthtimespec; + const stat_timestamp *ts = &st->ST_(birthtimespec); return rb_time_nano_new(ts->tv_sec, ts->tv_nsec); } +#elif defined(HAVE_STRUCT_STATX_STX_BTIME) +static VALUE statx_birthtime(const rb_io_stat_data *st); #elif defined(_WIN32) -typedef struct stat statx_data; -# define stat_birthtime stat_ctime +# define statx_birthtime stat_ctime #else # undef HAVE_STAT_BIRTHTIME -#endif +#endif /* defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) */ /* * call-seq: @@ -980,7 +1107,7 @@ typedef struct stat statx_data; static VALUE rb_stat_atime(VALUE self) { - return stat_atime(get_stat(self)); + return stat_time(statx_atimespec(get_stat(self))); } /* @@ -996,7 +1123,7 @@ rb_stat_atime(VALUE self) static VALUE rb_stat_mtime(VALUE self) { - return stat_mtime(get_stat(self)); + return stat_time(statx_mtimespec(get_stat(self))); } /* @@ -1016,7 +1143,7 @@ rb_stat_mtime(VALUE self) static VALUE rb_stat_ctime(VALUE self) { - return stat_ctime(get_stat(self)); + return stat_time(statx_ctimespec(get_stat(self))); } #if defined(HAVE_STAT_BIRTHTIME) @@ -1045,7 +1172,7 @@ rb_stat_ctime(VALUE self) static VALUE rb_stat_birthtime(VALUE self) { - return stat_birthtime(get_stat(self)); + return statx_birthtime(get_stat(self)); } #else # define rb_stat_birthtime rb_f_notimplement @@ -1093,9 +1220,9 @@ rb_stat_inspect(VALUE self) #endif }; - struct stat* st; - TypedData_Get_Struct(self, struct stat, &stat_data_type, st); - if (!st) { + struct rb_stat* rb_st; + TypedData_Get_Struct(self, struct rb_stat, &stat_data_type, rb_st); + if (!rb_st->initialized) { return rb_sprintf("#<%s: uninitialized>", rb_obj_classname(self)); } @@ -1143,14 +1270,14 @@ no_gvl_fstat(void *data) } static int -fstat_without_gvl(int fd, struct stat *st) +fstat_without_gvl(rb_io_t *fptr, struct stat *st) { no_gvl_stat_data data; - data.file.fd = fd; + data.file.fd = fptr->fd; data.st = st; - return (int)(VALUE)rb_thread_io_blocking_region(no_gvl_fstat, &data, fd); + return (int)rb_io_blocking_region(fptr, no_gvl_fstat, &data); } static void * @@ -1168,13 +1295,14 @@ stat_without_gvl(const char *path, struct stat *st) data.file.path = path; data.st = st; - return (int)(VALUE)rb_thread_call_without_gvl(no_gvl_stat, &data, - RUBY_UBF_IO, NULL); + return IO_WITHOUT_GVL_INT(no_gvl_stat, &data); } #if !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \ defined(HAVE_STRUCT_STATX_STX_BTIME) +# define STATX(path, st, mask) statx(AT_FDCWD, path, 0, mask, st) + # ifndef HAVE_STATX # ifdef HAVE_SYSCALL_H # include <syscall.h> @@ -1189,21 +1317,21 @@ statx(int dirfd, const char *pathname, int flags, { return (int)syscall(__NR_statx, dirfd, pathname, flags, mask, statxbuf); } -# endif -# endif +# endif /* __linux__ */ +# endif /* HAVE_STATX */ -typedef struct no_gvl_statx_data { +typedef struct no_gvl_rb_io_stat_data { struct statx *stx; int fd; const char *path; int flags; unsigned int mask; -} no_gvl_statx_data; +} no_gvl_rb_io_stat_data; static VALUE io_blocking_statx(void *data) { - no_gvl_statx_data *arg = data; + no_gvl_rb_io_stat_data *arg = data; return (VALUE)statx(arg->fd, arg->path, arg->flags, arg->mask, arg->stx); } @@ -1214,24 +1342,34 @@ no_gvl_statx(void *data) } static int -statx_without_gvl(const char *path, struct statx *stx, unsigned int mask) +statx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask) { - no_gvl_statx_data data = {stx, AT_FDCWD, path, 0, mask}; + no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, 0, mask}; /* call statx(2) with pathname */ - return (int)(VALUE)rb_thread_call_without_gvl(no_gvl_statx, &data, - RUBY_UBF_IO, NULL); + return IO_WITHOUT_GVL_INT(no_gvl_statx, &data); } static int -fstatx_without_gvl(int fd, struct statx *stx, unsigned int mask) +lstatx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask) { - no_gvl_statx_data data = {stx, fd, "", AT_EMPTY_PATH, mask}; + no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask}; + + /* call statx(2) with pathname */ + return IO_WITHOUT_GVL_INT(no_gvl_statx, &data); +} + +static int +fstatx_without_gvl(rb_io_t *fptr, rb_io_stat_data *stx, unsigned int mask) +{ + no_gvl_rb_io_stat_data data = {stx, fptr->fd, "", AT_EMPTY_PATH, mask}; /* call statx(2) with fd */ - return (int)rb_thread_io_blocking_region(io_blocking_statx, &data, fd); + return (int)rb_io_blocking_region(fptr, io_blocking_statx, &data); } +#define FSTATX(fd, st) statx(fd, "", AT_EMPTY_PATH, STATX_ALL, st) + static int rb_statx(VALUE file, struct statx *stx, unsigned int mask) { @@ -1241,8 +1379,9 @@ rb_statx(VALUE file, struct statx *stx, unsigned int mask) tmp = rb_check_convert_type_with_id(file, T_FILE, "IO", idTo_io); if (!NIL_P(tmp)) { rb_io_t *fptr; + GetOpenFile(tmp, fptr); - result = fstatx_without_gvl(fptr->fd, stx, mask); + result = fstatx_without_gvl(fptr, stx, mask); file = tmp; } else { @@ -1269,7 +1408,7 @@ statx_notimplement(const char *field_name) } static VALUE -statx_birthtime(const struct statx *stx, VALUE fname) +statx_birthtime(const rb_io_stat_data *stx) { if (!statx_has_birthtime(stx)) { /* birthtime is not supported on the filesystem */ @@ -1278,19 +1417,27 @@ statx_birthtime(const struct statx *stx, VALUE fname) return rb_time_nano_new((time_t)stx->stx_btime.tv_sec, stx->stx_btime.tv_nsec); } -typedef struct statx statx_data; -# define HAVE_STAT_BIRTHTIME +#else -#elif defined(HAVE_STAT_BIRTHTIME) # define statx_without_gvl(path, st, mask) stat_without_gvl(path, st) -# define fstatx_without_gvl(fd, st, mask) fstat_without_gvl(fd, st) -# define statx_birthtime(st, fname) stat_birthtime(st) +# define fstatx_without_gvl(fptr, st, mask) fstat_without_gvl(fptr, st) +# define lstatx_without_gvl(path, st, mask) lstat_without_gvl(path, st) +# define rb_statx(file, stx, mask) rb_stat(file, stx) +# define STATX(path, st, mask) STAT(path, st) + +#if defined(HAVE_STAT_BIRTHTIME) # define statx_has_birthtime(st) 1 -# define rb_statx(file, st, mask) rb_stat(file, st) #else # define statx_has_birthtime(st) 0 #endif +#endif /* !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \ + defined(HAVE_STRUCT_STATX_STX_BTIME) */ + +#ifndef FSTAT +# define FSTAT(fd, st) fstat(fd, st) +#endif + static int rb_stat(VALUE file, struct stat *st) { @@ -1302,7 +1449,7 @@ rb_stat(VALUE file, struct stat *st) rb_io_t *fptr; GetOpenFile(tmp, fptr); - result = fstat_without_gvl(fptr->fd, st); + result = fstat_without_gvl(fptr, st); file = tmp; } else { @@ -1327,14 +1474,14 @@ rb_stat(VALUE file, struct stat *st) static VALUE rb_file_s_stat(VALUE klass, VALUE fname) { - struct stat st; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (stat_without_gvl(RSTRING_PTR(fname), &st) < 0) { + if (statx_without_gvl(RSTRING_PTR(fname), &st, STATX_ALL) < 0) { rb_sys_fail_path(fname); } - return rb_stat_new(&st); + return rb_statx_new(&st); } /* @@ -1356,13 +1503,13 @@ static VALUE rb_io_stat(VALUE obj) { rb_io_t *fptr; - struct stat st; + rb_io_stat_data st; GetOpenFile(obj, fptr); - if (fstat(fptr->fd, &st) == -1) { + if (fstatx_without_gvl(fptr, &st, STATX_ALL) == -1) { rb_sys_fail_path(fptr->pathv); } - return rb_stat_new(&st); + return rb_statx_new(&st); } #ifdef HAVE_LSTAT @@ -1381,8 +1528,7 @@ lstat_without_gvl(const char *path, struct stat *st) data.file.path = path; data.st = st; - return (int)(VALUE)rb_thread_call_without_gvl(no_gvl_lstat, &data, - RUBY_UBF_IO, NULL); + return IO_WITHOUT_GVL_INT(no_gvl_lstat, &data); } #endif /* HAVE_LSTAT */ @@ -1403,14 +1549,14 @@ static VALUE rb_file_s_lstat(VALUE klass, VALUE fname) { #ifdef HAVE_LSTAT - struct stat st; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) { + if (lstatx_without_gvl(StringValueCStr(fname), &st, STATX_ALL) == -1) { rb_sys_fail_path(fname); } - return rb_stat_new(&st); + return rb_statx_new(&st); #else return rb_file_s_stat(klass, fname); #endif @@ -1435,16 +1581,16 @@ rb_file_lstat(VALUE obj) { #ifdef HAVE_LSTAT rb_io_t *fptr; - struct stat st; + rb_io_stat_data st; VALUE path; GetOpenFile(obj, fptr); if (NIL_P(fptr->pathv)) return Qnil; path = rb_str_encode_ospath(fptr->pathv); - if (lstat_without_gvl(RSTRING_PTR(path), &st) == -1) { + if (lstatx_without_gvl(RSTRING_PTR(path), &st, STATX_ALL) == -1) { rb_sys_fail_path(fptr->pathv); } - return rb_stat_new(&st); + return rb_statx_new(&st); #else return rb_io_stat(obj); #endif @@ -1478,7 +1624,7 @@ rb_group_member(GETGROUPS_T gid) ALLOCV_END(v); return rv; -#endif +#endif /* defined(_WIN32) || !defined(HAVE_GETGROUPS) */ } #ifndef S_IXUGO @@ -1529,9 +1675,9 @@ eaccess(const char *path, int mode) return -1; #else return access(path, mode); -#endif +#endif /* USE_GETEUID */ } -#endif +#endif /* HAVE_EACCESS */ struct access_arg { const char *path; @@ -1556,8 +1702,7 @@ rb_eaccess(VALUE fname, int mode) aa.path = StringValueCStr(fname); aa.mode = mode; - return (int)(VALUE)rb_thread_call_without_gvl(nogvl_eaccess, &aa, - RUBY_UBF_IO, 0); + return IO_WITHOUT_GVL_INT(nogvl_eaccess, &aa); } static void * @@ -1578,8 +1723,7 @@ rb_access(VALUE fname, int mode) aa.path = StringValueCStr(fname); aa.mode = mode; - return (int)(VALUE)rb_thread_call_without_gvl(nogvl_access, &aa, - RUBY_UBF_IO, 0); + return IO_WITHOUT_GVL_INT(nogvl_access, &aa); } /* @@ -1593,8 +1737,6 @@ rb_access(VALUE fname, int mode) */ /* - * Document-method: directory? - * * call-seq: * File.directory?(path) -> true or false * @@ -1773,7 +1915,7 @@ rb_file_blockdev_p(VALUE obj, VALUE fname) * * Returns +true+ if +filepath+ points to a character device, +false+ otherwise. * - * File.chardev?($stdin) # => true + * File.chardev?($stdin) # => true * File.chardev?('t.txt') # => false * */ @@ -2059,7 +2201,7 @@ rb_file_size_p(VALUE obj, VALUE fname) * File.owned?(file_name) -> true or false * * Returns <code>true</code> if the named file exists and the - * effective used id of the calling process is the owner of + * effective user id of the calling process is the owner of * the file. * * _file_name_ can be an IO object. @@ -2233,36 +2375,36 @@ rb_file_s_size(VALUE klass, VALUE fname) } static VALUE -rb_file_ftype(const struct stat *st) +rb_file_ftype(mode_t mode) { const char *t; - if (S_ISREG(st->st_mode)) { + if (S_ISREG(mode)) { t = "file"; } - else if (S_ISDIR(st->st_mode)) { + else if (S_ISDIR(mode)) { t = "directory"; } - else if (S_ISCHR(st->st_mode)) { + else if (S_ISCHR(mode)) { t = "characterSpecial"; } #ifdef S_ISBLK - else if (S_ISBLK(st->st_mode)) { + else if (S_ISBLK(mode)) { t = "blockSpecial"; } #endif #ifdef S_ISFIFO - else if (S_ISFIFO(st->st_mode)) { + else if (S_ISFIFO(mode)) { t = "fifo"; } #endif #ifdef S_ISLNK - else if (S_ISLNK(st->st_mode)) { + else if (S_ISLNK(mode)) { t = "link"; } #endif #ifdef S_ISSOCK - else if (S_ISSOCK(st->st_mode)) { + else if (S_ISSOCK(mode)) { t = "socket"; } #endif @@ -2299,7 +2441,7 @@ rb_file_s_ftype(VALUE klass, VALUE fname) rb_sys_fail_path(fname); } - return rb_file_ftype(&st); + return rb_file_ftype(st.st_mode); } /* @@ -2324,7 +2466,7 @@ rb_file_s_atime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_atime(&st); + return stat_time(stat_atimespec(&st)); } /* @@ -2348,7 +2490,7 @@ rb_file_atime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_atime(&st); + return stat_time(stat_atimespec(&st)); } /* @@ -2373,7 +2515,7 @@ rb_file_s_mtime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_mtime(&st); + return stat_time(stat_mtimespec(&st)); } /* @@ -2396,7 +2538,7 @@ rb_file_mtime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_mtime(&st); + return stat_time(stat_mtimespec(&st)); } /* @@ -2425,7 +2567,7 @@ rb_file_s_ctime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_ctime(&st); + return stat_time(stat_ctimespec(&st)); } /* @@ -2451,9 +2593,10 @@ rb_file_ctime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_ctime(&st); + return stat_time(stat_ctimespec(&st)); } +#if defined(HAVE_STAT_BIRTHTIME) /* * call-seq: * File.birthtime(file_name) -> time @@ -2468,18 +2611,17 @@ rb_file_ctime(VALUE obj) * */ -#if defined(HAVE_STAT_BIRTHTIME) -RUBY_FUNC_EXPORTED VALUE +VALUE rb_file_s_birthtime(VALUE klass, VALUE fname) { - statx_data st; + rb_io_stat_data st; if (rb_statx(fname, &st, STATX_BTIME) < 0) { int e = errno; FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return statx_birthtime(&st, fname); + return statx_birthtime(&st); } #else # define rb_file_s_birthtime rb_f_notimplement @@ -2502,28 +2644,18 @@ static VALUE rb_file_birthtime(VALUE obj) { rb_io_t *fptr; - statx_data st; + rb_io_stat_data st; GetOpenFile(obj, fptr); - if (fstatx_without_gvl(fptr->fd, &st, STATX_BTIME) == -1) { + if (fstatx_without_gvl(fptr, &st, STATX_BTIME) == -1) { rb_sys_fail_path(fptr->pathv); } - return statx_birthtime(&st, fptr->pathv); + return statx_birthtime(&st); } #else # define rb_file_birthtime rb_f_notimplement #endif -/* - * call-seq: - * file.size -> integer - * - * Returns the size of <i>file</i> in bytes. - * - * File.new("testfile").size #=> 66 - * - */ - rb_off_t rb_file_size(VALUE file) { @@ -2547,12 +2679,45 @@ rb_file_size(VALUE file) } } +/* + * call-seq: + * file.size -> integer + * + * Returns the size of <i>file</i> in bytes. + * + * File.new("testfile").size #=> 66 + * + */ + static VALUE file_size(VALUE self) { return OFFT2NUM(rb_file_size(self)); } +struct nogvl_chmod_data { + const char *path; + mode_t mode; +}; + +static void * +nogvl_chmod(void *ptr) +{ + struct nogvl_chmod_data *data = ptr; + int ret = chmod(data->path, data->mode); + return (void *)(VALUE)ret; +} + +static int +rb_chmod(const char *path, mode_t mode) +{ + struct nogvl_chmod_data data = { + .path = path, + .mode = mode, + }; + return IO_WITHOUT_GVL_INT(nogvl_chmod, &data); +} + static int chmod_internal(const char *path, void *mode) { @@ -2583,6 +2748,29 @@ rb_file_s_chmod(int argc, VALUE *argv, VALUE _) return apply2files(chmod_internal, argc, argv, &mode); } +#ifdef HAVE_FCHMOD +struct nogvl_fchmod_data { + int fd; + mode_t mode; +}; + +static VALUE +io_blocking_fchmod(void *ptr) +{ + struct nogvl_fchmod_data *data = ptr; + int ret = fchmod(data->fd, data->mode); + return (VALUE)ret; +} + +static int +rb_fchmod(struct rb_io* io, mode_t mode) +{ + (void)rb_chmod; /* suppress unused-function warning when HAVE_FCHMOD */ + struct nogvl_fchmod_data data = {.fd = io->fd, .mode = mode}; + return (int)rb_thread_io_blocking_region(io, io_blocking_fchmod, &data); +} +#endif + /* * call-seq: * file.chmod(mode_int) -> 0 @@ -2609,7 +2797,7 @@ rb_file_chmod(VALUE obj, VALUE vmode) GetOpenFile(obj, fptr); #ifdef HAVE_FCHMOD - if (fchmod(fptr->fd, mode) == -1) { + if (rb_fchmod(fptr, mode) == -1) { if (HAVE_FCHMOD || errno != ENOSYS) rb_sys_fail_path(fptr->pathv); } @@ -2620,7 +2808,7 @@ rb_file_chmod(VALUE obj, VALUE vmode) #if !defined HAVE_FCHMOD || !HAVE_FCHMOD if (NIL_P(fptr->pathv)) return Qnil; path = rb_str_encode_ospath(fptr->pathv); - if (chmod(RSTRING_PTR(path), mode) == -1) + if (rb_chmod(RSTRING_PTR(path), mode) == -1) rb_sys_fail_path(fptr->pathv); #endif @@ -2715,6 +2903,51 @@ rb_file_s_chown(int argc, VALUE *argv, VALUE _) return apply2files(chown_internal, argc, argv, &arg); } +struct nogvl_chown_data { + union { + const char *path; + int fd; + } as; + struct chown_args new; +}; + +static void * +nogvl_chown(void *ptr) +{ + struct nogvl_chown_data *data = ptr; + return (void *)(VALUE)chown(data->as.path, data->new.owner, data->new.group); +} + +static int +rb_chown(const char *path, rb_uid_t owner, rb_gid_t group) +{ + struct nogvl_chown_data data = { + .as = {.path = path}, + .new = {.owner = owner, .group = group}, + }; + return IO_WITHOUT_GVL_INT(nogvl_chown, &data); +} + +#ifdef HAVE_FCHOWN +static void * +nogvl_fchown(void *ptr) +{ + struct nogvl_chown_data *data = ptr; + return (void *)(VALUE)fchown(data->as.fd, data->new.owner, data->new.group); +} + +static int +rb_fchown(int fd, rb_uid_t owner, rb_gid_t group) +{ + (void)rb_chown; /* suppress unused-function warning when HAVE_FCHMOD */ + struct nogvl_chown_data data = { + .as = {.fd = fd}, + .new = {.owner = owner, .group = group}, + }; + return IO_WITHOUT_GVL_INT(nogvl_fchown, &data); +} +#endif + /* * call-seq: * file.chown(owner_int, group_int ) -> 0 @@ -2746,10 +2979,10 @@ rb_file_chown(VALUE obj, VALUE owner, VALUE group) #ifndef HAVE_FCHOWN if (NIL_P(fptr->pathv)) return Qnil; path = rb_str_encode_ospath(fptr->pathv); - if (chown(RSTRING_PTR(path), o, g) == -1) + if (rb_chown(RSTRING_PTR(path), o, g) == -1) rb_sys_fail_path(fptr->pathv); #else - if (fchown(fptr->fd, o, g) == -1) + if (rb_fchown(fptr->fd, o, g) == -1) rb_sys_fail_path(fptr->pathv); #endif @@ -2836,7 +3069,7 @@ utime_failed(struct apply_arg *aa) } rb_syserr_fail_path(e, path); } -#endif +#endif /* UTIME_EINVAL */ #if defined(HAVE_UTIMES) @@ -2845,7 +3078,7 @@ utime_failed(struct apply_arg *aa) # elif defined(__APPLE__) && \ (!defined(MAC_OS_X_VERSION_13_0) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_13_0)) -# if defined(__has_attribute) && __has_attribute(availability) +# if __has_attribute(availability) && __has_warning("-Wunguarded-availability-new") typedef int utimensat_func(int, const char *, const struct timespec [2], int); RBIMPL_WARNING_PUSH() @@ -2860,8 +3093,8 @@ RBIMPL_WARNING_POP() # define utimensat rb_utimensat() # else /* __API_AVAILABLE macro does nothing on gcc */ __attribute__((weak)) int utimensat(int, const char *, const struct timespec [2], int); -# endif -# endif +# endif /* utimesat availability */ +# endif /* __APPLE__ && < MAC_OS_X_VERSION_13_0 */ static int utime_internal(const char *path, void *arg) @@ -2874,15 +3107,15 @@ utime_internal(const char *path, void *arg) # if defined(__APPLE__) const int try_utimensat = utimensat != NULL; const int try_utimensat_follow = utimensat != NULL; -# else +# else /* !__APPLE__ */ # define TRY_UTIMENSAT 1 static int try_utimensat = 1; -# ifdef AT_SYMLINK_NOFOLLOW +# ifdef AT_SYMLINK_NOFOLLOW static int try_utimensat_follow = 1; -# else +# else const int try_utimensat_follow = 0; -# endif -# endif +# endif +# endif /* __APPLE__ */ int flags = 0; if (v->follow ? try_utimensat_follow : try_utimensat) { @@ -2897,15 +3130,15 @@ utime_internal(const char *path, void *arg) if (result < 0 && errno == ENOSYS) { # ifdef AT_SYMLINK_NOFOLLOW try_utimensat_follow = 0; -# endif +# endif /* AT_SYMLINK_NOFOLLOW */ if (!v->follow) try_utimensat = 0; } else -# endif +# endif /* TRY_UTIMESAT */ return result; } -#endif +#endif /* defined(HAVE_UTIMENSAT) */ if (tsp) { tvbuf[0].tv_sec = tsp[0].tv_sec; @@ -2920,7 +3153,7 @@ utime_internal(const char *path, void *arg) return utimes(path, tvp); } -#else +#else /* !defined(HAVE_UTIMES) */ #if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H struct utimbuf { @@ -2933,7 +3166,7 @@ static int utime_internal(const char *path, void *arg) { struct utime_args *v = arg; - const struct timespec *tsp = v->tsp; + const stat_timestamp *tsp = v->tsp; struct utimbuf utbuf, *utp = NULL; if (tsp) { utbuf.actime = tsp[0].tv_sec; @@ -2942,8 +3175,7 @@ utime_internal(const char *path, void *arg) } return utime(path, utp); } - -#endif +#endif /* !defined(HAVE_UTIMES) */ static VALUE utime_internal_i(int argc, VALUE *argv, int follow) @@ -3142,8 +3374,7 @@ readlink_without_gvl(VALUE path, VALUE buf, size_t size) ra.buf = RSTRING_PTR(buf); ra.size = size; - return (ssize_t)rb_thread_call_without_gvl(nogvl_readlink, &ra, - RUBY_UBF_IO, 0); + return (ssize_t)IO_WITHOUT_GVL(nogvl_readlink, &ra); } VALUE @@ -3244,8 +3475,7 @@ rb_file_s_rename(VALUE klass, VALUE from, VALUE to) #if defined __CYGWIN__ errno = 0; #endif - if ((int)(VALUE)rb_thread_call_without_gvl(no_gvl_rename, &ra, - RUBY_UBF_IO, 0) < 0) { + if (IO_WITHOUT_GVL_INT(no_gvl_rename, &ra) < 0) { int e = errno; #if defined DOSISH switch (e) { @@ -3314,12 +3544,13 @@ static const char file_alt_separator[] = {FILE_ALT_SEPARATOR, '\0'}; #endif #ifndef USE_NTFS -#if defined _WIN32 -#define USE_NTFS 1 -#else -#define USE_NTFS 0 -#endif +# if defined _WIN32 +# define USE_NTFS 1 +# else +# define USE_NTFS 0 +# endif #endif + #ifndef USE_NTFS_ADS # if USE_NTFS # define USE_NTFS_ADS 1 @@ -3333,14 +3564,15 @@ static const char file_alt_separator[] = {FILE_ALT_SEPARATOR, '\0'}; #else #define istrailinggarbage(x) 0 #endif + #if USE_NTFS_ADS # define isADS(x) ((x) == ':') #else # define isADS(x) 0 #endif -#define Next(p, e, enc) ((p) + rb_enc_mbclen((p), (e), (enc))) -#define Inc(p, e, enc) ((p) = Next((p), (e), (enc))) +#define Next(p, e, mb_enc, enc) ((p) + ((mb_enc) ? rb_enc_mbclen((p), (e), (enc)) : 1)) +#define Inc(p, e, mb_enc, enc) ((p) = Next((p), (e), (mb_enc), (enc))) #if defined(DOSISH_UNC) #define has_unc(buf) (isdirsep((buf)[0]) && isdirsep((buf)[1])) @@ -3400,11 +3632,11 @@ not_same_drive(VALUE path, int drive) return has_unc(p); } } -#endif -#endif +#endif /* _WIN32 */ +#endif /* DOSISH_DRIVE_LETTER */ static inline char * -skiproot(const char *path, const char *end, rb_encoding *enc) +skiproot(const char *path, const char *end) { #ifdef DOSISH_DRIVE_LETTER if (path + 2 <= end && has_drive_letter(path)) path += 2; @@ -3413,31 +3645,37 @@ skiproot(const char *path, const char *end, rb_encoding *enc) return (char *)path; } -#define nextdirsep rb_enc_path_next -char * -rb_enc_path_next(const char *s, const char *e, rb_encoding *enc) +static inline char * +enc_path_next(const char *s, const char *e, bool mb_enc, rb_encoding *enc) { while (s < e && !isdirsep(*s)) { - Inc(s, e, enc); + Inc(s, e, mb_enc, enc); } return (char *)s; } +#define nextdirsep rb_enc_path_next +char * +rb_enc_path_next(const char *s, const char *e, rb_encoding *enc) +{ + return enc_path_next(s, e, true, enc); +} + #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) -#define skipprefix rb_enc_path_skip_prefix +#define skipprefix enc_path_skip_prefix #else -#define skipprefix(path, end, enc) (path) +#define skipprefix(path, end, mb_enc, enc) (path) #endif -char * -rb_enc_path_skip_prefix(const char *path, const char *end, rb_encoding *enc) +static inline char * +enc_path_skip_prefix(const char *path, const char *end, bool mb_enc, rb_encoding *enc) { #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) #ifdef DOSISH_UNC if (path + 2 <= end && isdirsep(path[0]) && isdirsep(path[1])) { path += 2; while (path < end && isdirsep(*path)) path++; - if ((path = rb_enc_path_next(path, end, enc)) < end && path[0] && path[1] && !isdirsep(path[1])) - path = rb_enc_path_next(path + 1, end, enc); + if ((path = enc_path_next(path, end, mb_enc, enc)) < end && path[0] && path[1] && !isdirsep(path[1])) + path = enc_path_next(path + 1, end, mb_enc, enc); return (char *)path; } #endif @@ -3445,23 +3683,28 @@ rb_enc_path_skip_prefix(const char *path, const char *end, rb_encoding *enc) if (has_drive_letter(path)) return (char *)(path + 2); #endif -#endif +#endif /* defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) */ return (char *)path; } +char * +rb_enc_path_skip_prefix(const char *path, const char *end, rb_encoding *enc) +{ + return enc_path_skip_prefix(path, end, true, enc); +} + static inline char * skipprefixroot(const char *path, const char *end, rb_encoding *enc) { #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) - char *p = skipprefix(path, end, enc); + char *p = skipprefix(path, end, true, enc); while (isdirsep(*p)) p++; return p; #else - return skiproot(path, end, enc); + return skiproot(path, end); #endif } -#define strrdirsep rb_enc_path_last_separator char * rb_enc_path_last_separator(const char *path, const char *end, rb_encoding *enc) { @@ -3474,14 +3717,39 @@ rb_enc_path_last_separator(const char *path, const char *end, rb_encoding *enc) last = (char *)tmp; } else { - Inc(path, end, enc); + Inc(path, end, true, enc); } } return last; } +static inline char * +strrdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc) +{ + if (RB_UNLIKELY(mb_enc)) { + return rb_enc_path_last_separator(path, end, enc); + } + + const char *cursor = end - 1; + + while (isdirsep(cursor[0])) { + cursor--; + } + + while (cursor >= path) { + if (isdirsep(cursor[0])) { + while (cursor > path && isdirsep(cursor[-1])) { + cursor--; + } + return (char *)cursor; + } + cursor--; + } + return NULL; +} + static char * -chompdirsep(const char *path, const char *end, rb_encoding *enc) +chompdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc) { while (path < end) { if (isdirsep(*path)) { @@ -3490,7 +3758,7 @@ chompdirsep(const char *path, const char *end, rb_encoding *enc) if (path >= end) return (char *)last; } else { - Inc(path, end, enc); + Inc(path, end, mb_enc, enc); } } return (char *)path; @@ -3500,13 +3768,13 @@ char * rb_enc_path_end(const char *path, const char *end, rb_encoding *enc) { if (path < end && isdirsep(*path)) path++; - return chompdirsep(path, end, enc); + return chompdirsep(path, end, true, enc); } static rb_encoding * fs_enc_check(VALUE path1, VALUE path2) { - rb_encoding *enc = rb_enc_check(path1, path2); + rb_encoding *enc = rb_enc_check_str(path1, path2); int encidx = rb_enc_to_index(enc); if (encidx == ENCINDEX_US_ASCII) { encidx = rb_enc_get_index(path1); @@ -3535,12 +3803,12 @@ ntfs_tail(const char *path, const char *end, rb_encoding *enc) if (isADS(*path)) path++; } else { - Inc(path, end, enc); + Inc(path, end, true, enc); } } return (char *)path; } -#endif +#endif /* USE_NTFS */ #define BUFCHECK(cond) do {\ bdiff = p - buf;\ @@ -3597,7 +3865,7 @@ copy_home_path(VALUE result, const char *dir) rb_enc_associate_index(result, encidx); #if defined DOSISH || defined __CYGWIN__ enc = rb_enc_from_index(encidx); - for (bend = (p = buf) + dirlen; p < bend; Inc(p, bend, enc)) { + for (bend = (p = buf) + dirlen; p < bend; Inc(p, bend, true, enc)) { if (*p == '\\') { *p = '/'; } @@ -3610,12 +3878,16 @@ VALUE rb_home_dir_of(VALUE user, VALUE result) { #ifdef HAVE_PWD_H - struct passwd *pwPtr; + VALUE dirname = rb_getpwdirnam_for_login(user); + if (dirname == Qnil) { + rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user); + } + const char *dir = RSTRING_PTR(dirname); #else extern char *getlogin(void); const char *pwPtr = 0; + const char *login; # define endpwent() ((void)0) -#endif const char *dir, *username = RSTRING_PTR(user); rb_encoding *enc = rb_enc_get(user); #if defined _WIN32 @@ -3627,25 +3899,17 @@ rb_home_dir_of(VALUE user, VALUE result) dir = username = RSTRING_PTR(rb_str_conv_enc(user, enc, fsenc)); } -#ifdef HAVE_PWD_H - pwPtr = getpwnam(username); -#else - if (strcasecmp(username, getlogin()) == 0) + if ((login = getlogin()) && strcasecmp(username, login) == 0) dir = pwPtr = getenv("HOME"); -#endif if (!pwPtr) { - endpwent(); rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user); } -#ifdef HAVE_PWD_H - dir = pwPtr->pw_dir; #endif copy_home_path(result, dir); - endpwent(); return result; } -#ifndef _WIN32 +#ifndef _WIN32 /* this encompasses rb_file_expand_path_internal */ VALUE rb_default_home_dir(VALUE result) { @@ -3673,15 +3937,15 @@ rb_default_home_dir(VALUE result) * lookup by getuid() has a chance of succeeding. */ if (NIL_P(login_name)) { - rb_raise(rb_eArgError, "couldn't find login name -- expanding `~'"); + rb_raise(rb_eArgError, "couldn't find login name -- expanding '~'"); } -# endif +# endif /* !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID) */ VALUE pw_dir = rb_getpwdirnam_for_login(login_name); if (NIL_P(pw_dir)) { pw_dir = rb_getpwdiruid(); if (NIL_P(pw_dir)) { - rb_raise(rb_eArgError, "couldn't find home for uid `%ld'", (long)getuid()); + rb_raise(rb_eArgError, "couldn't find home for uid '%ld'", (long)getuid()); } } @@ -3690,9 +3954,9 @@ rb_default_home_dir(VALUE result) rb_str_resize(pw_dir, 0); return result; } -#endif +#endif /* defined HAVE_PWD_H */ if (!dir) { - rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding `~'"); + rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'"); } return copy_home_path(result, dir); } @@ -3717,7 +3981,15 @@ append_fspath(VALUE result, VALUE fname, char *dir, rb_encoding **enc, rb_encodi size_t dirlen = strlen(dir), buflen = rb_str_capacity(result); if (NORMALIZE_UTF8PATH || *enc != fsenc) { - rb_encoding *direnc = fs_enc_check(fname, dirname = ospath_new(dir, dirlen, fsenc)); + dirname = ospath_new(dir, dirlen, fsenc); + if (!rb_enc_compatible(fname, dirname)) { + xfree(dir); + /* rb_enc_check must raise because the two encodings are not + * compatible. */ + rb_enc_check(fname, dirname); + rb_bug("unreachable"); + } + rb_encoding *direnc = fs_enc_check(fname, dirname); if (direnc != fsenc) { dirname = rb_str_conv_enc(dirname, fsenc, direnc); RSTRING_GETMEM(dirname, cwdp, dirlen); @@ -3816,11 +4088,11 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na rb_enc_associate(result, enc = fs_enc_check(result, fname)); p = pend; } - p = chompdirsep(skiproot(buf, p, enc), p, enc); + p = chompdirsep(skiproot(buf, p), p, true, enc); s += 2; } } -#endif +#endif /* DOSISH_DRIVE_LETTER */ else if (!rb_is_absolute_path(s)) { if (!NIL_P(dname)) { rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result); @@ -3837,11 +4109,11 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na if (isdirsep(*s)) { /* specified full path, but not drive letter nor UNC */ /* we need to get the drive letter or UNC share name */ - p = skipprefix(buf, p, enc); + p = skipprefix(buf, p, true, enc); } else -#endif - p = chompdirsep(skiproot(buf, p, enc), p, enc); +#endif /* defined DOSISH || defined __CYGWIN__ */ + p = chompdirsep(skiproot(buf, p), p, true, enc); } else { size_t len; @@ -3865,7 +4137,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na rb_str_set_len(result, p-buf+1); BUFCHECK(bdiff + 1 >= buflen); p[1] = 0; - root = skipprefix(buf, p+1, enc); + root = skipprefix(buf, p+1, true, enc); b = s; while (*s) { @@ -3881,7 +4153,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na /* We must go back to the parent */ char *n; *p = '\0'; - if (!(n = strrdirsep(root, p, enc))) { + if (!(n = strrdirsep(root, p, true, enc))) { *p = '/'; } else { @@ -3893,7 +4165,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na else { do ++s; while (istrailinggarbage(*s)); } -#endif +#endif /* USE_NTFS */ break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -3918,7 +4190,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na } } } -#endif +#endif /* USE_NTFS */ break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -3943,8 +4215,8 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na break; } } -#endif - Inc(s, fend, enc); +#endif /* __APPLE__ */ + Inc(s, fend, true, enc); break; } } @@ -3967,12 +4239,12 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na s -= prime_len; /* alternative */ } } -# endif -#endif +# endif /* USE_NTFS_ADS */ +#endif /* USE_NTFS */ BUFCOPY(b, s-b); rb_str_set_len(result, p-buf); } - if (p == skiproot(buf, p + !!*p, enc) - 1) p++; + if (p == skiproot(buf, p + !!*p) - 1) p++; #if USE_NTFS *p = '\0'; @@ -3989,7 +4261,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na const int flags = CCP_POSIX_TO_WIN_A | CCP_RELATIVE; #else char w32buf[MAXPATHLEN]; -#endif +#endif /* HAVE_CYGWIN_CONV_PATH */ const char *path; ssize_t bufsize; int lnk_added = 0, is_symlink = 0; @@ -4013,12 +4285,12 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na b = w32buf; } } -#else +#else /* !HAVE_CYGWIN_CONV_PATH */ bufsize = MAXPATHLEN; if (cygwin_conv_to_win32_path(path, w32buf) == 0) { b = w32buf; } -#endif +#endif /* !HAVE_CYGWIN_CONV_PATH */ if (is_symlink && b == w32buf) { *p = '\\'; strlcat(w32buf, p, bufsize); @@ -4030,7 +4302,7 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na lnk_added = 0; } *p = '/'; -#endif +#endif /* __CYGWIN__ */ rb_str_set_len(result, p - buf + strlen(p)); encidx = ENCODING_GET(result); tmp = result; @@ -4078,14 +4350,14 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na } #endif } -#endif +#endif /* USE_NTFS */ rb_str_set_len(result, p - buf); rb_enc_check(fname, result); ENC_CODERANGE_CLEAR(result); return result; } -#endif /* _WIN32 */ +#endif /* !_WIN32 (this ifdef started above rb_default_home_dir) */ #define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, 1) @@ -4244,7 +4516,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f if (*prefixlenp < RSTRING_LEN(*resolvedp)) { const char *resolved_str = RSTRING_PTR(*resolvedp); const char *resolved_names = resolved_str + *prefixlenp; - const char *lastsep = strrdirsep(resolved_names, resolved_str + RSTRING_LEN(*resolvedp), enc); + const char *lastsep = strrdirsep(resolved_names, resolved_str + RSTRING_LEN(*resolvedp), true, enc); long len = lastsep ? lastsep - resolved_names : 0; rb_str_resize(*resolvedp, *prefixlenp + len); } @@ -4325,7 +4597,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp)); } else -#endif +#endif /* HAVE_READLINK */ { VALUE s = rb_str_dup_frozen(testpath); rb_hash_aset(loopcheck, s, s); @@ -4384,7 +4656,7 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum root_found: RSTRING_GETMEM(resolved, prefixptr, prefixlen); pend = prefixptr + prefixlen; - ptr = chompdirsep(prefixptr, pend, enc); + ptr = chompdirsep(prefixptr, pend, true, enc); if (ptr < pend) { prefixlen = ++ptr - prefixptr; rb_str_set_len(resolved, prefixlen); @@ -4394,7 +4666,7 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum if (*prefixptr == FILE_ALT_SEPARATOR) { *prefixptr = '/'; } - Inc(prefixptr, pend, enc); + Inc(prefixptr, pend, true, enc); } #endif @@ -4430,7 +4702,7 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum return resolved; } -static VALUE rb_file_join(VALUE ary); +static VALUE rb_file_join(long argc, VALUE *args); #ifndef HAVE_REALPATH static VALUE @@ -4445,6 +4717,11 @@ rb_check_realpath_emulate_rescue(VALUE arg, VALUE exc) { return Qnil; } +#elif !defined(NEEDS_REALPATH_BUFFER) && defined(__APPLE__) && \ + (!defined(MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6)) +/* realpath() on OSX < 10.6 doesn't implement automatic allocation */ +# include <sys/syslimits.h> +# define NEEDS_REALPATH_BUFFER 1 #endif /* HAVE_REALPATH */ static VALUE @@ -4454,6 +4731,11 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum VALUE unresolved_path; char *resolved_ptr = NULL; VALUE resolved; +# if defined(NEEDS_REALPATH_BUFFER) && NEEDS_REALPATH_BUFFER + char resolved_buffer[PATH_MAX]; +# else + char *const resolved_buffer = NULL; +# endif if (mode == RB_REALPATH_DIR) { return rb_check_realpath_emulate(basedir, path, origenc, mode); @@ -4461,17 +4743,22 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum unresolved_path = rb_str_dup_frozen(path); if (*RSTRING_PTR(unresolved_path) != '/' && !NIL_P(basedir)) { - unresolved_path = rb_file_join(rb_assoc_new(basedir, unresolved_path)); + VALUE paths[2] = {basedir, unresolved_path}; + unresolved_path = rb_file_join(2, paths); } if (origenc) unresolved_path = TO_OSPATH(unresolved_path); - if ((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) { - /* glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb, + if ((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), resolved_buffer)) == NULL) { + /* + wasi-libc 22 and later support realpath(3) but return ENOTSUP + when the underlying host syscall returns it. + glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb, returning ENOTDIR in that case. glibc realpath(3) can also return ENOENT for paths that exist, such as /dev/fd/5. Fallback to the emulated approach in either of those cases. */ - if (errno == ENOTDIR || + if (errno == ENOTSUP || + errno == ENOTDIR || (errno == ENOENT && rb_file_exist_p(0, unresolved_path))) { return rb_check_realpath_emulate(basedir, path, origenc, mode); @@ -4482,9 +4769,11 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_sys_fail_path(unresolved_path); } resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding()); +# if !(defined(NEEDS_REALPATH_BUFFER) && NEEDS_REALPATH_BUFFER) free(resolved_ptr); +# endif -# if !defined(__LINUX__) && !defined(__APPLE__) +# if !defined(__linux__) && !defined(__APPLE__) /* As `resolved` is a String in the filesystem encoding, no * conversion is needed */ struct stat st; @@ -4494,7 +4783,7 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum } rb_sys_fail_path(unresolved_path); } -# endif +# endif /* !defined(__linux__) && !defined(__APPLE__) */ if (origenc && origenc != rb_enc_get(resolved)) { if (!rb_enc_str_asciionly_p(resolved)) { @@ -4512,7 +4801,7 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum RB_GC_GUARD(unresolved_path); return resolved; -#else +#else /* !HAVE_REALPATH */ if (mode == RB_REALPATH_CHECK) { VALUE arg[3]; arg[0] = basedir; @@ -4609,7 +4898,7 @@ rmext(const char *p, long l0, long l1, const char *e, long l2, rb_encoding *enc) if (l1 < l2) return l1; s = p+l1-l2; - if (rb_enc_left_char_head(p, s, p+l1, enc) != s) return 0; + if (!at_char_boundary(p, s, p+l1, enc)) return 0; #if CASEFOLD_FILESYSTEM #define fncomp strncasecmp #else @@ -4621,8 +4910,8 @@ rmext(const char *p, long l0, long l1, const char *e, long l2, rb_encoding *enc) return 0; } -const char * -ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encoding *enc) +static inline const char * +enc_find_basename(const char *name, long *baselen, long *alllen, bool mb_enc, rb_encoding *enc) { const char *p, *q, *e, *end; #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC @@ -4630,13 +4919,22 @@ ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encodin #endif long f = 0, n = -1; - end = name + (alllen ? (size_t)*alllen : strlen(name)); - name = skipprefix(name, end, enc); + long len = (alllen ? (size_t)*alllen : strlen(name)); + + if (len <= 0) { + return name; + } + + end = name + len; + name = skipprefix(name, end, mb_enc, enc); #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC root = name; #endif - while (isdirsep(*name)) + + while (isdirsep(*name)) { name++; + } + if (!*name) { p = name - 1; f = 1; @@ -4649,41 +4947,56 @@ ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encodin p++; f = 0; } -#endif +#endif /* DOSISH_DRIVE_LETTER */ #ifdef DOSISH_UNC else { p = "/"; } -#endif -#endif +#endif /* DOSISH_UNC */ +#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */ } else { - if (!(p = strrdirsep(name, end, enc))) { + p = strrdirsep(name, end, mb_enc, enc); + if (!p) { p = name; } else { - while (isdirsep(*p)) p++; /* skip last / */ + while (isdirsep(*p)) { + p++; /* skip last / */ + } } #if USE_NTFS n = ntfs_tail(p, end, enc) - p; #else - n = chompdirsep(p, end, enc) - p; + n = chompdirsep(p, end, mb_enc, enc) - p; #endif for (q = p; q - p < n && *q == '.'; q++); - for (e = 0; q - p < n; Inc(q, end, enc)) { + for (e = 0; q - p < n; Inc(q, end, mb_enc, enc)) { if (*q == '.') e = q; } - if (e) f = e - p; - else f = n; + if (e) { + f = e - p; + } + else { + f = n; + } } - if (baselen) + if (baselen) { *baselen = f; - if (alllen) + } + if (alllen) { *alllen = n; + } return p; } +const char * +ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encoding *enc) +{ + return enc_find_basename(name, baselen, alllen, true, enc); +} + /* * call-seq: * File.basename(file_name [, suffix] ) -> base_name @@ -4704,7 +5017,7 @@ ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encodin static VALUE rb_file_s_basename(int argc, VALUE *argv, VALUE _) { - VALUE fname, fext, basename; + VALUE fname, fext; const char *name, *p; long f, n; rb_encoding *enc; @@ -4713,18 +5026,23 @@ rb_file_s_basename(int argc, VALUE *argv, VALUE _) if (rb_check_arity(argc, 1, 2) == 2) { fext = argv[1]; StringValue(fext); - enc = check_path_encoding(fext); + check_path_encoding(fext); + enc = rb_str_enc_get(fext); } fname = argv[0]; - FilePathStringValue(fname); + CheckPath(fname, name); if (NIL_P(fext) || !(enc = rb_enc_compatible(fname, fext))) { - enc = rb_enc_get(fname); + enc = rb_str_enc_get(fname); fext = Qnil; } - if ((n = RSTRING_LEN(fname)) == 0 || !*(name = RSTRING_PTR(fname))) - return rb_str_new_shared(fname); - p = ruby_enc_find_basename(name, &f, &n, enc); + n = RSTRING_LEN(fname); + if (n == 0 || !*name) { + rb_enc_str_new(0, 0, enc); + } + + bool mb_enc = !rb_str_encindex_fastpath(rb_enc_to_index(enc)); + p = enc_find_basename(name, &f, &n, mb_enc, enc); if (n >= 0) { if (NIL_P(fext)) { f = n; @@ -4737,12 +5055,12 @@ rb_file_s_basename(int argc, VALUE *argv, VALUE _) } RB_GC_GUARD(fext); } - if (f == RSTRING_LEN(fname)) return rb_str_new_shared(fname); + if (f == RSTRING_LEN(fname)) { + return rb_str_new_shared(fname); + } } - basename = rb_str_new(p, f); - rb_enc_copy(basename, fname); - return basename; + return rb_enc_str_new(p, f, enc); } static VALUE rb_file_dirname_n(VALUE fname, int n); @@ -4787,19 +5105,18 @@ rb_file_dirname_n(VALUE fname, int n) { const char *name, *root, *p, *end; VALUE dirname; - rb_encoding *enc; - VALUE sepsv = 0; - const char **seps; if (n < 0) rb_raise(rb_eArgError, "negative level: %d", n); - FilePathStringValue(fname); - name = StringValueCStr(fname); + CheckPath(fname, name); end = name + RSTRING_LEN(fname); - enc = rb_enc_get(fname); - root = skiproot(name, end, enc); + + bool mb_enc = !rb_str_enc_fastpath(fname); + rb_encoding *enc = rb_str_enc_get(fname); + + root = skiproot(name, end); #ifdef DOSISH_UNC if (root > name + 1 && isdirsep(*name)) - root = skipprefix(name = root - 2, end, enc); + root = skipprefix(name = root - 2, end, mb_enc, enc); #else if (root > name + 1) name = root - 1; @@ -4808,72 +5125,41 @@ rb_file_dirname_n(VALUE fname, int n) p = root; } else { - int i; - switch (n) { - case 0: - p = end; - break; - case 1: - if (!(p = strrdirsep(root, end, enc))) p = root; - break; - default: - seps = ALLOCV_N(const char *, sepsv, n); - for (i = 0; i < n; ++i) seps[i] = root; - i = 0; - for (p = root; p < end; ) { - if (isdirsep(*p)) { - const char *tmp = p++; - while (p < end && isdirsep(*p)) p++; - if (p >= end) break; - seps[i++] = tmp; - if (i == n) i = 0; - } - else { - Inc(p, end, enc); - } + p = end; + while (n) { + if (!(p = strrdirsep(root, p, mb_enc, enc))) { + p = root; + break; } - p = seps[i]; - ALLOCV_END(sepsv); - break; + n--; } } - if (p == name) - return rb_usascii_str_new2("."); + + if (p == name) { + return rb_enc_str_new(".", 1, enc); + } #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && isdirsep(*(name + 2))) { - const char *top = skiproot(name + 2, end, enc); - dirname = rb_str_new(name, 3); + const char *top = skiproot(name + 2, end); + dirname = rb_enc_str_new(name, 3, enc); rb_str_cat(dirname, top, p - top); } else #endif - dirname = rb_str_new(name, p - name); + dirname = rb_enc_str_new(name, p - name, enc); #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && root == name + 2 && p - name == 2) rb_str_cat(dirname, ".", 1); #endif - rb_enc_copy(dirname, fname); return dirname; } -/* - * accept a String, and return the pointer of the extension. - * if len is passed, set the length of extension to it. - * returned pointer is in ``name'' or NULL. - * returns *len - * no dot NULL 0 - * dotfile top 0 - * end with dot dot 1 - * .ext dot len of .ext - * .ext:stream dot len of .ext without :stream (NTFS only) - * - */ -const char * -ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) +static inline const char * +enc_find_extname(const char *name, long *len, bool mb_enc, rb_encoding *enc) { const char *p, *e, *end = name + (len ? *len : (long)strlen(name)); - p = strrdirsep(name, end, enc); /* get the last path component */ + p = strrdirsep(name, end, mb_enc, enc); /* get the last path component */ if (!p) p = name; else @@ -4897,7 +5183,7 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) continue; #else e = p; /* get the last dot of the last component */ -#endif +#endif /* USE_NTFS */ } #if USE_NTFS else if (isADS(*p)) { @@ -4906,7 +5192,7 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) #endif else if (isdirsep(*p)) break; - Inc(p, end, enc); + Inc(p, end, mb_enc, enc); } if (len) { @@ -4922,6 +5208,24 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) } /* + * accept a String, and return the pointer of the extension. + * if len is passed, set the length of extension to it. + * returned pointer is in ``name'' or NULL. + * returns *len + * no dot NULL 0 + * dotfile top 0 + * end with dot dot 1 + * .ext dot len of .ext + * .ext:stream dot len of .ext without :stream (NTFS only) + * + */ +const char * +ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) +{ + return enc_find_extname(name, len, true, enc); +} + +/* * call-seq: * File.extname(path) -> string * @@ -4950,18 +5254,19 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) static VALUE rb_file_s_extname(VALUE klass, VALUE fname) { - const char *name, *e; - long len; - VALUE extname; + const char *name; + CheckPath(fname, name); + long len = RSTRING_LEN(fname); - FilePathStringValue(fname); - name = StringValueCStr(fname); - len = RSTRING_LEN(fname); - e = ruby_enc_find_extname(name, &len, rb_enc_get(fname)); - if (len < 1) - return rb_str_new(0, 0); - extname = rb_str_subseq(fname, e - name, len); /* keep the dot, too! */ - return extname; + if (len < 1) { + return rb_enc_str_new(0, 0, rb_str_enc_get(fname)); + } + + bool mb_enc = !rb_str_enc_fastpath(fname); + rb_encoding *enc = rb_str_enc_get(fname); + + const char *ext = enc_find_extname(name, &len, mb_enc, enc); + return rb_enc_str_new(ext, len, enc); } /* @@ -4970,9 +5275,25 @@ rb_file_s_extname(VALUE klass, VALUE fname) * * Returns the string representation of the path * - * File.path("/dev/null") #=> "/dev/null" + * File.path(File::NULL) #=> "/dev/null" * File.path(Pathname.new("/tmp")) #=> "/tmp" * + * If +path+ is not a String: + * + * 1. If it has the +to_path+ method, that method will be called to + * coerce to a String. + * + * 2. Otherwise, or if the coerced result is not a String too, the + * standard coersion using +to_str+ method will take place on that + * object. (See also String.try_convert) + * + * The coerced string must satisfy the following conditions: + * + * 1. It must be in an ASCII-compatible encoding; otherwise, an + * Encoding::CompatibilityError is raised. + * + * 2. It must not contain the NUL character (<tt>\0</tt>); otherwise, + * an ArgumentError is raised. */ static VALUE @@ -4999,15 +5320,17 @@ rb_file_s_split(VALUE klass, VALUE path) return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path,Qundef)); } +static VALUE rb_file_join_ary(VALUE ary); + static VALUE file_inspect_join(VALUE ary, VALUE arg, int recur) { if (recur || ary == arg) rb_raise(rb_eArgError, "recursive array"); - return rb_file_join(arg); + return rb_file_join_ary(arg); } static VALUE -rb_file_join(VALUE ary) +rb_file_join_ary(VALUE ary) { long len, i; VALUE result, tmp; @@ -5055,7 +5378,7 @@ rb_file_join(VALUE ary) rb_enc_copy(result, tmp); } else { - tail = chompdirsep(name, name + len, rb_enc_get(result)); + tail = chompdirsep(name, name + len, true, rb_enc_get(result)); if (RSTRING_PTR(tmp) && isdirsep(RSTRING_PTR(tmp)[0])) { rb_str_set_len(result, tail - name); } @@ -5072,6 +5395,77 @@ rb_file_join(VALUE ary) return result; } +static inline VALUE +rb_file_join_fastpath(long argc, VALUE *args) +{ + long size = argc; + + long i; + for (i = 0; i < argc; i++) { + VALUE tmp = args[i]; + if (RB_LIKELY(RB_TYPE_P(tmp, T_STRING) && rb_str_enc_fastpath(tmp))) { + size += RSTRING_LEN(tmp); + } + else { + return 0; + } + } + + VALUE result = rb_str_buf_new(size); + + int encidx = ENCODING_GET_INLINED(args[0]); + ENCODING_SET_INLINED(result, encidx); + rb_str_buf_append(result, args[0]); + + const char *name = RSTRING_PTR(result); + for (i = 1; i < argc; i++) { + VALUE tmp = args[i]; + long len = RSTRING_LEN(result); + + const char *tmp_s; + long tmp_len; + RSTRING_GETMEM(tmp, tmp_s, tmp_len); + + if (isdirsep(tmp_s[0])) { + // right side has a leading separator, remove left side separators. + long trailing_seps = 0; + while (isdirsep(name[len - trailing_seps - 1])) { + trailing_seps++; + } + rb_str_set_len(result, len - trailing_seps); + } + else if (!isdirsep(name[len - 1])) { + // neither side have a separator, append one; + rb_str_cat(result, "/", 1); + } + + if (RB_UNLIKELY(ENCODING_GET_INLINED(tmp) != encidx)) { + rb_encoding *new_enc = fs_enc_check(result, tmp); + rb_enc_associate(result, new_enc); + encidx = rb_enc_to_index(new_enc); + } + + rb_str_buf_cat(result, tmp_s, tmp_len); + } + + rb_str_null_check(result); + return result; +} + +static inline VALUE +rb_file_join(long argc, VALUE *args) +{ + if (RB_UNLIKELY(argc == 0)) { + return rb_str_new(0, 0); + } + + VALUE result = rb_file_join_fastpath(argc, args); + if (RB_LIKELY(result)) { + return result; + } + + return rb_file_join_ary(rb_ary_new_from_values(argc, args)); +} /* * call-seq: * File.join(string, ...) -> string @@ -5084,9 +5478,9 @@ rb_file_join(VALUE ary) */ static VALUE -rb_file_s_join(VALUE klass, VALUE args) +rb_file_s_join(int argc, VALUE *argv, VALUE klass) { - return rb_file_join(args); + return rb_file_join(argc, argv); } #if defined(HAVE_TRUNCATE) @@ -5128,8 +5522,7 @@ rb_file_s_truncate(VALUE klass, VALUE path, VALUE len) path = rb_str_encode_ospath(path); ta.path = StringValueCStr(path); - r = (int)(VALUE)rb_thread_call_without_gvl(nogvl_truncate, &ta, - RUBY_UBF_IO, NULL); + r = IO_WITHOUT_GVL_INT(nogvl_truncate, &ta); if (r < 0) rb_sys_fail_path(path); return INT2FIX(0); @@ -5179,7 +5572,7 @@ rb_file_truncate(VALUE obj, VALUE len) } rb_io_flush_raw(obj, 0); fa.fd = fptr->fd; - if ((int)rb_thread_io_blocking_region(nogvl_ftruncate, &fa, fa.fd) < 0) { + if ((int)rb_io_blocking_region(fptr, nogvl_ftruncate, &fa) < 0) { rb_sys_fail_path(fptr->pathv); } return INT2FIX(0); @@ -5222,47 +5615,46 @@ rb_thread_flock(void *data) return (VALUE)ret; } -/* +/* :markup: markdown + * * call-seq: - * file.flock(locking_constant) -> 0 or false - * - * Locks or unlocks a file according to <i>locking_constant</i> (a - * logical <em>or</em> of the values in the table below). - * Returns <code>false</code> if File::LOCK_NB is specified and the - * operation would otherwise have blocked. Not available on all - * platforms. - * - * Locking constants (in class File): - * - * LOCK_EX | Exclusive lock. Only one process may hold an - * | exclusive lock for a given file at a time. - * ----------+------------------------------------------------ - * LOCK_NB | Don't block when locking. May be combined - * | with other lock options using logical or. - * ----------+------------------------------------------------ - * LOCK_SH | Shared lock. Multiple processes may each hold a - * | shared lock for a given file at the same time. - * ----------+------------------------------------------------ - * LOCK_UN | Unlock. + * flock(locking_constant) -> 0 or false + * + * Locks or unlocks file `self` according to the given `locking_constant`, + * a bitwise OR of the values in the table below. + * + * Not available on all platforms. + * + * Returns `false` if `File::LOCK_NB` is specified and the operation would have blocked; + * otherwise returns `0`. + * + * | Constant | Lock | Effect + * |-----------------|--------------|-----------------------------------------------------------------------------------------------------------------| + * | `File::LOCK_EX` | Exclusive | Only one process may hold an exclusive lock for `self` at a time. | + * | `File::LOCK_NB` | Non-blocking | No blocking; may be combined with `File::LOCK_SH` or `File::LOCK_EX` using the bitwise OR operator <tt>\|</tt>. | + * | `File::LOCK_SH` | Shared | Multiple processes may each hold a shared lock for `self` at the same time. | + * | `File::LOCK_UN` | Unlock | Remove an existing lock held by this process. | * * Example: * - * # update a counter using write lock - * # don't use "w" because it truncates the file before lock. - * File.open("counter", File::RDWR|File::CREAT, 0644) {|f| - * f.flock(File::LOCK_EX) - * value = f.read.to_i + 1 - * f.rewind - * f.write("#{value}\n") - * f.flush - * f.truncate(f.pos) - * } - * - * # read the counter using read lock - * File.open("counter", "r") {|f| - * f.flock(File::LOCK_SH) - * p f.read - * } + * ```ruby + * # Update a counter using an exclusive lock. + * # Don't use File::WRONLY because it truncates the file. + * File.open('counter', File::RDWR | File::CREAT, 0644) do |f| + * f.flock(File::LOCK_EX) + * value = f.read.to_i + 1 + * f.rewind + * f.write("#{value}\n") + * f.flush + * f.truncate(f.pos) + * end + * + * # Read the counter using a shared lock. + * File.open('counter', 'r') do |f| + * f.flock(File::LOCK_SH) + * f.read + * end + * ``` * */ @@ -5280,7 +5672,7 @@ rb_file_flock(VALUE obj, VALUE operation) if (fptr->mode & FMODE_WRITABLE) { rb_io_flush_raw(obj, 0); } - while ((int)rb_thread_io_blocking_region(rb_thread_flock, op, fptr->fd) < 0) { + while ((int)rb_io_blocking_region(fptr, rb_thread_flock, op) < 0) { int e = errno; switch (e) { case EAGAIN: @@ -5326,60 +5718,83 @@ test_check(int n, int argc, VALUE *argv) #define CHECK(n) test_check((n), argc, argv) /* + * :markup: markdown + * * call-seq: - * test(cmd, file1 [, file2] ) -> obj - * - * Uses the character +cmd+ to perform various tests on +file1+ (first - * table below) or on +file1+ and +file2+ (second table). - * - * File tests on a single file: - * - * Cmd Returns Meaning - * "A" | Time | Last access time for file1 - * "b" | boolean | True if file1 is a block device - * "c" | boolean | True if file1 is a character device - * "C" | Time | Last change time for file1 - * "d" | boolean | True if file1 exists and is a directory - * "e" | boolean | True if file1 exists - * "f" | boolean | True if file1 exists and is a regular file - * "g" | boolean | True if file1 has the setgid bit set - * "G" | boolean | True if file1 exists and has a group - * | | ownership equal to the caller's group - * "k" | boolean | True if file1 exists and has the sticky bit set - * "l" | boolean | True if file1 exists and is a symbolic link - * "M" | Time | Last modification time for file1 - * "o" | boolean | True if file1 exists and is owned by - * | | the caller's effective uid - * "O" | boolean | True if file1 exists and is owned by - * | | the caller's real uid - * "p" | boolean | True if file1 exists and is a fifo - * "r" | boolean | True if file1 is readable by the effective - * | | uid/gid of the caller - * "R" | boolean | True if file is readable by the real - * | | uid/gid of the caller - * "s" | int/nil | If file1 has nonzero size, return the size, - * | | otherwise return nil - * "S" | boolean | True if file1 exists and is a socket - * "u" | boolean | True if file1 has the setuid bit set - * "w" | boolean | True if file1 exists and is writable by - * | | the effective uid/gid - * "W" | boolean | True if file1 exists and is writable by - * | | the real uid/gid - * "x" | boolean | True if file1 exists and is executable by - * | | the effective uid/gid - * "X" | boolean | True if file1 exists and is executable by - * | | the real uid/gid - * "z" | boolean | True if file1 exists and has a zero length - * - * Tests that take two files: - * - * "-" | boolean | True if file1 and file2 are identical - * "=" | boolean | True if the modification times of file1 - * | | and file2 are equal - * "<" | boolean | True if the modification time of file1 - * | | is prior to that of file2 - * ">" | boolean | True if the modification time of file1 - * | | is after that of file2 + * test(char, path0, path1 = nil) -> object + * + * Performs a test on one or both of the <i>filesystem entities</i> at the given paths + * `path0` and `path1`: + * + * - Each path `path0` or `path1` points to a file, directory, device, pipe, etc. + * - Character `char` selects a specific test. + * + * The tests: + * + * - Each of these tests operates only on the entity at `path0`, + * and returns `true` or `false`; + * for a non-existent entity, returns `false` (does not raise exception): + * + * | Character | Test | + * |:------------:|:--------------------------------------------------------------------------| + * | <tt>'b'</tt> | Whether the entity is a block device. | + * | <tt>'c'</tt> | Whether the entity is a character device. | + * | <tt>'d'</tt> | Whether the entity is a directory. | + * | <tt>'e'</tt> | Whether the entity is an existing entity. | + * | <tt>'f'</tt> | Whether the entity is an existing regular file. | + * | <tt>'g'</tt> | Whether the entity's setgid bit is set. | + * | <tt>'G'</tt> | Whether the entity's group ownership is equal to the caller's. | + * | <tt>'k'</tt> | Whether the entity's sticky bit is set. | + * | <tt>'l'</tt> | Whether the entity is a symbolic link. | + * | <tt>'o'</tt> | Whether the entity is owned by the caller's effective uid. | + * | <tt>'O'</tt> | Like <tt>'o'</tt>, but uses the real uid (not the effective uid). | + * | <tt>'p'</tt> | Whether the entity is a FIFO device (named pipe). | + * | <tt>'r'</tt> | Whether the entity is readable by the caller's effective uid/gid. | + * | <tt>'R'</tt> | Like <tt>'r'</tt>, but uses the real uid/gid (not the effective uid/gid). | + * | <tt>'S'</tt> | Whether the entity is a socket. | + * | <tt>'u'</tt> | Whether the entity's setuid bit is set. | + * | <tt>'w'</tt> | Whether the entity is writable by the caller's effective uid/gid. | + * | <tt>'W'</tt> | Like <tt>'w'</tt>, but uses the real uid/gid (not the effective uid/gid). | + * | <tt>'x'</tt> | Whether the entity is executable by the caller's effective uid/gid. | + * | <tt>'X'</tt> | Like <tt>'x'</tt>, but uses the real uid/gid (not the effective uid/git). | + * | <tt>'z'</tt> | Whether the entity exists and is of length zero. | + * + * - This test operates only on the entity at `path0`, + * and returns an integer size or `nil`: + * + * | Character | Test | + * |:------------:|:---------------------------------------------------------------------------------------------| + * | <tt>'s'</tt> | Returns positive integer size if the entity exists and has non-zero length, `nil` otherwise. | + * + * - Each of these tests operates only on the entity at `path0`, + * and returns a Time object; + * raises an exception if the entity does not exist: + * + * | Character | Test | + * |:------------:|:---------------------------------------| + * | <tt>'A'</tt> | Last access time for the entity. | + * | <tt>'C'</tt> | Last change time for the entity. | + * | <tt>'M'</tt> | Last modification time for the entity. | + * + * - Each of these tests operates on the modification time (`mtime`) + * of each of the entities at `path0` and `path1`, + * and returns a `true` or `false`; + * returns `false` if either entity does not exist: + * + * | Character | Test | + * |:------------:|:----------------------------------------------------------------| + * | <tt>'<'</tt> | Whether the `mtime` at `path0` is less than that at `path1`. | + * | <tt>'='</tt> | Whether the `mtime` at `path0` is equal to that at `path1`. | + * | <tt>'>'</tt> | Whether the `mtime` at `path0` is greater than that at `path1`. | + * + * - This test operates on the content of each of the entities at `path0` and `path1`, + * and returns a `true` or `false`; + * returns `false` if either entity does not exist: + * + * | Character | Test | + * |:------------:|:----------------------------------------------| + * | <tt>'-'</tt> | Whether the entities exist and are identical. | + * */ static VALUE @@ -5491,7 +5906,7 @@ rb_f_test(int argc, VALUE *argv, VALUE _) if (strchr("=<>", cmd)) { struct stat st1, st2; - struct timespec t1, t2; + stat_timestamp t1, t2; CHECK(2); if (rb_stat(argv[1], &st1) < 0) return Qfalse; @@ -5543,12 +5958,13 @@ rb_f_test(int argc, VALUE *argv, VALUE _) static VALUE rb_stat_s_alloc(VALUE klass) { - return stat_new_0(klass, 0); + VALUE obj; + stat_alloc(rb_cStat, &obj); + return obj; } /* * call-seq: - * * File::Stat.new(file_name) -> stat * * Create a File::Stat object for the given file name (raising an @@ -5558,21 +5974,19 @@ rb_stat_s_alloc(VALUE klass) static VALUE rb_stat_init(VALUE obj, VALUE fname) { - struct stat st, *nst; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (STAT(StringValueCStr(fname), &st) == -1) { + if (STATX(StringValueCStr(fname), &st, STATX_ALL) == -1) { rb_sys_fail_path(fname); } - if (DATA_PTR(obj)) { - xfree(DATA_PTR(obj)); - DATA_PTR(obj) = NULL; - } - nst = ALLOC(struct stat); - *nst = st; - DATA_PTR(obj) = nst; + struct rb_stat *rb_st; + TypedData_Get_Struct(obj, struct rb_stat, &stat_data_type, rb_st); + + rb_st->stat = st; + rb_st->initialized = true; return Qnil; } @@ -5581,19 +5995,15 @@ rb_stat_init(VALUE obj, VALUE fname) static VALUE rb_stat_init_copy(VALUE copy, VALUE orig) { - struct stat *nst; - if (!OBJ_INIT_COPY(copy, orig)) return copy; - if (DATA_PTR(copy)) { - xfree(DATA_PTR(copy)); - DATA_PTR(copy) = 0; - } - if (DATA_PTR(orig)) { - nst = ALLOC(struct stat); - *nst = *(struct stat*)DATA_PTR(orig); - DATA_PTR(copy) = nst; - } + struct rb_stat *orig_rb_st; + TypedData_Get_Struct(orig, struct rb_stat, &stat_data_type, orig_rb_st); + + struct rb_stat *copy_rb_st; + TypedData_Get_Struct(copy, struct rb_stat, &stat_data_type, copy_rb_st); + + *copy_rb_st = *orig_rb_st; return copy; } @@ -5614,7 +6024,7 @@ rb_stat_init_copy(VALUE copy, VALUE orig) static VALUE rb_stat_ftype(VALUE obj) { - return rb_file_ftype(get_stat(obj)); + return rb_file_ftype(get_stat(obj)->ST_(mode)); } /* @@ -5631,7 +6041,7 @@ rb_stat_ftype(VALUE obj) static VALUE rb_stat_d(VALUE obj) { - if (S_ISDIR(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISDIR(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -5647,7 +6057,7 @@ static VALUE rb_stat_p(VALUE obj) { #ifdef S_IFIFO - if (S_ISFIFO(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISFIFO(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5673,7 +6083,7 @@ static VALUE rb_stat_l(VALUE obj) { #ifdef S_ISLNK - if (S_ISLNK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISLNK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; } @@ -5694,7 +6104,7 @@ static VALUE rb_stat_S(VALUE obj) { #ifdef S_ISSOCK - if (S_ISSOCK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISSOCK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5717,7 +6127,7 @@ static VALUE rb_stat_b(VALUE obj) { #ifdef S_ISBLK - if (S_ISBLK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISBLK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5738,7 +6148,7 @@ rb_stat_b(VALUE obj) static VALUE rb_stat_c(VALUE obj) { - if (S_ISCHR(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISCHR(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -5758,14 +6168,14 @@ rb_stat_c(VALUE obj) static VALUE rb_stat_owned(VALUE obj) { - if (get_stat(obj)->st_uid == geteuid()) return Qtrue; + if (get_stat(obj)->ST_(uid) == geteuid()) return Qtrue; return Qfalse; } static VALUE rb_stat_rowned(VALUE obj) { - if (get_stat(obj)->st_uid == getuid()) return Qtrue; + if (get_stat(obj)->ST_(uid) == getuid()) return Qtrue; return Qfalse; } @@ -5785,7 +6195,7 @@ static VALUE rb_stat_grpowned(VALUE obj) { #ifndef _WIN32 - if (rb_group_member(get_stat(obj)->st_gid)) return Qtrue; + if (rb_group_member(get_stat(obj)->ST_(gid))) return Qtrue; #endif return Qfalse; } @@ -5804,21 +6214,21 @@ rb_stat_grpowned(VALUE obj) static VALUE rb_stat_r(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IRUSR); + return RBOOL(st->ST_(mode) & S_IRUSR); #endif #ifdef S_IRGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IRGRP); + return RBOOL(st->ST_(mode) & S_IRGRP); #endif #ifdef S_IROTH - if (!(st->st_mode & S_IROTH)) return Qfalse; + if (!(st->ST_(mode) & S_IROTH)) return Qfalse; #endif return Qtrue; } @@ -5837,21 +6247,21 @@ rb_stat_r(VALUE obj) static VALUE rb_stat_R(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IRUSR); + return RBOOL(st->ST_(mode) & S_IRUSR); #endif #ifdef S_IRGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IRGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IRGRP); #endif #ifdef S_IROTH - if (!(st->st_mode & S_IROTH)) return Qfalse; + if (!(st->ST_(mode) & S_IROTH)) return Qfalse; #endif return Qtrue; } @@ -5873,14 +6283,12 @@ static VALUE rb_stat_wr(VALUE obj) { #ifdef S_IROTH - struct stat *st = get_stat(obj); - if ((st->st_mode & (S_IROTH)) == S_IROTH) { - return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); - } - else { - return Qnil; + rb_io_stat_data *st = get_stat(obj); + if ((st->ST_(mode) & (S_IROTH)) == S_IROTH) { + return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif + return Qnil; } /* @@ -5897,21 +6305,21 @@ rb_stat_wr(VALUE obj) static VALUE rb_stat_w(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IWUSR); + return RBOOL(st->ST_(mode) & S_IWUSR); #endif #ifdef S_IWGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IWGRP); + return RBOOL(st->ST_(mode) & S_IWGRP); #endif #ifdef S_IWOTH - if (!(st->st_mode & S_IWOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IWOTH)) return Qfalse; #endif return Qtrue; } @@ -5930,21 +6338,21 @@ rb_stat_w(VALUE obj) static VALUE rb_stat_W(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IWUSR); + return RBOOL(st->ST_(mode) & S_IWUSR); #endif #ifdef S_IWGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IWGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IWGRP); #endif #ifdef S_IWOTH - if (!(st->st_mode & S_IWOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IWOTH)) return Qfalse; #endif return Qtrue; } @@ -5965,15 +6373,13 @@ rb_stat_W(VALUE obj) static VALUE rb_stat_ww(VALUE obj) { -#ifdef S_IROTH - struct stat *st = get_stat(obj); - if ((st->st_mode & (S_IWOTH)) == S_IWOTH) { - return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); - } - else { - return Qnil; +#ifdef S_IWOTH + rb_io_stat_data *st = get_stat(obj); + if ((st->ST_(mode) & (S_IWOTH)) == S_IWOTH) { + return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif + return Qnil; } /* @@ -5992,23 +6398,23 @@ rb_stat_ww(VALUE obj) static VALUE rb_stat_x(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) { - return RBOOL(st->st_mode & S_IXUGO); + return RBOOL(st->ST_(mode) & S_IXUGO); } #endif #ifdef S_IXUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IXUSR); + return RBOOL(st->ST_(mode) & S_IXUSR); #endif #ifdef S_IXGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IXGRP); + return RBOOL(st->ST_(mode) & S_IXGRP); #endif #ifdef S_IXOTH - if (!(st->st_mode & S_IXOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IXOTH)) return Qfalse; #endif return Qtrue; } @@ -6024,23 +6430,23 @@ rb_stat_x(VALUE obj) static VALUE rb_stat_X(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) { - return RBOOL(st->st_mode & S_IXUGO); + return RBOOL(st->ST_(mode) & S_IXUGO); } #endif #ifdef S_IXUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IXUSR); + return RBOOL(st->ST_(mode) & S_IXUSR); #endif #ifdef S_IXGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IXGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IXGRP); #endif #ifdef S_IXOTH - if (!(st->st_mode & S_IXOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IXOTH)) return Qfalse; #endif return Qtrue; } @@ -6059,7 +6465,7 @@ rb_stat_X(VALUE obj) static VALUE rb_stat_f(VALUE obj) { - if (S_ISREG(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISREG(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -6077,7 +6483,7 @@ rb_stat_f(VALUE obj) static VALUE rb_stat_z(VALUE obj) { - if (get_stat(obj)->st_size == 0) return Qtrue; + if (get_stat(obj)->ST_(size) == 0) return Qtrue; return Qfalse; } @@ -6089,14 +6495,14 @@ rb_stat_z(VALUE obj) * the file otherwise. * * File.stat("testfile").size? #=> 66 - * File.stat("/dev/null").size? #=> nil + * File.stat(File::NULL).size? #=> nil * */ static VALUE rb_stat_s(VALUE obj) { - rb_off_t size = get_stat(obj)->st_size; + rb_off_t size = get_stat(obj)->ST_(size); if (size == 0) return Qnil; return OFFT2NUM(size); @@ -6117,7 +6523,7 @@ static VALUE rb_stat_suid(VALUE obj) { #ifdef S_ISUID - if (get_stat(obj)->st_mode & S_ISUID) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISUID) return Qtrue; #endif return Qfalse; } @@ -6138,7 +6544,7 @@ static VALUE rb_stat_sgid(VALUE obj) { #ifdef S_ISGID - if (get_stat(obj)->st_mode & S_ISGID) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISGID) return Qtrue; #endif return Qfalse; } @@ -6159,7 +6565,7 @@ static VALUE rb_stat_sticky(VALUE obj) { #ifdef S_ISVTX - if (get_stat(obj)->st_mode & S_ISVTX) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISVTX) return Qtrue; #endif return Qfalse; } @@ -6208,7 +6614,7 @@ rb_file_s_mkfifo(int argc, VALUE *argv, VALUE _) FilePathValue(path); path = rb_str_encode_ospath(path); ma.path = RSTRING_PTR(path); - if (rb_thread_call_without_gvl(nogvl_mkfifo, &ma, RUBY_UBF_IO, 0)) { + if (IO_WITHOUT_GVL(nogvl_mkfifo, &ma)) { rb_sys_fail_path(path); } return INT2FIX(0); @@ -6240,95 +6646,6 @@ rb_is_absolute_path(const char *path) return 0; } -#ifndef ENABLE_PATH_CHECK -# if defined DOSISH || defined __CYGWIN__ -# define ENABLE_PATH_CHECK 0 -# else -# define ENABLE_PATH_CHECK 1 -# endif -#endif - -#if ENABLE_PATH_CHECK -static int -path_check_0(VALUE path) -{ - struct stat st; - const char *p0 = StringValueCStr(path); - const char *e0; - rb_encoding *enc; - char *p = 0, *s; - - if (!rb_is_absolute_path(p0)) { - char *buf = ruby_getcwd(); - VALUE newpath; - - newpath = rb_str_new2(buf); - xfree(buf); - - rb_str_cat2(newpath, "/"); - rb_str_cat2(newpath, p0); - path = newpath; - p0 = RSTRING_PTR(path); - } - e0 = p0 + RSTRING_LEN(path); - enc = rb_enc_get(path); - for (;;) { -#ifndef S_IWOTH -# define S_IWOTH 002 -#endif - if (STAT(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) -#ifdef S_ISVTX - && !(p && (st.st_mode & S_ISVTX)) -#endif - && !access(p0, W_OK)) { - rb_enc_warn(enc, "Insecure world writable dir %s in PATH, mode 0%" -#if SIZEOF_DEV_T > SIZEOF_INT - PRI_MODET_PREFIX"o", -#else - "o", -#endif - p0, st.st_mode); - if (p) *p = '/'; - RB_GC_GUARD(path); - return 0; - } - s = strrdirsep(p0, e0, enc); - if (p) *p = '/'; - if (!s || s == p0) return 1; - p = s; - e0 = p; - *p = '\0'; - } -} -#endif - -int -rb_path_check(const char *path) -{ -#if ENABLE_PATH_CHECK - const char *p0, *p, *pend; - const char sep = PATH_SEP_CHAR; - - if (!path) return 1; - - pend = path + strlen(path); - p0 = path; - p = strchr(path, sep); - if (!p) p = pend; - - for (;;) { - if (!path_check_0(rb_str_new(p0, p - p0))) { - return 0; /* not safe */ - } - p0 = p + 1; - if (p0 > pend) break; - p = strchr(p0, sep); - if (!p) p = pend; - } -#endif - return 1; -} - int ruby_is_fd_loadable(int fd) { @@ -6372,7 +6689,11 @@ rb_file_load_ok(const char *path) #endif 0); int fd = rb_cloexec_open(path, mode, 0); - if (fd == -1) return 0; + if (fd < 0) { + if (!rb_gc_for_fd(errno)) return 0; + fd = rb_cloexec_open(path, mode, 0); + if (fd < 0) return 0; + } rb_update_max_fd(fd); ret = ruby_is_fd_loadable(fd); (void)close(fd); @@ -6527,10 +6848,10 @@ const char ruby_null_device[] = /* * A \File object is a representation of a file in the underlying platform. * - * \Class \File extends module FileTest, supporting such singleton methods + * Class \File extends module FileTest, supporting such singleton methods * as <tt>File.exist?</tt>. * - * === About the Examples + * == About the Examples * * Many examples here use these variables: * @@ -6538,11 +6859,11 @@ const char ruby_null_device[] = * * == Access Modes * - * \Methods File.new and File.open each create a \File object for a given file path. + * Methods File.new and File.open each create a \File object for a given file path. * * === \String Access Modes * - * \Methods File.new and File.open each may take string argument +mode+, which: + * Methods File.new and File.open each may take string argument +mode+, which: * * - Begins with a 1- or 2-character * {read/write mode}[rdoc-ref:File@Read-2FWrite+Mode]. @@ -6611,7 +6932,7 @@ const char ruby_null_device[] = * * - <tt>'r'</tt>: * - * - File is not initially truncated: + * - \File is not initially truncated: * * f = File.new('t.txt') # => #<File:t.txt> * f.size == 0 # => false @@ -6620,7 +6941,7 @@ const char ruby_null_device[] = * * f.pos # => 0 * - * - File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: + * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: * * f.readline # => "First line\n" * f.readline # => "Second line\n" @@ -6640,7 +6961,7 @@ const char ruby_null_device[] = * * - <tt>'w'</tt>: * - * - File is initially truncated: + * - \File is initially truncated: * * path = 't.tmp' * File.write(path, text) @@ -6651,7 +6972,7 @@ const char ruby_null_device[] = * * f.pos # => 0 * - * - File may be written anywhere (even past end-of-file); + * - \File may be written anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.write('foo') @@ -6694,7 +7015,7 @@ const char ruby_null_device[] = * * - <tt>'a'</tt>: * - * - File is not initially truncated: + * - \File is not initially truncated: * * path = 't.tmp' * File.write(path, 'foo') @@ -6705,7 +7026,7 @@ const char ruby_null_device[] = * * f.pos # => 0 * - * - File may be written only at end-of-file; + * - \File may be written only at end-of-file; * IO#rewind, IO#pos=, IO#seek do not affect writing: * * f.write('bar') @@ -6726,7 +7047,7 @@ const char ruby_null_device[] = * * - <tt>'r+'</tt>: * - * - File is not initially truncated: + * - \File is not initially truncated: * * path = 't.tmp' * File.write(path, text) @@ -6737,7 +7058,7 @@ const char ruby_null_device[] = * * f.pos # => 0 * - * - File may be read or written anywhere (even past end-of-file); + * - \File may be read or written anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.readline # => "First line\n" @@ -6782,7 +7103,7 @@ const char ruby_null_device[] = * * - <tt>'a+'</tt>: * - * - File is not initially truncated: + * - \File is not initially truncated: * * path = 't.tmp' * File.write(path, 'foo') @@ -6793,7 +7114,7 @@ const char ruby_null_device[] = * * f.pos # => 0 * - * - File may be written only at end-of-file; + * - \File may be written only at end-of-file; * IO#rewind, IO#pos=, IO#seek do not affect writing: * * f.write('bar') @@ -6808,7 +7129,7 @@ const char ruby_null_device[] = * f.flush * File.read(path) # => "foobarbazbat" * - * - File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: + * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: * * f.rewind * f.read # => "foobarbazbat" @@ -6833,7 +7154,7 @@ const char ruby_null_device[] = * f = File.new(path, 'w') * f.pos # => 0 * - * - File may be written anywhere (even past end-of-file); + * - \File may be written anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.write('foo') @@ -6910,7 +7231,7 @@ const char ruby_null_device[] = * f = File.new(path, 'w+') * f.pos # => 0 * - * - File may be written anywhere (even past end-of-file); + * - \File may be written anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.write('foo') @@ -6947,7 +7268,7 @@ const char ruby_null_device[] = * File.read(path) # => "bazbam\u0000\u0000bah" * f.pos # => 11 * - * - File may be read anywhere (even past end-of-file); + * - \File may be read anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.rewind @@ -6988,7 +7309,7 @@ const char ruby_null_device[] = * f.flush * File.read(path) # => "foobarbaz" * - * - File may be read anywhere (even past end-of-file); + * - \File may be read anywhere (even past end-of-file); * see IO#rewind, IO#pos=, IO#seek: * * f.rewind @@ -7133,7 +7454,6 @@ const char ruby_null_device[] = * * Note that file permissions are quite different from the _mode_ * of a file stream (\File object). - * See IO@Modes. * * In a \File object, the permissions are available thus, * where method +mode+, despite its name, returns permissions: @@ -7179,17 +7499,17 @@ const char ruby_null_device[] = * * == \File \Constants * - * Various constants for use in \File and \IO methods + * Various constants for use in \File and IO methods * may be found in module File::Constants; * an array of their names is returned by <tt>File::Constants.constants</tt>. * * == What's Here * - * First, what's elsewhere. \Class \File: + * First, what's elsewhere. Class \File: * * - Inherits from {class IO}[rdoc-ref:IO@What-27s+Here], * in particular, methods for creating, reading, and writing files - * - Includes {module FileTest}[rdoc-ref:FileTest@What-27s+Here]. + * - Includes module FileTest, * which provides dozens of additional methods. * * Here, class \File provides methods that are useful for: @@ -7234,15 +7554,15 @@ const char ruby_null_device[] = * * _Times_ * - * - ::atime: Returns a \Time for the most recent access to the given file. - * - ::birthtime: Returns a \Time for the creation of the given file. - * - ::ctime: Returns a \Time for the metadata change of the given file. - * - ::mtime: Returns a \Time for the most recent data modification to + * - ::atime: Returns a Time for the most recent access to the given file. + * - ::birthtime: Returns a Time for the creation of the given file. + * - ::ctime: Returns a Time for the metadata change of the given file. + * - ::mtime: Returns a Time for the most recent data modification to * the content of the given file. - * - #atime: Returns a \Time for the most recent access to +self+. - * - #birthtime: Returns a \Time the creation for +self+. - * - #ctime: Returns a \Time for the metadata change of +self+. - * - #mtime: Returns a \Time for the most recent data modification + * - #atime: Returns a Time for the most recent access to +self+. + * - #birthtime: Returns a Time the creation for +self+. + * - #ctime: Returns a Time for the metadata change of +self+. + * - #mtime: Returns a Time for the most recent data modification * to the content of +self+. * * _Types_ @@ -7402,7 +7722,7 @@ Init_File(void) /* separates directory parts in path */ rb_define_const(rb_cFile, "SEPARATOR", separator); rb_define_singleton_method(rb_cFile, "split", rb_file_s_split, 1); - rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -2); + rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -1); #ifdef DOSISH /* platform specific alternative separator */ @@ -7431,93 +7751,400 @@ Init_File(void) /* * Document-module: File::Constants * - * File::Constants provides file-related constants. All possible - * file constants are listed in the documentation but they may not all - * be present on your platform. + * Module +File::Constants+ defines file-related constants. + * + * There are two families of constants here: + * + * - Those having to do with {file access}[rdoc-ref:File::Constants@File+Access]. + * - Those having to do with {filename globbing}[rdoc-ref:File::Constants@Filename+Globbing+Constants+-28File-3A-3AFNM_-2A-29]. + * + * \File constants defined for the local process may be retrieved + * with method File::Constants.constants: + * + * File::Constants.constants.take(5) + * # => [:RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT] + * + * == \File Access + * + * \File-access constants may be used with optional argument +mode+ in calls + * to the following methods: + * + * - File.new. + * - File.open. + * - IO.for_fd. + * - IO.new. + * - IO.open. + * - IO.popen. + * - IO.reopen. + * - IO.sysopen. + * - StringIO.new. + * - StringIO.open. + * - StringIO#reopen. + * + * === Read/Write Access + * + * Read-write access for a stream + * may be specified by a file-access constant. + * + * The constant may be specified as part of a bitwise OR of other such constants. + * + * Any combination of the constants in this section may be specified. + * + * ==== File::RDONLY + * + * Flag File::RDONLY specifies the stream should be opened for reading only: + * + * filepath = '/tmp/t.tmp' + * f = File.new(filepath, File::RDONLY) + * f.write('Foo') # Raises IOError (not opened for writing). + * + * ==== File::WRONLY + * + * Flag File::WRONLY specifies that the stream should be opened for writing only: + * + * f = File.new(filepath, File::WRONLY) + * f.read # Raises IOError (not opened for reading). + * + * ==== File::RDWR + * + * Flag File::RDWR specifies that the stream should be opened + * for both reading and writing: + * + * f = File.new(filepath, File::RDWR) + * f.write('Foo') # => 3 + * f.rewind # => 0 + * f.read # => "Foo" + * + * === \File Positioning + * + * ==== File::APPEND + * + * Flag File::APPEND specifies that the stream should be opened + * in append mode. + * + * Before each write operation, the position is set to end-of-stream. + * The modification of the position and the following write operation + * are performed as a single atomic step. + * + * ==== File::TRUNC + * + * Flag File::TRUNC specifies that the stream should be truncated + * at its beginning. + * If the file exists and is successfully opened for writing, + * it is to be truncated to position zero; + * its ctime and mtime are updated. + * + * There is no effect on a FIFO special file or a terminal device. + * The effect on other file types is implementation-defined. + * The result of using File::TRUNC with File::RDONLY is undefined. + * + * === Creating and Preserving + * + * ==== File::CREAT + * + * Flag File::CREAT specifies that the stream should be created + * if it does not already exist. + * + * If the file exists: + * + * - Raise an exception if File::EXCL is also specified. + * - Otherwise, do nothing. + * + * If the file does not exist, then it is created. + * Upon successful completion, the atime, ctime, and mtime of the file are updated, + * and the ctime and mtime of the parent directory are updated. + * + * ==== File::EXCL + * + * Flag File::EXCL specifies that the stream should not already exist; + * If flags File::CREAT and File::EXCL are both specified + * and the stream already exists, an exception is raised. + * + * The check for the existence and creation of the file is performed as an + * atomic operation. + * + * If both File::EXCL and File::CREAT are specified and the path names a symbolic link, + * an exception is raised regardless of the contents of the symbolic link. + * + * If File::EXCL is specified and File::CREAT is not specified, + * the result is undefined. * - * If the underlying platform doesn't define a constant the corresponding - * Ruby constant is not defined. + * === POSIX \File \Constants + * + * Some file-access constants are defined only on POSIX-compliant systems; + * those are: + * + * - File::SYNC. + * - File::DSYNC. + * - File::RSYNC. + * - File::DIRECT. + * - File::NOATIME. + * - File::NOCTTY. + * - File::NOFOLLOW. + * - File::TMPFILE. + * + * ==== File::SYNC, File::RSYNC, and File::DSYNC + * + * Flag File::SYNC, File::RSYNC, or File::DSYNC + * specifies synchronization of I/O operations with the underlying file system. + * + * These flags are valid only for POSIX-compliant systems. + * + * - File::SYNC specifies that all write operations (both data and metadata) + * are immediately to be flushed to the underlying storage device. + * This means that the data is written to the storage device, + * and the file's metadata (e.g., file size, timestamps, permissions) + * are also synchronized. + * This guarantees that data is safely stored on the storage medium + * before returning control to the calling program. + * This flag can have a significant impact on performance + * since it requires synchronous writes, which can be slower + * compared to asynchronous writes. + * + * - File::RSYNC specifies that any read operations on the file will not return + * until all outstanding write operations + * (those that have been issued but not completed) are also synchronized. + * This is useful when you want to read the most up-to-date data, + * which may still be in the process of being written. + * + * - File::DSYNC specifies that all _data_ write operations + * are immediately to be flushed to the underlying storage device; + * this differs from File::SYNC, which requires that _metadata_ + * also be synchronized. + * + * Note that the behavior of these flags may vary slightly + * depending on the operating system and filesystem being used. + * Additionally, using these flags can have an impact on performance + * due to the synchronous nature of the I/O operations, + * so they should be used judiciously, + * especially in performance-critical applications. + * + * ==== File::NOCTTY + * + * Flag File::NOCTTY specifies that if the stream is a terminal device, + * that device does not become the controlling terminal for the process. + * + * Defined only for POSIX-compliant systems. + * + * ==== File::DIRECT + * + * Flag File::DIRECT requests that cache effects of the I/O to and from the stream + * be minimized. + * + * Defined only for POSIX-compliant systems. + * + * ==== File::NOATIME + * + * Flag File::NOATIME specifies that act of opening the stream + * should not modify its access time (atime). + * + * Defined only for POSIX-compliant systems. + * + * ==== File::NOFOLLOW + * + * Flag File::NOFOLLOW specifies that if path is a symbolic link, + * it should not be followed. + * + * Defined only for POSIX-compliant systems. + * + * ==== File::TMPFILE + * + * Flag File::TMPFILE specifies that the opened stream + * should be a new temporary file. + * + * Defined only for POSIX-compliant systems. + * + * === Other File-Access \Constants + * + * ==== File::NONBLOCK + * + * When possible, the file is opened in nonblocking mode. + * Neither the open operation nor any subsequent I/O operations on + * the file will cause the calling process to wait. + * + * ==== File::BINARY + * + * Flag File::BINARY specifies that the stream is to be accessed in binary mode. + * + * ==== File::SHARE_DELETE + * + * Flag File::SHARE_DELETE enables other processes to open the stream + * with delete access. + * + * Windows only. + * + * If the stream is opened for (local) delete access without File::SHARE_DELETE, + * and another process attempts to open it with delete access, + * the attempt fails and the stream is not opened for that process. + * + * == Locking + * + * Four file constants relate to stream locking; + * see File#flock: + * + * ==== File::LOCK_EX + * + * Flag File::LOCK_EX specifies an exclusive lock; + * only one process a a time may lock the stream. + * + * ==== File::LOCK_NB + * + * Flag File::LOCK_NB specifies non-blocking locking for the stream; + * may be combined with File::LOCK_EX or File::LOCK_SH. + * + * ==== File::LOCK_SH + * + * Flag File::LOCK_SH specifies that multiple processes may lock + * the stream at the same time. + * + * ==== File::LOCK_UN + * + * Flag File::LOCK_UN specifies that the stream is not to be locked. + * + * == Filename Globbing \Constants (File::FNM_*) + * + * Filename-globbing constants may be used with optional argument +flags+ + * in calls to the following methods: + * + * - Dir.glob. + * - File.fnmatch. + * - Pathname#fnmatch. + * - Pathname.glob. + * - Pathname#glob. + * + * The constants are: + * + * ==== File::FNM_CASEFOLD + * + * Flag File::FNM_CASEFOLD makes patterns case insensitive + * for File.fnmatch (but not Dir.glob). + * + * ==== File::FNM_DOTMATCH + * + * Flag File::FNM_DOTMATCH makes the <tt>'*'</tt> pattern + * match a filename starting with <tt>'.'</tt>. + * + * ==== File::FNM_EXTGLOB + * + * Flag File::FNM_EXTGLOB enables pattern <tt>'{a,b}'</tt>, + * which matches pattern '_a_' and pattern '_b_'; + * behaves like + * a {regexp union}[rdoc-ref:Regexp.union] + * (e.g., <tt>'(?:a|b)'</tt>): + * + * pattern = '{LEGAL,BSDL}' + * Dir.glob(pattern) # => ["LEGAL", "BSDL"] + * Pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>] + * pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>] + * + * ==== File::FNM_NOESCAPE + * + * Flag File::FNM_NOESCAPE disables <tt>'\'</tt> escaping. + * + * ==== File::FNM_PATHNAME + * + * Flag File::FNM_PATHNAME specifies that patterns <tt>'*'</tt> and <tt>'?'</tt> + * do not match the directory separator + * (the value of constant File::SEPARATOR). + * + * ==== File::FNM_SHORTNAME + * + * Flag File::FNM_SHORTNAME allows patterns to match short names if they exist. + * + * Windows only. + * + * ==== File::FNM_SYSCASE + * + * Flag File::FNM_SYSCASE specifies that case sensitivity + * is the same as in the underlying operating system; + * effective for File.fnmatch, but not Dir.glob. + * + * == Other \Constants + * + * ==== File::NULL + * + * Flag File::NULL contains the string value of the null device: + * + * - On a Unix-like OS, <tt>'/dev/null'</tt>. + * - On Windows, <tt>'NUL'</tt>. * - * Your platform documentations (e.g. man open(2)) may describe more - * detailed information. */ rb_mFConst = rb_define_module_under(rb_cFile, "Constants"); rb_include_module(rb_cIO, rb_mFConst); - - /* open for reading only */ + /* {File::RDONLY}[rdoc-ref:File::Constants@File-3A-3ARDONLY] */ rb_define_const(rb_mFConst, "RDONLY", INT2FIX(O_RDONLY)); - /* open for writing only */ + /* {File::WRONLY}[rdoc-ref:File::Constants@File-3A-3AWRONLY] */ rb_define_const(rb_mFConst, "WRONLY", INT2FIX(O_WRONLY)); - /* open for reading and writing */ + /* {File::RDWR}[rdoc-ref:File::Constants@File-3A-3ARDWR] */ rb_define_const(rb_mFConst, "RDWR", INT2FIX(O_RDWR)); - /* append on each write */ + /* {File::APPEND}[rdoc-ref:File::Constants@File-3A-3AAPPEND] */ rb_define_const(rb_mFConst, "APPEND", INT2FIX(O_APPEND)); - /* create file if it does not exist */ + /* {File::CREAT}[rdoc-ref:File::Constants@File-3A-3ACREAT] */ rb_define_const(rb_mFConst, "CREAT", INT2FIX(O_CREAT)); - /* error if CREAT and the file exists */ + /* {File::EXCL}[rdoc-ref:File::Constants@File-3A-3AEXCL] */ rb_define_const(rb_mFConst, "EXCL", INT2FIX(O_EXCL)); #if defined(O_NDELAY) || defined(O_NONBLOCK) # ifndef O_NONBLOCK # define O_NONBLOCK O_NDELAY # endif - /* do not block on open or for data to become available */ + /* {File::NONBLOCK}[rdoc-ref:File::Constants@File-3A-3ANONBLOCK] */ rb_define_const(rb_mFConst, "NONBLOCK", INT2FIX(O_NONBLOCK)); #endif - /* truncate size to 0 */ + /* {File::TRUNC}[rdoc-ref:File::Constants@File-3A-3ATRUNC] */ rb_define_const(rb_mFConst, "TRUNC", INT2FIX(O_TRUNC)); #ifdef O_NOCTTY - /* not to make opened IO the controlling terminal device */ + /* {File::NOCTTY}[rdoc-ref:File::Constants@File-3A-3ANOCTTY] */ rb_define_const(rb_mFConst, "NOCTTY", INT2FIX(O_NOCTTY)); #endif #ifndef O_BINARY # define O_BINARY 0 #endif - /* disable line code conversion */ + /* {File::BINARY}[rdoc-ref:File::Constants@File-3A-3ABINARY] */ rb_define_const(rb_mFConst, "BINARY", INT2FIX(O_BINARY)); #ifndef O_SHARE_DELETE # define O_SHARE_DELETE 0 #endif - /* can delete opened file */ + /* {File::SHARE_DELETE}[rdoc-ref:File::Constants@File-3A-3ASHARE_DELETE] */ rb_define_const(rb_mFConst, "SHARE_DELETE", INT2FIX(O_SHARE_DELETE)); #ifdef O_SYNC - /* any write operation perform synchronously */ + /* {File::SYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */ rb_define_const(rb_mFConst, "SYNC", INT2FIX(O_SYNC)); #endif #ifdef O_DSYNC - /* any write operation perform synchronously except some meta data */ + /* {File::DSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */ rb_define_const(rb_mFConst, "DSYNC", INT2FIX(O_DSYNC)); #endif #ifdef O_RSYNC - /* any read operation perform synchronously. used with SYNC or DSYNC. */ + /* {File::RSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */ rb_define_const(rb_mFConst, "RSYNC", INT2FIX(O_RSYNC)); #endif #ifdef O_NOFOLLOW - /* do not follow symlinks */ + /* {File::NOFOLLOW}[rdoc-ref:File::Constants@File-3A-3ANOFOLLOW] */ rb_define_const(rb_mFConst, "NOFOLLOW", INT2FIX(O_NOFOLLOW)); /* FreeBSD, Linux */ #endif #ifdef O_NOATIME - /* do not change atime */ + /* {File::NOATIME}[rdoc-ref:File::Constants@File-3A-3ANOATIME] */ rb_define_const(rb_mFConst, "NOATIME", INT2FIX(O_NOATIME)); /* Linux */ #endif #ifdef O_DIRECT - /* Try to minimize cache effects of the I/O to and from this file. */ + /* {File::DIRECT}[rdoc-ref:File::Constants@File-3A-3ADIRECT] */ rb_define_const(rb_mFConst, "DIRECT", INT2FIX(O_DIRECT)); #endif #ifdef O_TMPFILE - /* Create an unnamed temporary file */ + /* {File::TMPFILE}[rdoc-ref:File::Constants@File-3A-3ATMPFILE] */ rb_define_const(rb_mFConst, "TMPFILE", INT2FIX(O_TMPFILE)); #endif - /* shared lock. see File#flock */ + /* {File::LOCK_SH}[rdoc-ref:File::Constants@File-3A-3ALOCK_SH] */ rb_define_const(rb_mFConst, "LOCK_SH", INT2FIX(LOCK_SH)); - /* exclusive lock. see File#flock */ + /* {File::LOCK_EX}[rdoc-ref:File::Constants@File-3A-3ALOCK_EX] */ rb_define_const(rb_mFConst, "LOCK_EX", INT2FIX(LOCK_EX)); - /* unlock. see File#flock */ + /* {File::LOCK_UN}[rdoc-ref:File::Constants@File-3A-3ALOCK_UN] */ rb_define_const(rb_mFConst, "LOCK_UN", INT2FIX(LOCK_UN)); - /* non-blocking lock. used with LOCK_SH or LOCK_EX. see File#flock */ + /* {File::LOCK_NB}[rdoc-ref:File::Constants@File-3A-3ALOCK_NB] */ rb_define_const(rb_mFConst, "LOCK_NB", INT2FIX(LOCK_NB)); - /* Name of the null device */ + /* {File::NULL}[rdoc-ref:File::Constants@File-3A-3ANULL] */ rb_define_const(rb_mFConst, "NULL", rb_fstring_cstr(ruby_null_device)); rb_define_global_function("test", rb_f_test, -1); |
