diff options
Diffstat (limited to 'file.c')
-rw-r--r-- | file.c | 4289 |
1 files changed, 2679 insertions, 1610 deletions
@@ -115,6 +115,8 @@ int flock(int, int); # define link(f, t) rb_w32_ulink((f), (t)) # undef unlink # define unlink(p) rb_w32_uunlink(p) +# undef readlink +# define readlink(f, t, l) rb_w32_ureadlink((f), (t), (l)) # undef rename # define rename(f, t) rb_w32_urename((f), (t)) # undef symlink @@ -127,7 +129,7 @@ int flock(int, int); # endif #else # define STAT(p, s) stat((p), (s)) -#endif +#endif /* _WIN32 */ #if defined _WIN32 || defined __APPLE__ # define USE_OSPATH 1 @@ -167,7 +169,6 @@ 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" @@ -182,15 +183,15 @@ file_path_convert(VALUE name) int fname_encidx = ENCODING_GET(name); int fs_encidx; if (ENCINDEX_US_ASCII != fname_encidx && - ENCINDEX_ASCII != fname_encidx && - (fs_encidx = rb_filesystem_encindex()) != fname_encidx && - rb_default_internal_encoding() && - !rb_enc_str_asciionly_p(name)) { - /* Don't call rb_filesystem_encoding() before US-ASCII and ASCII-8BIT */ - /* fs_encoding should be ascii compatible */ - rb_encoding *fname_encoding = rb_enc_from_index(fname_encidx); - rb_encoding *fs_encoding = rb_enc_from_index(fs_encidx); - name = rb_str_conv_enc(name, fname_encoding, fs_encoding); + ENCINDEX_ASCII_8BIT != fname_encidx && + (fs_encidx = rb_filesystem_encindex()) != fname_encidx && + rb_default_internal_encoding() && + !rb_enc_str_asciionly_p(name)) { + /* Don't call rb_filesystem_encoding() before US-ASCII and ASCII-8BIT */ + /* fs_encoding should be ascii compatible */ + rb_encoding *fname_encoding = rb_enc_from_index(fname_encidx); + rb_encoding *fs_encoding = rb_enc_from_index(fs_encidx); + name = rb_str_conv_enc(name, fname_encoding, fs_encoding); } #endif return name; @@ -201,8 +202,8 @@ 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)); + rb_raise(rb_eEncCompatError, "path name must be ASCII-compatible (%s): %"PRIsVALUE, + rb_enc_name(enc), rb_str_inspect(str)); } return enc; } @@ -214,7 +215,7 @@ rb_get_path_check_to_string(VALUE obj) ID to_path; if (RB_TYPE_P(obj, T_STRING)) { - return obj; + return obj; } CONST_ID(to_path, "to_path"); tmp = rb_check_funcall_default(obj, to_path, 0, 0, obj); @@ -229,7 +230,7 @@ rb_get_path_check_convert(VALUE obj) check_path_encoding(obj); if (!rb_str_to_cstr(obj)) { - rb_raise(rb_eArgError, "path name contains null byte"); + rb_raise(rb_eArgError, "path name contains null byte"); } return rb_str_new4(obj); @@ -253,30 +254,70 @@ rb_str_encode_ospath(VALUE path) #if USE_OSPATH int encidx = ENCODING_GET(path); #if 0 && defined _WIN32 - if (encidx == ENCINDEX_ASCII) { - encidx = rb_filesystem_encindex(); + if (encidx == ENCINDEX_ASCII_8BIT) { + encidx = rb_filesystem_encindex(); } #endif - if (encidx != ENCINDEX_ASCII && encidx != ENCINDEX_UTF_8) { - rb_encoding *enc = rb_enc_from_index(encidx); - rb_encoding *utf8 = rb_utf8_encoding(); - path = rb_str_conv_enc(path, enc, utf8); + if (encidx != ENCINDEX_ASCII_8BIT && encidx != ENCINDEX_UTF_8) { + rb_encoding *enc = rb_enc_from_index(encidx); + rb_encoding *utf8 = rb_utf8_encoding(); + path = rb_str_conv_enc(path, enc, utf8); } -#endif +#endif /* USE_OSPATH */ return path; } #ifdef __APPLE__ # define NORMALIZE_UTF8PATH 1 + +# ifdef HAVE_WORKING_FORK +static void +rb_CFString_class_initialize_before_fork(void) +{ + /* + * Since macOS 13, CFString family API used in + * rb_str_append_normalized_ospath may internally use Objective-C classes + * (NSTaggedPointerString and NSPlaceholderMutableString) for small strings. + * + * On the other hand, Objective-C classes should not be used for the first + * time in a fork()'ed but not exec()'ed process. Violations for this rule + * can result deadlock during class initialization, so Objective-C runtime + * conservatively crashes on such cases by default. + * + * Therefore, we need to use CFString API to initialize Objective-C classes + * used internally *before* fork(). + * + * For future changes, please note that this initialization process cannot + * be done in ctor because NSTaggedPointerString in CoreFoundation is enabled + * after CFStringInitializeTaggedStrings(), which is called during loading + * Objective-C runtime after ctor. + * For more details, see https://bugs.ruby-lang.org/issues/18912 + */ + + /* 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); +} +# 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); + (const UInt8 *)ptr, len, + kCFStringEncodingUTF8, FALSE, + kCFAllocatorNull); CFMutableStringRef m = CFStringCreateMutableCopy(kCFAllocatorDefault, len, s); long oldlen = RSTRING_LEN(str); @@ -285,7 +326,7 @@ rb_str_append_normalized_ospath(VALUE str, const char *ptr, long len) CFStringGetBytes(m, all, kCFStringEncodingUTF8, '?', FALSE, NULL, 0, &buflen); rb_str_modify_expand(str, buflen); CFStringGetBytes(m, all, kCFStringEncodingUTF8, '?', FALSE, - (UInt8 *)(RSTRING_PTR(str) + oldlen), buflen, &buflen); + (UInt8 *)(RSTRING_PTR(str) + oldlen), buflen, &buflen); rb_str_set_len(str, oldlen + buflen); CFRelease(m); CFRelease(s); @@ -303,34 +344,34 @@ rb_str_normalize_ospath(const char *ptr, long len) rb_enc_associate(str, enc); while (p < e) { - int l, c; - 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"; - rb_str_append_normalized_ospath(str, p1, p-p1); - rb_str_cat(str, invalid, sizeof(invalid)); - p += 1; - p1 = p; - continue; - } - l = MBCLEN_CHARFOUND_LEN(r); - c = rb_enc_mbc_to_codepoint(p, e, enc); - if ((0x2000 <= c && c <= 0x2FFF) || (0xF900 <= c && c <= 0xFAFF) || - (0x2F800 <= c && c <= 0x2FAFF)) { - if (p - p1 > 0) { - rb_str_append_normalized_ospath(str, p1, p-p1); - } - rb_str_cat(str, p, l); - p += l; - p1 = p; - } - else { - p += l; - } + int l, c; + 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"; + rb_str_append_normalized_ospath(str, p1, p-p1); + rb_str_cat(str, invalid, sizeof(invalid)); + p += 1; + p1 = p; + continue; + } + l = MBCLEN_CHARFOUND_LEN(r); + c = rb_enc_mbc_to_codepoint(p, e, enc); + if ((0x2000 <= c && c <= 0x2FFF) || (0xF900 <= c && c <= 0xFAFF) || + (0x2F800 <= c && c <= 0x2FAFF)) { + if (p - p1 > 0) { + rb_str_append_normalized_ospath(str, p1, p-p1); + } + rb_str_cat(str, p, l); + p += l; + p1 = p; + } + else { + p += l; + } } if (p - p1 > 0) { - rb_str_append_normalized_ospath(str, p1, p-p1); + rb_str_append_normalized_ospath(str, p1, p-p1); } return str; @@ -343,33 +384,33 @@ ignored_char_p(const char *p, const char *e, rb_encoding *enc) if (p+3 > e) return 0; switch ((unsigned char)*p) { case 0xe2: - switch ((unsigned char)p[1]) { - case 0x80: - c = (unsigned char)p[2]; - /* c >= 0x200c && c <= 0x200f */ - if (c >= 0x8c && c <= 0x8f) return 3; - /* c >= 0x202a && c <= 0x202e */ - if (c >= 0xaa && c <= 0xae) return 3; - return 0; - case 0x81: - c = (unsigned char)p[2]; - /* c >= 0x206a && c <= 0x206f */ - if (c >= 0xaa && c <= 0xaf) return 3; - return 0; - } - break; + switch ((unsigned char)p[1]) { + case 0x80: + c = (unsigned char)p[2]; + /* c >= 0x200c && c <= 0x200f */ + if (c >= 0x8c && c <= 0x8f) return 3; + /* c >= 0x202a && c <= 0x202e */ + if (c >= 0xaa && c <= 0xae) return 3; + return 0; + case 0x81: + c = (unsigned char)p[2]; + /* c >= 0x206a && c <= 0x206f */ + if (c >= 0xaa && c <= 0xaf) return 3; + return 0; + } + break; case 0xef: - /* c == 0xfeff */ - if ((unsigned char)p[1] == 0xbb && - (unsigned char)p[2] == 0xbf) - return 3; - break; + /* c == 0xfeff */ + if ((unsigned char)p[1] == 0xbb && + (unsigned char)p[2] == 0xbf) + return 3; + break; } return 0; } -#else +#else /* !__APPLE__ */ # define NORMALIZE_UTF8PATH 0 -#endif +#endif /* __APPLE__ */ #define apply2args(n) (rb_check_arity(argc, n, UNLIMITED_ARGUMENTS), argc-=n) @@ -393,10 +434,10 @@ no_gvl_apply2files(void *ptr) struct apply_arg *aa = ptr; for (aa->i = 0; aa->i < aa->argc; aa->i++) { - if (aa->func(aa->fn[aa->i].ptr, aa->arg) < 0) { - aa->errnum = errno; - break; - } + if (aa->func(aa->fn[aa->i].ptr, aa->arg) < 0) { + aa->errnum = errno; + break; + } } return 0; } @@ -420,85 +461,51 @@ apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg) aa->func = func; for (aa->i = 0; aa->i < argc; aa->i++) { - VALUE path = rb_get_path(argv[aa->i]); + VALUE path = rb_get_path(argv[aa->i]); - path = rb_str_encode_ospath(path); - aa->fn[aa->i].ptr = RSTRING_PTR(path); - aa->fn[aa->i].path = path; + path = rb_str_encode_ospath(path); + aa->fn[aa->i].ptr = RSTRING_PTR(path); + 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) { - utime_failed(aa); - } + if (func == utime_internal) { + utime_failed(aa); + } #endif - rb_syserr_fail_path(aa->errnum, aa->fn[aa->i].path); + rb_syserr_fail_path(aa->errnum, aa->fn[aa->i].path); } if (v) { - ALLOCV_END(v); + ALLOCV_END(v); } return LONG2FIX(argc); } -/* - * call-seq: - * file.path -> filename - * file.to_path -> filename - * - * Returns the pathname used to create <i>file</i> as a string. Does - * not normalize the name. - * - * The pathname may not point to the file corresponding to <i>file</i>. - * For instance, the pathname becomes void when the file has been - * moved or deleted. - * - * This method raises IOError for a <i>file</i> created using - * File::Constants::TMPFILE because they don't have a pathname. - * - * File.new("testfile").path #=> "testfile" - * File.new("/tmp/../tmp/xxx", "w").path #=> "/tmp/../tmp/xxx" - * - */ - -static VALUE -rb_file_path(VALUE obj) -{ - rb_io_t *fptr; - - fptr = RFILE(rb_io_taint_check(obj))->fptr; - rb_io_check_initialized(fptr); - - if (NIL_P(fptr->pathv)) { - rb_raise(rb_eIOError, "File is unnamed (TMPFILE?)"); - } - - return rb_str_dup(fptr->pathv); -} - -static size_t -stat_memsize(const void *p) -{ - return sizeof(struct stat); -} - 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 +}; + +struct rb_stat { + struct stat stat; + bool initialized; }; static VALUE stat_new_0(VALUE klass, const struct stat *st) { - struct stat *nst = 0; - VALUE obj = TypedData_Wrap_Struct(klass, &stat_data_type, 0); - + struct rb_stat *rb_st; + VALUE obj = TypedData_Make_Struct(klass, struct rb_stat, &stat_data_type, rb_st); if (st) { - nst = ALLOC(struct stat); - *nst = *st; - RTYPEDDATA_DATA(obj) = nst; + rb_st->stat = *st; + rb_st->initialized = true; } return obj; } @@ -512,10 +519,10 @@ rb_stat_new(const struct stat *st) static struct stat* 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); @@ -957,7 +964,7 @@ typedef struct stat statx_data; # define stat_birthtime stat_ctime #else # undef HAVE_STAT_BIRTHTIME -#endif +#endif /* defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) */ /* * call-seq: @@ -978,7 +985,7 @@ rb_stat_atime(VALUE self) /* * call-seq: - * stat.mtime -> aTime + * stat.mtime -> time * * Returns the modification time of <i>stat</i>. * @@ -994,7 +1001,7 @@ rb_stat_mtime(VALUE self) /* * call-seq: - * stat.ctime -> aTime + * stat.ctime -> time * * Returns the change time for <i>stat</i> (that is, the time * directory information about the file was changed, not the file @@ -1015,7 +1022,7 @@ rb_stat_ctime(VALUE self) #if defined(HAVE_STAT_BIRTHTIME) /* * call-seq: - * stat.birthtime -> aTime + * stat.birthtime -> time * * Returns the birth time for <i>stat</i>. * @@ -1065,30 +1072,30 @@ rb_stat_inspect(VALUE self) VALUE str; size_t i; static const struct { - const char *name; - VALUE (*func)(VALUE); + const char *name; + VALUE (*func)(VALUE); } member[] = { - {"dev", rb_stat_dev}, - {"ino", rb_stat_ino}, - {"mode", rb_stat_mode}, - {"nlink", rb_stat_nlink}, - {"uid", rb_stat_uid}, - {"gid", rb_stat_gid}, - {"rdev", rb_stat_rdev}, - {"size", rb_stat_size}, - {"blksize", rb_stat_blksize}, - {"blocks", rb_stat_blocks}, - {"atime", rb_stat_atime}, - {"mtime", rb_stat_mtime}, - {"ctime", rb_stat_ctime}, + {"dev", rb_stat_dev}, + {"ino", rb_stat_ino}, + {"mode", rb_stat_mode}, + {"nlink", rb_stat_nlink}, + {"uid", rb_stat_uid}, + {"gid", rb_stat_gid}, + {"rdev", rb_stat_rdev}, + {"size", rb_stat_size}, + {"blksize", rb_stat_blksize}, + {"blocks", rb_stat_blocks}, + {"atime", rb_stat_atime}, + {"mtime", rb_stat_mtime}, + {"ctime", rb_stat_ctime}, #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) - {"birthtime", rb_stat_birthtime}, + {"birthtime", rb_stat_birthtime}, #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)); } @@ -1097,23 +1104,23 @@ rb_stat_inspect(VALUE self) rb_str_buf_cat2(str, " "); for (i = 0; i < sizeof(member)/sizeof(member[0]); i++) { - VALUE v; - - if (i > 0) { - rb_str_buf_cat2(str, ", "); - } - rb_str_buf_cat2(str, member[i].name); - rb_str_buf_cat2(str, "="); - v = (*member[i].func)(self); - if (i == 2) { /* mode */ - rb_str_catf(str, "0%lo", (unsigned long)NUM2ULONG(v)); - } - else if (i == 0 || i == 6) { /* dev/rdev */ - rb_str_catf(str, "0x%"PRI_DEVT_PREFIX"x", NUM2DEVT(v)); - } - else { - rb_str_append(str, rb_inspect(v)); - } + VALUE v; + + if (i > 0) { + rb_str_buf_cat2(str, ", "); + } + rb_str_buf_cat2(str, member[i].name); + rb_str_buf_cat2(str, "="); + v = (*member[i].func)(self); + if (i == 2) { /* mode */ + rb_str_catf(str, "0%lo", (unsigned long)NUM2ULONG(v)); + } + else if (i == 0 || i == 6) { /* dev/rdev */ + rb_str_catf(str, "0x%"PRI_DEVT_PREFIX"x", NUM2DEVT(v)); + } + else { + rb_str_append(str, rb_inspect(v)); + } } rb_str_buf_cat2(str, ">"); @@ -1123,8 +1130,8 @@ rb_stat_inspect(VALUE self) typedef struct no_gvl_stat_data { struct stat *st; union { - const char *path; - int fd; + const char *path; + int fd; } file; } no_gvl_stat_data; @@ -1161,8 +1168,7 @@ 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) && \ @@ -1182,8 +1188,8 @@ 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 { struct statx *stx; @@ -1212,8 +1218,7 @@ statx_without_gvl(const char *path, struct statx *stx, unsigned int mask) no_gvl_statx_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 @@ -1282,7 +1287,8 @@ typedef struct statx statx_data; # 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) */ static int rb_stat(VALUE file, struct stat *st) @@ -1292,16 +1298,16 @@ rb_stat(VALUE file, struct stat *st) tmp = rb_check_convert_type_with_id(file, T_FILE, "IO", idTo_io); if (!NIL_P(tmp)) { - rb_io_t *fptr; + rb_io_t *fptr; - GetOpenFile(tmp, fptr); - result = fstat_without_gvl(fptr->fd, st); - file = tmp; + GetOpenFile(tmp, fptr); + result = fstat_without_gvl(fptr->fd, st); + file = tmp; } else { - FilePathValue(file); - file = rb_str_encode_ospath(file); - result = stat_without_gvl(RSTRING_PTR(file), st); + FilePathValue(file); + file = rb_str_encode_ospath(file); + result = stat_without_gvl(RSTRING_PTR(file), st); } RB_GC_GUARD(file); return result; @@ -1309,11 +1315,11 @@ rb_stat(VALUE file, struct stat *st) /* * call-seq: - * File.stat(file_name) -> stat + * File.stat(filepath) -> stat * - * Returns a File::Stat object for the named file (see File::Stat). + * Returns a File::Stat object for the file at +filepath+ (see File::Stat): * - * File.stat("testfile").mtime #=> Tue Apr 08 12:58:04 CDT 2003 + * File.stat('t.txt').class # => File::Stat * */ @@ -1325,7 +1331,7 @@ rb_file_s_stat(VALUE klass, VALUE fname) FilePathValue(fname); fname = rb_str_encode_ospath(fname); if (stat_without_gvl(RSTRING_PTR(fname), &st) < 0) { - rb_sys_fail_path(fname); + rb_sys_fail_path(fname); } return rb_stat_new(&st); } @@ -1353,7 +1359,7 @@ rb_io_stat(VALUE obj) GetOpenFile(obj, fptr); if (fstat(fptr->fd, &st) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return rb_stat_new(&st); } @@ -1374,22 +1380,20 @@ 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 */ /* * call-seq: - * File.lstat(file_name) -> stat + * File.lstat(filepath) -> stat * - * Same as File::stat, but does not follow the last symbolic link. - * Instead, reports on the link itself. + * Like File::stat, but does not follow the last symbolic link; + * instead, returns a File::Stat object for the link itself. * - * File.symlink("testfile", "link2test") #=> 0 - * File.stat("testfile").size #=> 66 - * File.lstat("link2test").size #=> 8 - * File.stat("link2test").size #=> 66 + * File.symlink('t.txt', 'symlink') + * File.stat('symlink').size # => 47 + * File.lstat('symlink').size # => 5 * */ @@ -1402,7 +1406,7 @@ rb_file_s_lstat(VALUE klass, VALUE fname) FilePathValue(fname); fname = rb_str_encode_ospath(fname); if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) { - rb_sys_fail_path(fname); + rb_sys_fail_path(fname); } return rb_stat_new(&st); #else @@ -1412,16 +1416,16 @@ rb_file_s_lstat(VALUE klass, VALUE fname) /* * call-seq: - * file.lstat -> stat + * lstat -> stat * - * Same as IO#stat, but does not follow the last symbolic link. - * Instead, reports on the link itself. + * Like File#stat, but does not follow the last symbolic link; + * instead, returns a File::Stat object for the link itself: + * + * File.symlink('t.txt', 'symlink') + * f = File.new('symlink') + * f.stat.size # => 47 + * f.lstat.size # => 11 * - * File.symlink("testfile", "link2test") #=> 0 - * File.stat("testfile").size #=> 66 - * f = File.new("link2test") - * f.lstat.size #=> 8 - * f.stat.size #=> 66 */ static VALUE @@ -1436,7 +1440,7 @@ rb_file_lstat(VALUE obj) if (NIL_P(fptr->pathv)) return Qnil; path = rb_str_encode_ospath(fptr->pathv); if (lstat_without_gvl(RSTRING_PTR(path), &st) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return rb_stat_new(&st); #else @@ -1451,44 +1455,28 @@ rb_group_member(GETGROUPS_T gid) return FALSE; #else int rv = FALSE; - int groups = 16; + int groups; VALUE v = 0; GETGROUPS_T *gary; int anum = -1; if (getgid() == gid || getegid() == gid) - return TRUE; - - /* - * On Mac OS X (Mountain Lion), NGROUPS is 16. But libc and kernel - * accept more larger value. - * So we don't trunk NGROUPS anymore. - */ - while (groups <= RB_MAX_GROUPS) { - gary = ALLOCV_N(GETGROUPS_T, v, groups); - anum = getgroups(groups, gary); - if (anum != -1 && anum != groups) - break; - groups *= 2; - if (v) { - ALLOCV_END(v); - v = 0; - } - } - if (anum == -1) - return FALSE; + return TRUE; + groups = getgroups(0, NULL); + gary = ALLOCV_N(GETGROUPS_T, v, groups); + anum = getgroups(groups, gary); while (--anum >= 0) { - if (gary[anum] == gid) { - rv = TRUE; - break; - } + if (gary[anum] == gid) { + rv = TRUE; + break; + } } if (v) - ALLOCV_END(v); + ALLOCV_END(v); return rv; -#endif +#endif /* defined(_WIN32) || !defined(HAVE_GETGROUPS) */ } #ifndef S_IXUGO @@ -1511,37 +1499,37 @@ eaccess(const char *path, int mode) /* no setuid nor setgid. run shortcut. */ if (getuid() == euid && getgid() == getegid()) - return access(path, mode); + return access(path, mode); if (STAT(path, &st) < 0) - return -1; + return -1; if (euid == 0) { - /* Root can read or write any file. */ - if (!(mode & X_OK)) - return 0; + /* Root can read or write any file. */ + if (!(mode & X_OK)) + return 0; - /* Root can execute any file that has any one of the execute - bits set. */ - if (st.st_mode & S_IXUGO) - return 0; + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & S_IXUGO) + return 0; - return -1; + return -1; } if (st.st_uid == euid) /* owner */ - mode <<= 6; + mode <<= 6; else if (rb_group_member(st.st_gid)) - mode <<= 3; + mode <<= 3; if ((int)(st.st_mode & mode) == mode) return 0; return -1; #else return access(path, mode); -#endif +#endif /* USE_GETEUID */ } -#endif +#endif /* HAVE_EACCESS */ struct access_arg { const char *path; @@ -1566,8 +1554,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 * @@ -1588,8 +1575,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); } /* @@ -1603,18 +1589,21 @@ rb_access(VALUE fname, int mode) */ /* - * Document-method: directory? - * * call-seq: - * File.directory?(file_name) -> true or false + * File.directory?(path) -> true or false * - * Returns <code>true</code> if the named file is a directory, - * or a symlink that points at a directory, and <code>false</code> - * otherwise. + * With string +object+ given, returns +true+ if +path+ is a string path + * leading to a directory, or to a symbolic link to a directory; +false+ otherwise: * - * _file_name_ can be an IO object. + * File.directory?('.') # => true + * File.directory?('foo') # => false + * File.symlink('.', 'dirlink') # => 0 + * File.directory?('dirlink') # => true + * File.symlink('t,txt', 'filelink') # => 0 + * File.directory?('filelink') # => false + * + * Argument +path+ can be an IO object. * - * File.directory?(".") */ VALUE @@ -1633,11 +1622,14 @@ rb_file_directory_p(VALUE obj, VALUE fname) /* * call-seq: - * File.pipe?(file_name) -> true or false + * File.pipe?(filepath) -> true or false * - * Returns <code>true</code> if the named file is a pipe. + * Returns +true+ if +filepath+ points to a pipe, +false+ otherwise: + * + * File.mkfifo('tmp/fifo') + * File.pipe?('tmp/fifo') # => true + * File.pipe?('t.txt') # => false * - * _file_name_ can be an IO object. */ static VALUE @@ -1659,9 +1651,14 @@ rb_file_pipe_p(VALUE obj, VALUE fname) /* * call-seq: - * File.symlink?(file_name) -> true or false + * File.symlink?(filepath) -> true or false + * + * Returns +true+ if +filepath+ points to a symbolic link, +false+ otherwise: + * + * symlink = File.symlink('t.txt', 'symlink') + * File.symlink?('symlink') # => true + * File.symlink?('t.txt') # => false * - * Returns <code>true</code> if the named file is a symbolic link. */ static VALUE @@ -1695,11 +1692,14 @@ rb_file_symlink_p(VALUE obj, VALUE fname) /* * call-seq: - * File.socket?(file_name) -> true or false + * File.socket?(filepath) -> true or false * - * Returns <code>true</code> if the named file is a socket. + * Returns +true+ if +filepath+ points to a socket, +false+ otherwise: + * + * require 'socket' + * File.socket?(Socket.new(:INET, :STREAM)) # => true + * File.socket?(File.new('t.txt')) # => false * - * _file_name_ can be an IO object. */ static VALUE @@ -1724,18 +1724,20 @@ rb_file_socket_p(VALUE obj, VALUE fname) if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISSOCK(st.st_mode)) return Qtrue; - #endif + return Qfalse; } /* * call-seq: - * File.blockdev?(file_name) -> true or false + * File.blockdev?(filepath) -> true or false * - * Returns <code>true</code> if the named file is a block device. + * Returns +true+ if +filepath+ points to a block device, +false+ otherwise: + * + * File.blockdev?('/dev/sda1') # => true + * File.blockdev?(File.new('t.tmp')) # => false * - * _file_name_ can be an IO object. */ static VALUE @@ -1761,11 +1763,13 @@ rb_file_blockdev_p(VALUE obj, VALUE fname) /* * call-seq: - * File.chardev?(file_name) -> true or false + * File.chardev?(filepath) -> true or false * - * Returns <code>true</code> if the named file is a character device. + * Returns +true+ if +filepath+ points to a character device, +false+ otherwise. + * + * File.chardev?($stdin) # => true + * File.chardev?('t.txt') # => false * - * _file_name_ can be an IO object. */ static VALUE rb_file_chardev_p(VALUE obj, VALUE fname) @@ -1802,23 +1806,6 @@ rb_file_exist_p(VALUE obj, VALUE fname) return Qtrue; } -/* :nodoc: */ -static VALUE -rb_file_exists_p(VALUE obj, VALUE fname) -{ - const char *s = "FileTest#exist?"; - if (obj == rb_mFileTest) { - s = "FileTest.exist?"; - } - else if (obj == rb_cFile || - (RB_TYPE_P(obj, T_CLASS) && - RTEST(rb_class_inherited_p(obj, rb_cFile)))) { - s = "File.exist?"; - } - rb_warn_deprecated("%.*ss?", s, (int)(strlen(s)-1), s); - return rb_file_exist_p(obj, fname); -} - /* * call-seq: * File.readable?(file_name) -> true or false @@ -1885,7 +1872,7 @@ rb_file_world_readable_p(VALUE obj, VALUE fname) if (rb_stat(fname, &st) < 0) return Qnil; if ((st.st_mode & (S_IROTH)) == S_IROTH) { - return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif return Qnil; @@ -1949,7 +1936,7 @@ rb_file_world_writable_p(VALUE obj, VALUE fname) if (rb_stat(fname, &st) < 0) return Qnil; if ((st.st_mode & (S_IWOTH)) == S_IWOTH) { - return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif return Qnil; @@ -2177,7 +2164,7 @@ rb_file_sticky_p(VALUE obj, VALUE fname) #ifdef S_ISVTX return check3rdbyte(fname, S_ISVTX); #else - return Qnil; + return Qfalse; #endif } @@ -2232,9 +2219,9 @@ rb_file_s_size(VALUE klass, VALUE fname) struct stat st; if (rb_stat(fname, &st) < 0) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); } return OFFT2NUM(st.st_size); } @@ -2245,36 +2232,36 @@ rb_file_ftype(const struct stat *st) const char *t; if (S_ISREG(st->st_mode)) { - t = "file"; + t = "file"; } else if (S_ISDIR(st->st_mode)) { - t = "directory"; + t = "directory"; } else if (S_ISCHR(st->st_mode)) { - t = "characterSpecial"; + t = "characterSpecial"; } #ifdef S_ISBLK else if (S_ISBLK(st->st_mode)) { - t = "blockSpecial"; + t = "blockSpecial"; } #endif #ifdef S_ISFIFO else if (S_ISFIFO(st->st_mode)) { - t = "fifo"; + t = "fifo"; } #endif #ifdef S_ISLNK else if (S_ISLNK(st->st_mode)) { - t = "link"; + t = "link"; } #endif #ifdef S_ISSOCK else if (S_ISSOCK(st->st_mode)) { - t = "socket"; + t = "socket"; } #endif else { - t = "unknown"; + t = "unknown"; } return rb_usascii_str_new2(t); @@ -2303,7 +2290,7 @@ rb_file_s_ftype(VALUE klass, VALUE fname) FilePathValue(fname); fname = rb_str_encode_ospath(fname); if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) { - rb_sys_fail_path(fname); + rb_sys_fail_path(fname); } return rb_file_ftype(&st); @@ -2327,9 +2314,9 @@ rb_file_s_atime(VALUE klass, VALUE fname) struct stat st; if (rb_stat(fname, &st) < 0) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); } return stat_atime(&st); } @@ -2353,7 +2340,7 @@ rb_file_atime(VALUE obj) GetOpenFile(obj, fptr); if (fstat(fptr->fd, &st) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return stat_atime(&st); } @@ -2376,9 +2363,9 @@ rb_file_s_mtime(VALUE klass, VALUE fname) struct stat st; if (rb_stat(fname, &st) < 0) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); } return stat_mtime(&st); } @@ -2401,7 +2388,7 @@ rb_file_mtime(VALUE obj) GetOpenFile(obj, fptr); if (fstat(fptr->fd, &st) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return stat_mtime(&st); } @@ -2428,9 +2415,9 @@ rb_file_s_ctime(VALUE klass, VALUE fname) struct stat st; if (rb_stat(fname, &st) < 0) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); } return stat_ctime(&st); } @@ -2456,11 +2443,12 @@ rb_file_ctime(VALUE obj) GetOpenFile(obj, fptr); if (fstat(fptr->fd, &st) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return stat_ctime(&st); } +#if defined(HAVE_STAT_BIRTHTIME) /* * call-seq: * File.birthtime(file_name) -> time @@ -2475,16 +2463,15 @@ 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; if (rb_statx(fname, &st, STATX_BTIME) < 0) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); } return statx_birthtime(&st, fname); } @@ -2513,7 +2500,7 @@ rb_file_birthtime(VALUE obj) GetOpenFile(obj, fptr); if (fstatx_without_gvl(fptr->fd, &st, STATX_BTIME) == -1) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return statx_birthtime(&st, fptr->pathv); } @@ -2521,6 +2508,29 @@ rb_file_birthtime(VALUE obj) # define rb_file_birthtime rb_f_notimplement #endif +rb_off_t +rb_file_size(VALUE file) +{ + if (RB_TYPE_P(file, T_FILE)) { + rb_io_t *fptr; + struct stat st; + + RB_IO_POINTER(file, fptr); + if (fptr->mode & FMODE_WRITABLE) { + rb_io_flush_raw(file, 0); + } + + if (fstat(fptr->fd, &st) == -1) { + rb_sys_fail_path(fptr->pathv); + } + + return st.st_size; + } + else { + return NUM2OFFT(rb_funcall(file, idSize, 0)); + } +} + /* * call-seq: * file.size -> integer @@ -2532,19 +2542,32 @@ rb_file_birthtime(VALUE obj) */ static VALUE -rb_file_size(VALUE obj) +file_size(VALUE self) { - rb_io_t *fptr; - struct stat st; + return OFFT2NUM(rb_file_size(self)); +} - GetOpenFile(obj, fptr); - if (fptr->mode & FMODE_WRITABLE) { - rb_io_flush_raw(obj, 0); - } - if (fstat(fptr->fd, &st) == -1) { - rb_sys_fail_path(fptr->pathv); - } - return OFFT2NUM(st.st_size); +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 @@ -2577,6 +2600,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(int fd, mode_t mode) +{ + (void)rb_chmod; /* suppress unused-function warning when HAVE_FCHMOD */ + struct nogvl_fchmod_data data = {.fd = fd, .mode = mode}; + return (int)rb_thread_io_blocking_region(io_blocking_fchmod, &data, fd); +} +#endif + /* * call-seq: * file.chmod(mode_int) -> 0 @@ -2603,19 +2649,19 @@ rb_file_chmod(VALUE obj, VALUE vmode) GetOpenFile(obj, fptr); #ifdef HAVE_FCHMOD - if (fchmod(fptr->fd, mode) == -1) { - if (HAVE_FCHMOD || errno != ENOSYS) - rb_sys_fail_path(fptr->pathv); + if (rb_fchmod(fptr->fd, mode) == -1) { + if (HAVE_FCHMOD || errno != ENOSYS) + rb_sys_fail_path(fptr->pathv); } else { - if (!HAVE_FCHMOD) return INT2FIX(0); + if (!HAVE_FCHMOD) return INT2FIX(0); } #endif #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) - rb_sys_fail_path(fptr->pathv); + if (rb_chmod(RSTRING_PTR(path), mode) == -1) + rb_sys_fail_path(fptr->pathv); #endif return INT2FIX(0); @@ -2656,7 +2702,7 @@ static inline rb_uid_t to_uid(VALUE u) { if (NIL_P(u)) { - return (rb_uid_t)-1; + return (rb_uid_t)-1; } return NUM2UIDT(u); } @@ -2665,7 +2711,7 @@ static inline rb_gid_t to_gid(VALUE g) { if (NIL_P(g)) { - return (rb_gid_t)-1; + return (rb_gid_t)-1; } return NUM2GIDT(g); } @@ -2709,6 +2755,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 @@ -2740,11 +2831,11 @@ 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) - rb_sys_fail_path(fptr->pathv); + if (rb_chown(RSTRING_PTR(path), o, g) == -1) + rb_sys_fail_path(fptr->pathv); #else - if (fchown(fptr->fd, o, g) == -1) - rb_sys_fail_path(fptr->pathv); + if (rb_fchown(fptr->fd, o, g) == -1) + rb_sys_fail_path(fptr->pathv); #endif return INT2FIX(0); @@ -2801,39 +2892,62 @@ utime_failed(struct apply_arg *aa) struct utime_args *ua = aa->arg; if (ua->tsp && e == EINVAL) { - VALUE e[2], a = Qnil, m = Qnil; - int d = 0; - VALUE atime = ua->atime; - VALUE mtime = ua->mtime; - - if (!NIL_P(atime)) { - a = rb_inspect(atime); - } - if (!NIL_P(mtime) && mtime != atime && !rb_equal(atime, mtime)) { - m = rb_inspect(mtime); - } - if (NIL_P(a)) e[0] = m; - else if (NIL_P(m) || rb_str_cmp(a, m) == 0) e[0] = a; - else { - e[0] = rb_str_plus(a, rb_str_new_cstr(" or ")); - rb_str_append(e[0], m); - d = 1; - } - if (!NIL_P(e[0])) { - if (path) { - if (!d) e[0] = rb_str_dup(e[0]); - rb_str_append(rb_str_cat2(e[0], " for "), path); - } - e[1] = INT2FIX(EINVAL); - rb_exc_raise(rb_class_new_instance(2, e, rb_eSystemCallError)); - } + VALUE e[2], a = Qnil, m = Qnil; + int d = 0; + VALUE atime = ua->atime; + VALUE mtime = ua->mtime; + + if (!NIL_P(atime)) { + a = rb_inspect(atime); + } + if (!NIL_P(mtime) && mtime != atime && !rb_equal(atime, mtime)) { + m = rb_inspect(mtime); + } + if (NIL_P(a)) e[0] = m; + else if (NIL_P(m) || rb_str_cmp(a, m) == 0) e[0] = a; + else { + e[0] = rb_str_plus(a, rb_str_new_cstr(" or ")); + rb_str_append(e[0], m); + d = 1; + } + if (!NIL_P(e[0])) { + if (path) { + if (!d) e[0] = rb_str_dup(e[0]); + rb_str_append(rb_str_cat2(e[0], " for "), path); + } + e[1] = INT2FIX(EINVAL); + rb_exc_raise(rb_class_new_instance(2, e, rb_eSystemCallError)); + } } rb_syserr_fail_path(e, path); } -#endif +#endif /* UTIME_EINVAL */ #if defined(HAVE_UTIMES) +# if !defined(HAVE_UTIMENSAT) +/* utimensat() is not found, runtime check is not needed */ +# 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) +typedef int utimensat_func(int, const char *, const struct timespec [2], int); + +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wunguarded-availability-new) +static inline utimensat_func * +rb_utimensat(void) +{ + return &utimensat; +} +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 /* defined(__has_attribute) && __has_attribute(availability) */ +# endif /* __APPLE__ && < MAC_OS_X_VERSION_13_0 */ + static int utime_internal(const char *path, void *arg) { @@ -2842,36 +2956,41 @@ utime_internal(const char *path, void *arg) struct timeval tvbuf[2], *tvp = NULL; #if defined(HAVE_UTIMENSAT) +# if defined(__APPLE__) + const int try_utimensat = utimensat != NULL; + const int try_utimensat_follow = utimensat != NULL; +# 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 /* __APPLE__ */ int flags = 0; if (v->follow ? try_utimensat_follow : try_utimensat) { # ifdef AT_SYMLINK_NOFOLLOW - if (v->follow) { - flags = AT_SYMLINK_NOFOLLOW; - } + if (v->follow) { + flags = AT_SYMLINK_NOFOLLOW; + } # endif - if (utimensat(AT_FDCWD, path, tsp, flags) < 0) { - if (errno == ENOSYS) { + int result = utimensat(AT_FDCWD, path, tsp, flags); +# ifdef TRY_UTIMENSAT + if (result < 0 && errno == ENOSYS) { # ifdef AT_SYMLINK_NOFOLLOW - try_utimensat_follow = 0; -# endif - if (!v->follow) - try_utimensat = 0; - goto no_utimensat; - } - return -1; /* calls utime_failed */ + try_utimensat_follow = 0; +# endif /* AT_SYMLINK_NOFOLLOW */ + if (!v->follow) + try_utimensat = 0; } - return 0; + else +# endif /* TRY_UTIMESAT */ + return result; } -no_utimensat: -#endif +#endif /* defined(HAVE_UTIMENSAT) */ if (tsp) { tvbuf[0].tv_sec = tsp[0].tv_sec; @@ -2886,7 +3005,7 @@ no_utimensat: return utimes(path, tvp); } -#else +#else /* !defined(HAVE_UTIMES) */ #if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H struct utimbuf { @@ -2908,8 +3027,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) @@ -2924,12 +3042,12 @@ utime_internal_i(int argc, VALUE *argv, int follow) args.follow = follow; if (!NIL_P(args.atime) || !NIL_P(args.mtime)) { - tsp = tss; - tsp[0] = rb_time_timespec(args.atime); - if (args.atime == args.mtime) - tsp[1] = tsp[0]; - else - tsp[1] = rb_time_timespec(args.mtime); + tsp = tss; + tsp[0] = rb_time_timespec(args.atime); + if (args.atime == args.mtime) + tsp[1] = tsp[0]; + else + tsp[1] = rb_time_timespec(args.mtime); } args.tsp = tsp; @@ -2993,7 +3111,7 @@ syserr_fail2_in(const char *func, int e, VALUE s1, VALUE s2) #endif if (e == EEXIST) { - rb_syserr_fail_path(e, rb_str_ellipsize(s2, max_pathlen)); + rb_syserr_fail_path(e, rb_str_ellipsize(s2, max_pathlen)); } str = rb_str_new_cstr("("); rb_str_append(str, rb_str_ellipsize(s1, max_pathlen)); @@ -3029,7 +3147,7 @@ rb_file_s_link(VALUE klass, VALUE from, VALUE to) to = rb_str_encode_ospath(to); if (link(StringValueCStr(from), StringValueCStr(to)) < 0) { - sys_fail2(from, to); + sys_fail2(from, to); } return INT2FIX(0); } @@ -3059,7 +3177,7 @@ rb_file_s_symlink(VALUE klass, VALUE from, VALUE to) to = rb_str_encode_ospath(to); if (symlink(StringValueCStr(from), StringValueCStr(to)) < 0) { - sys_fail2(from, to); + sys_fail2(from, to); } return INT2FIX(0); } @@ -3085,7 +3203,6 @@ rb_file_s_readlink(VALUE klass, VALUE path) return rb_readlink(path, rb_filesystem_encoding()); } -#ifndef _WIN32 struct readlink_arg { const char *path; char *buf; @@ -3109,8 +3226,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 @@ -3125,23 +3241,22 @@ rb_readlink(VALUE path, rb_encoding *enc) v = rb_enc_str_new(0, size, enc); while ((rv = readlink_without_gvl(path, v, size)) == size #ifdef _AIX - || (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */ + || (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */ #endif - ) { - rb_str_modify_expand(v, size); - size *= 2; - rb_str_set_len(v, size); + ) { + rb_str_modify_expand(v, size); + size *= 2; + rb_str_set_len(v, size); } if (rv < 0) { - int e = errno; - rb_str_resize(v, 0); - rb_syserr_fail_path(e, path); + int e = errno; + rb_str_resize(v, 0); + rb_syserr_fail_path(e, path); } rb_str_resize(v, rv); return v; } -#endif #else #define rb_file_s_readlink rb_f_notimplement #endif @@ -3212,19 +3327,18 @@ 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) { - int e = errno; + if (IO_WITHOUT_GVL_INT(no_gvl_rename, &ra) < 0) { + int e = errno; #if defined DOSISH - switch (e) { - case EEXIST: - if (chmod(ra.dst, 0666) == 0 && - unlink(ra.dst) == 0 && - rename(ra.src, ra.dst) == 0) - return INT2FIX(0); - } + switch (e) { + case EEXIST: + if (chmod(ra.dst, 0666) == 0 && + unlink(ra.dst) == 0 && + rename(ra.src, ra.dst) == 0) + return INT2FIX(0); + } #endif - syserr_fail2(e, from, to); + syserr_fail2(e, from, to); } return INT2FIX(0); @@ -3252,11 +3366,11 @@ rb_file_s_umask(int argc, VALUE *argv, VALUE _) switch (argc) { case 0: - omask = umask(0); - umask(omask); + omask = umask(0); + umask(omask); break; case 1: - omask = umask(NUM2MODET(argv[0])); + omask = umask(NUM2MODET(argv[0])); break; default: rb_error_arity(argc, 0, 1); @@ -3282,12 +3396,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 @@ -3301,6 +3416,7 @@ 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 @@ -3321,10 +3437,10 @@ static inline int has_drive_letter(const char *buf) { if (ISALPHA(buf[0]) && buf[1] == ':') { - return 1; + return 1; } else { - return 0; + return 0; } } @@ -3345,17 +3461,16 @@ getcwdofdrv(int drv) */ oldcwd = ruby_getcwd(); if (chdir(drive) == 0) { - drvcwd = ruby_getcwd(); - chdir(oldcwd); - xfree(oldcwd); + drvcwd = ruby_getcwd(); + chdir(oldcwd); + xfree(oldcwd); } else { - /* perhaps the drive is not exist. we return only drive letter */ - drvcwd = strdup(drive); + /* perhaps the drive is not exist. we return only drive letter */ + drvcwd = strdup(drive); } return drvcwd; } -#endif static inline int not_same_drive(VALUE path, int drive) @@ -3363,13 +3478,14 @@ not_same_drive(VALUE path, int drive) const char *p = RSTRING_PTR(path); if (RSTRING_LEN(path) < 2) return 0; if (has_drive_letter(p)) { - return TOLOWER(p[0]) != TOLOWER(drive); + return TOLOWER(p[0]) != TOLOWER(drive); } else { - return has_unc(p); + return has_unc(p); } } -#endif +#endif /* _WIN32 */ +#endif /* DOSISH_DRIVE_LETTER */ static inline char * skiproot(const char *path, const char *end, rb_encoding *enc) @@ -3386,7 +3502,7 @@ char * rb_enc_path_next(const char *s, const char *e, rb_encoding *enc) { while (s < e && !isdirsep(*s)) { - Inc(s, e, enc); + Inc(s, e, enc); } return (char *)s; } @@ -3402,18 +3518,18 @@ rb_enc_path_skip_prefix(const char *path, const char *end, 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); - return (char *)path; + 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); + return (char *)path; } #endif #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(path)) - return (char *)(path + 2); -#endif + return (char *)(path + 2); #endif +#endif /* defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) */ return (char *)path; } @@ -3435,15 +3551,15 @@ rb_enc_path_last_separator(const char *path, const char *end, rb_encoding *enc) { char *last = NULL; while (path < end) { - if (isdirsep(*path)) { - const char *tmp = path++; - while (path < end && isdirsep(*path)) path++; - if (path >= end) break; - last = (char *)tmp; - } - else { - Inc(path, end, enc); - } + if (isdirsep(*path)) { + const char *tmp = path++; + while (path < end && isdirsep(*path)) path++; + if (path >= end) break; + last = (char *)tmp; + } + else { + Inc(path, end, enc); + } } return last; } @@ -3452,14 +3568,14 @@ static char * chompdirsep(const char *path, const char *end, rb_encoding *enc) { while (path < end) { - if (isdirsep(*path)) { - const char *last = path++; - while (path < end && isdirsep(*path)) path++; - if (path >= end) return (char *)last; - } - else { - Inc(path, end, enc); - } + if (isdirsep(*path)) { + const char *last = path++; + while (path < end && isdirsep(*path)) path++; + if (path >= end) return (char *)last; + } + else { + Inc(path, end, enc); + } } return (char *)path; } @@ -3491,33 +3607,33 @@ ntfs_tail(const char *path, const char *end, rb_encoding *enc) { while (path < end && *path == '.') path++; while (path < end && !isADS(*path)) { - if (istrailinggarbage(*path)) { - const char *last = path++; - while (path < end && istrailinggarbage(*path)) path++; - if (path >= end || isADS(*path)) return (char *)last; - } - else if (isdirsep(*path)) { - const char *last = path++; - while (path < end && isdirsep(*path)) path++; - if (path >= end) return (char *)last; - if (isADS(*path)) path++; - } - else { - Inc(path, end, enc); - } + if (istrailinggarbage(*path)) { + const char *last = path++; + while (path < end && istrailinggarbage(*path)) path++; + if (path >= end || isADS(*path)) return (char *)last; + } + else if (isdirsep(*path)) { + const char *last = path++; + while (path < end && isdirsep(*path)) path++; + if (path >= end) return (char *)last; + if (isADS(*path)) path++; + } + else { + Inc(path, end, enc); + } } return (char *)path; } -#endif +#endif /* USE_NTFS */ #define BUFCHECK(cond) do {\ bdiff = p - buf;\ if (cond) {\ - do {buflen *= 2;} while (cond);\ - rb_str_resize(result, buflen);\ - buf = RSTRING_PTR(result);\ - p = buf + bdiff;\ - pend = buf + buflen;\ + do {buflen *= 2;} while (cond);\ + rb_str_resize(result, buflen);\ + buf = RSTRING_PTR(result);\ + p = buf + bdiff;\ + pend = buf + buflen;\ }\ } while (0) @@ -3566,9 +3682,9 @@ copy_home_path(VALUE result, const char *dir) #if defined DOSISH || defined __CYGWIN__ enc = rb_enc_from_index(encidx); for (bend = (p = buf) + dirlen; p < bend; Inc(p, bend, enc)) { - if (*p == '\\') { - *p = '/'; - } + if (*p == '\\') { + *p = '/'; + } } #endif return result; @@ -3599,11 +3715,11 @@ rb_home_dir_of(VALUE user, VALUE result) pwPtr = getpwnam(username); #else if (strcasecmp(username, getlogin()) == 0) - dir = pwPtr = getenv("HOME"); + dir = pwPtr = getenv("HOME"); #endif if (!pwPtr) { - endpwent(); - rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user); + endpwent(); + rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user); } #ifdef HAVE_PWD_H dir = pwPtr->pw_dir; @@ -3613,7 +3729,7 @@ rb_home_dir_of(VALUE user, VALUE result) return result; } -#ifndef _WIN32 +#ifndef _WIN32 /* this encompasses rb_file_expand_path_internal */ VALUE rb_default_home_dir(VALUE result) { @@ -3641,15 +3757,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()); } } @@ -3658,9 +3774,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); } @@ -3685,15 +3801,23 @@ 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)); - if (direnc != fsenc) { - dirname = rb_str_conv_enc(dirname, fsenc, direnc); - RSTRING_GETMEM(dirname, cwdp, dirlen); - } - else if (NORMALIZE_UTF8PATH) { - RSTRING_GETMEM(dirname, cwdp, dirlen); - } - *enc = direnc; + 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); + } + else if (NORMALIZE_UTF8PATH) { + RSTRING_GETMEM(dirname, cwdp, dirlen); + } + *enc = direnc; } do {buflen *= 2;} while (dirlen > buflen); rb_str_resize(result, buflen); @@ -3719,115 +3843,115 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na BUFINIT(); if (s[0] == '~' && abs_mode == 0) { /* execute only if NOT absolute_path() */ - long userlen = 0; - if (isdirsep(s[1]) || s[1] == '\0') { - buf = 0; - b = 0; - rb_str_set_len(result, 0); - if (*++s) ++s; - rb_default_home_dir(result); - } - else { - s = nextdirsep(b = s, fend, enc); - b++; /* b[0] is '~' */ - userlen = s - b; - BUFCHECK(bdiff + userlen >= buflen); - memcpy(p, b, userlen); - ENC_CODERANGE_CLEAR(result); - rb_str_set_len(result, userlen); - rb_enc_associate(result, enc); - rb_home_dir_of(result, result); - buf = p + 1; - p += userlen; - } - if (!rb_is_absolute_path(RSTRING_PTR(result))) { - if (userlen) { - rb_enc_raise(enc, rb_eArgError, "non-absolute home of %.*s%.0"PRIsVALUE, - (int)userlen, b, fname); - } - else { - rb_raise(rb_eArgError, "non-absolute home"); - } - } - BUFINIT(); - p = pend; + long userlen = 0; + if (isdirsep(s[1]) || s[1] == '\0') { + buf = 0; + b = 0; + rb_str_set_len(result, 0); + if (*++s) ++s; + rb_default_home_dir(result); + } + else { + s = nextdirsep(b = s, fend, enc); + b++; /* b[0] is '~' */ + userlen = s - b; + BUFCHECK(bdiff + userlen >= buflen); + memcpy(p, b, userlen); + ENC_CODERANGE_CLEAR(result); + rb_str_set_len(result, userlen); + rb_enc_associate(result, enc); + rb_home_dir_of(result, result); + buf = p + 1; + p += userlen; + } + if (!rb_is_absolute_path(RSTRING_PTR(result))) { + if (userlen) { + rb_enc_raise(enc, rb_eArgError, "non-absolute home of %.*s%.0"PRIsVALUE, + (int)userlen, b, fname); + } + else { + rb_raise(rb_eArgError, "non-absolute home"); + } + } + BUFINIT(); + p = pend; } #ifdef DOSISH_DRIVE_LETTER /* skip drive letter */ else if (has_drive_letter(s)) { - if (isdirsep(s[2])) { - /* specified drive letter, and full path */ - /* skip drive letter */ - BUFCHECK(bdiff + 2 >= buflen); - memcpy(p, s, 2); - p += 2; - s += 2; - rb_enc_copy(result, fname); - } - else { - /* specified drive, but not full path */ - int same = 0; - if (!NIL_P(dname) && !not_same_drive(dname, s[0])) { - rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result); - BUFINIT(); - if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) { - /* ok, same drive */ - same = 1; - } - } - if (!same) { - char *e = append_fspath(result, fname, getcwdofdrv(*s), &enc, fsenc); - BUFINIT(); - p = e; - } - else { - rb_enc_associate(result, enc = fs_enc_check(result, fname)); - p = pend; - } - p = chompdirsep(skiproot(buf, p, enc), p, enc); - s += 2; - } + if (isdirsep(s[2])) { + /* specified drive letter, and full path */ + /* skip drive letter */ + BUFCHECK(bdiff + 2 >= buflen); + memcpy(p, s, 2); + p += 2; + s += 2; + rb_enc_copy(result, fname); + } + else { + /* specified drive, but not full path */ + int same = 0; + if (!NIL_P(dname) && !not_same_drive(dname, s[0])) { + rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result); + BUFINIT(); + if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) { + /* ok, same drive */ + same = 1; + } + } + if (!same) { + char *e = append_fspath(result, fname, getcwdofdrv(*s), &enc, fsenc); + BUFINIT(); + p = e; + } + else { + rb_enc_associate(result, enc = fs_enc_check(result, fname)); + p = pend; + } + p = chompdirsep(skiproot(buf, p, enc), p, 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); - rb_enc_associate(result, fs_enc_check(result, fname)); - BUFINIT(); - p = pend; - } - else { - char *e = append_fspath(result, fname, ruby_getcwd(), &enc, fsenc); - BUFINIT(); - p = e; - } + if (!NIL_P(dname)) { + rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result); + rb_enc_associate(result, fs_enc_check(result, fname)); + BUFINIT(); + p = pend; + } + else { + char *e = append_fspath(result, fname, ruby_getcwd(), &enc, fsenc); + BUFINIT(); + p = e; + } #if defined DOSISH || defined __CYGWIN__ - 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); - } - else -#endif - p = chompdirsep(skiproot(buf, p, enc), p, enc); + 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); + } + else +#endif /* defined DOSISH || defined __CYGWIN__ */ + p = chompdirsep(skiproot(buf, p, enc), p, enc); } else { - size_t len; - b = s; - do s++; while (isdirsep(*s)); - len = s - b; - p = buf + len; - BUFCHECK(bdiff >= buflen); - memset(buf, '/', len); - rb_str_set_len(result, len); - rb_enc_associate(result, fs_enc_check(result, fname)); + size_t len; + b = s; + do s++; while (isdirsep(*s)); + len = s - b; + p = buf + len; + BUFCHECK(bdiff >= buflen); + memset(buf, '/', len); + rb_str_set_len(result, len); + rb_enc_associate(result, fs_enc_check(result, fname)); } if (p > buf && p[-1] == '/') - --p; + --p; else { - rb_str_set_len(result, p-buf); - BUFCHECK(bdiff + 1 >= buflen); - *p = '/'; + rb_str_set_len(result, p-buf); + BUFCHECK(bdiff + 1 >= buflen); + *p = '/'; } rb_str_set_len(result, p-buf+1); @@ -3837,225 +3961,225 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na b = s; while (*s) { - switch (*s) { - case '.': - if (b == s++) { /* beginning of path element */ - switch (*s) { - case '\0': - b = s; - break; - case '.': - if (*(s+1) == '\0' || isdirsep(*(s+1))) { - /* We must go back to the parent */ - char *n; - *p = '\0'; - if (!(n = strrdirsep(root, p, enc))) { - *p = '/'; - } - else { - p = n; - } - b = ++s; - } + switch (*s) { + case '.': + if (b == s++) { /* beginning of path element */ + switch (*s) { + case '\0': + b = s; + break; + case '.': + if (*(s+1) == '\0' || isdirsep(*(s+1))) { + /* We must go back to the parent */ + char *n; + *p = '\0'; + if (!(n = strrdirsep(root, p, enc))) { + *p = '/'; + } + else { + p = n; + } + b = ++s; + } #if USE_NTFS - else { - do ++s; while (istrailinggarbage(*s)); - } -#endif - break; - case '/': + else { + do ++s; while (istrailinggarbage(*s)); + } +#endif /* USE_NTFS */ + break; + case '/': #if defined DOSISH || defined __CYGWIN__ - case '\\': -#endif - b = ++s; - break; - default: - /* ordinary path element, beginning don't move */ - break; - } - } + case '\\': +#endif + b = ++s; + break; + default: + /* ordinary path element, beginning don't move */ + break; + } + } #if USE_NTFS - else { - --s; - case ' ': { - const char *e = s; - while (s < fend && istrailinggarbage(*s)) s++; - if (s >= fend) { - s = e; - goto endpath; - } - } - } -#endif - break; - case '/': + else { + --s; + case ' ': { + const char *e = s; + while (s < fend && istrailinggarbage(*s)) s++; + if (s >= fend) { + s = e; + goto endpath; + } + } + } +#endif /* USE_NTFS */ + break; + case '/': #if defined DOSISH || defined __CYGWIN__ - case '\\': -#endif - if (s > b) { - WITH_ROOTDIFF(BUFCOPY(b, s-b)); - *p = '/'; - } - b = ++s; - break; - default: + case '\\': +#endif + if (s > b) { + WITH_ROOTDIFF(BUFCOPY(b, s-b)); + *p = '/'; + } + b = ++s; + break; + default: #ifdef __APPLE__ - { - int n = ignored_char_p(s, fend, enc); - if (n) { - if (s > b) { - WITH_ROOTDIFF(BUFCOPY(b, s-b)); - *p = '\0'; - } - b = s += n; - break; - } - } -#endif - Inc(s, fend, enc); - break; - } + { + int n = ignored_char_p(s, fend, enc); + if (n) { + if (s > b) { + WITH_ROOTDIFF(BUFCOPY(b, s-b)); + *p = '\0'; + } + b = s += n; + break; + } + } +#endif /* __APPLE__ */ + Inc(s, fend, enc); + break; + } } if (s > b) { #if USE_NTFS # if USE_NTFS_ADS - static const char prime[] = ":$DATA"; - enum {prime_len = sizeof(prime) -1}; + static const char prime[] = ":$DATA"; + enum {prime_len = sizeof(prime) -1}; # endif endpath: # if USE_NTFS_ADS - if (s > b + prime_len && strncasecmp(s - prime_len, prime, prime_len) == 0) { - /* alias of stream */ - /* get rid of a bug of x64 VC++ */ - if (isADS(*(s - (prime_len+1)))) { - s -= prime_len + 1; /* prime */ - } - else if (memchr(b, ':', s - prime_len - b)) { - s -= prime_len; /* alternative */ - } - } -# endif -#endif - BUFCOPY(b, s-b); - rb_str_set_len(result, p-buf); + if (s > b + prime_len && strncasecmp(s - prime_len, prime, prime_len) == 0) { + /* alias of stream */ + /* get rid of a bug of x64 VC++ */ + if (isADS(*(s - (prime_len+1)))) { + s -= prime_len + 1; /* prime */ + } + else if (memchr(b, ':', s - prime_len - b)) { + s -= prime_len; /* alternative */ + } + } +# 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 USE_NTFS *p = '\0'; if ((s = strrdirsep(b = buf, p, enc)) != 0 && !strpbrk(s, "*?")) { - VALUE tmp, v; - size_t len; - int encidx; - WCHAR *wstr; - WIN32_FIND_DATAW wfd; - HANDLE h; + VALUE tmp, v; + size_t len; + int encidx; + WCHAR *wstr; + WIN32_FIND_DATAW wfd; + HANDLE h; #ifdef __CYGWIN__ #ifdef HAVE_CYGWIN_CONV_PATH - char *w32buf = NULL; - const int flags = CCP_POSIX_TO_WIN_A | CCP_RELATIVE; + char *w32buf = NULL; + const int flags = CCP_POSIX_TO_WIN_A | CCP_RELATIVE; #else - char w32buf[MAXPATHLEN]; -#endif - const char *path; - ssize_t bufsize; - int lnk_added = 0, is_symlink = 0; - struct stat st; - p = (char *)s; - len = strlen(p); - if (lstat_without_gvl(buf, &st) == 0 && S_ISLNK(st.st_mode)) { - is_symlink = 1; - if (len > 4 && STRCASECMP(p + len - 4, ".lnk") != 0) { - lnk_added = 1; - } - } - path = *buf ? buf : "/"; + char w32buf[MAXPATHLEN]; +#endif /* HAVE_CYGWIN_CONV_PATH */ + const char *path; + ssize_t bufsize; + int lnk_added = 0, is_symlink = 0; + struct stat st; + p = (char *)s; + len = strlen(p); + if (lstat_without_gvl(buf, &st) == 0 && S_ISLNK(st.st_mode)) { + is_symlink = 1; + if (len > 4 && STRCASECMP(p + len - 4, ".lnk") != 0) { + lnk_added = 1; + } + } + path = *buf ? buf : "/"; #ifdef HAVE_CYGWIN_CONV_PATH - bufsize = cygwin_conv_path(flags, path, NULL, 0); - if (bufsize > 0) { - bufsize += len; - if (lnk_added) bufsize += 4; - w32buf = ALLOCA_N(char, bufsize); - if (cygwin_conv_path(flags, path, w32buf, bufsize) == 0) { - b = w32buf; - } - } -#else - bufsize = MAXPATHLEN; - if (cygwin_conv_to_win32_path(path, w32buf) == 0) { - b = w32buf; - } -#endif - if (is_symlink && b == w32buf) { - *p = '\\'; - strlcat(w32buf, p, bufsize); - if (lnk_added) { - strlcat(w32buf, ".lnk", bufsize); - } - } - else { - lnk_added = 0; - } - *p = '/'; -#endif - rb_str_set_len(result, p - buf + strlen(p)); - encidx = ENCODING_GET(result); - tmp = result; - if (encidx != ENCINDEX_UTF_8 && rb_enc_str_coderange(result) != ENC_CODERANGE_7BIT) { - tmp = rb_str_encode_ospath(result); - } - len = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, NULL, 0); - wstr = ALLOCV_N(WCHAR, v, len); - MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, wstr, len); - if (tmp != result) rb_str_set_len(tmp, 0); - h = FindFirstFileW(wstr, &wfd); - ALLOCV_END(v); - if (h != INVALID_HANDLE_VALUE) { - size_t wlen; - FindClose(h); - len = lstrlenW(wfd.cFileName); + bufsize = cygwin_conv_path(flags, path, NULL, 0); + if (bufsize > 0) { + bufsize += len; + if (lnk_added) bufsize += 4; + w32buf = ALLOCA_N(char, bufsize); + if (cygwin_conv_path(flags, path, w32buf, bufsize) == 0) { + b = w32buf; + } + } +#else /* !HAVE_CYGWIN_CONV_PATH */ + bufsize = MAXPATHLEN; + if (cygwin_conv_to_win32_path(path, w32buf) == 0) { + b = w32buf; + } +#endif /* !HAVE_CYGWIN_CONV_PATH */ + if (is_symlink && b == w32buf) { + *p = '\\'; + strlcat(w32buf, p, bufsize); + if (lnk_added) { + strlcat(w32buf, ".lnk", bufsize); + } + } + else { + lnk_added = 0; + } + *p = '/'; +#endif /* __CYGWIN__ */ + rb_str_set_len(result, p - buf + strlen(p)); + encidx = ENCODING_GET(result); + tmp = result; + if (encidx != ENCINDEX_UTF_8 && !is_ascii_string(result)) { + tmp = rb_str_encode_ospath(result); + } + len = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, NULL, 0); + wstr = ALLOCV_N(WCHAR, v, len); + MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, wstr, len); + if (tmp != result) rb_str_set_len(tmp, 0); + h = FindFirstFileW(wstr, &wfd); + ALLOCV_END(v); + if (h != INVALID_HANDLE_VALUE) { + size_t wlen; + FindClose(h); + len = lstrlenW(wfd.cFileName); #ifdef __CYGWIN__ - if (lnk_added && len > 4 && - wcscasecmp(wfd.cFileName + len - 4, L".lnk") == 0) { - wfd.cFileName[len -= 4] = L'\0'; - } + if (lnk_added && len > 4 && + wcscasecmp(wfd.cFileName + len - 4, L".lnk") == 0) { + wfd.cFileName[len -= 4] = L'\0'; + } #else - p = (char *)s; -#endif - ++p; - wlen = (int)len; - len = WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, NULL, 0, NULL, NULL); - if (tmp == result) { - BUFCHECK(bdiff + len >= buflen); - WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, p, len + 1, NULL, NULL); - } - else { - rb_str_modify_expand(tmp, len); - WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, RSTRING_PTR(tmp), len + 1, NULL, NULL); - rb_str_cat_conv_enc_opts(result, bdiff, RSTRING_PTR(tmp), len, - rb_utf8_encoding(), 0, Qnil); - BUFINIT(); - rb_str_resize(tmp, 0); - } - p += len; - } + p = (char *)s; +#endif + ++p; + wlen = (int)len; + len = WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, NULL, 0, NULL, NULL); + if (tmp == result) { + BUFCHECK(bdiff + len >= buflen); + WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, p, len + 1, NULL, NULL); + } + else { + rb_str_modify_expand(tmp, len); + WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, RSTRING_PTR(tmp), len + 1, NULL, NULL); + rb_str_cat_conv_enc_opts(result, bdiff, RSTRING_PTR(tmp), len, + rb_utf8_encoding(), 0, Qnil); + BUFINIT(); + rb_str_resize(tmp, 0); + } + p += len; + } #ifdef __CYGWIN__ - else { - p += strlen(p); - } + else { + p += strlen(p); + } #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, MAXPATHLEN + 2) +#define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, 1) static VALUE str_shrink(VALUE str) @@ -4192,27 +4316,27 @@ enum rb_realpath_mode { static int realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE fallback, - VALUE loopcheck, enum rb_realpath_mode mode, int last) + VALUE loopcheck, enum rb_realpath_mode mode, int last) { const char *pend = unresolved + strlen(unresolved); rb_encoding *enc = rb_enc_get(*resolvedp); ID resolving; CONST_ID(resolving, "resolving"); while (unresolved < pend) { - const char *testname = unresolved; - const char *unresolved_firstsep = rb_enc_path_next(unresolved, pend, enc); - long testnamelen = unresolved_firstsep - unresolved; - const char *unresolved_nextname = unresolved_firstsep; + const char *testname = unresolved; + const char *unresolved_firstsep = rb_enc_path_next(unresolved, pend, enc); + long testnamelen = unresolved_firstsep - unresolved; + const char *unresolved_nextname = unresolved_firstsep; while (unresolved_nextname < pend && isdirsep(*unresolved_nextname)) - unresolved_nextname++; + unresolved_nextname++; unresolved = unresolved_nextname; if (testnamelen == 1 && testname[0] == '.') { } else if (testnamelen == 2 && testname[0] == '.' && testname[1] == '.') { 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 *resolved_str = RSTRING_PTR(*resolvedp); + const char *resolved_names = resolved_str + *prefixlenp; + const char *lastsep = strrdirsep(resolved_names, resolved_str + RSTRING_LEN(*resolvedp), enc); long len = lastsep ? lastsep - resolved_names : 0; rb_str_resize(*resolvedp, *prefixlenp + len); } @@ -4223,20 +4347,20 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f if (*prefixlenp < RSTRING_LEN(testpath)) rb_str_cat2(testpath, "/"); #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) - if (*prefixlenp > 1 && *prefixlenp == RSTRING_LEN(testpath)) { - const char *prefix = RSTRING_PTR(testpath); - const char *last = rb_enc_left_char_head(prefix, prefix + *prefixlenp - 1, prefix + *prefixlenp, enc); - if (!isdirsep(*last)) rb_str_cat2(testpath, "/"); - } + if (*prefixlenp > 1 && *prefixlenp == RSTRING_LEN(testpath)) { + const char *prefix = RSTRING_PTR(testpath); + const char *last = rb_enc_left_char_head(prefix, prefix + *prefixlenp - 1, prefix + *prefixlenp, enc); + if (!isdirsep(*last)) rb_str_cat2(testpath, "/"); + } #endif rb_str_cat(testpath, testname, testnamelen); checkval = rb_hash_aref(loopcheck, testpath); if (!NIL_P(checkval)) { if (checkval == ID2SYM(resolving)) { - if (mode == RB_REALPATH_CHECK) { - errno = ELOOP; - return -1; - } + if (mode == RB_REALPATH_CHECK) { + errno = ELOOP; + return -1; + } rb_syserr_fail_path(ELOOP, testpath); } else { @@ -4248,52 +4372,52 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f int ret; ret = lstat_without_gvl(RSTRING_PTR(testpath), &sbuf); if (ret == -1) { - int e = errno; - if (e == ENOENT && !NIL_P(fallback)) { - if (stat_without_gvl(RSTRING_PTR(fallback), &sbuf) == 0) { - rb_str_replace(*resolvedp, fallback); - return 0; - } - } - if (mode == RB_REALPATH_CHECK) return -1; - if (e == ENOENT) { - if (mode == RB_REALPATH_STRICT || !last || *unresolved_firstsep) - rb_syserr_fail_path(e, testpath); + int e = errno; + if (e == ENOENT && !NIL_P(fallback)) { + if (stat_without_gvl(RSTRING_PTR(fallback), &sbuf) == 0) { + rb_str_replace(*resolvedp, fallback); + return 0; + } + } + if (mode == RB_REALPATH_CHECK) return -1; + if (e == ENOENT) { + if (mode == RB_REALPATH_STRICT || !last || *unresolved_firstsep) + rb_syserr_fail_path(e, testpath); *resolvedp = testpath; break; } else { - rb_syserr_fail_path(e, testpath); + rb_syserr_fail_path(e, testpath); } } #ifdef HAVE_READLINK if (S_ISLNK(sbuf.st_mode)) { - VALUE link; - VALUE link_orig = Qnil; - const char *link_prefix, *link_names; + VALUE link; + VALUE link_orig = Qnil; + const char *link_prefix, *link_names; long link_prefixlen; rb_hash_aset(loopcheck, testpath, ID2SYM(resolving)); - link = rb_readlink(testpath, enc); + link = rb_readlink(testpath, enc); link_prefix = RSTRING_PTR(link); - link_names = skipprefixroot(link_prefix, link_prefix + RSTRING_LEN(link), rb_enc_get(link)); - link_prefixlen = link_names - link_prefix; - if (link_prefixlen > 0) { - rb_encoding *tmpenc, *linkenc = rb_enc_get(link); - link_orig = link; - link = rb_str_subseq(link, 0, link_prefixlen); - tmpenc = fs_enc_check(*resolvedp, link); - if (tmpenc != linkenc) link = rb_str_conv_enc(link, linkenc, tmpenc); - *resolvedp = link; - *prefixlenp = link_prefixlen; - } - if (realpath_rec(prefixlenp, resolvedp, link_names, testpath, - loopcheck, mode, !*unresolved_firstsep)) - return -1; - RB_GC_GUARD(link_orig); - rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp)); + link_names = skipprefixroot(link_prefix, link_prefix + RSTRING_LEN(link), rb_enc_get(link)); + link_prefixlen = link_names - link_prefix; + if (link_prefixlen > 0) { + rb_encoding *tmpenc, *linkenc = rb_enc_get(link); + link_orig = link; + link = rb_str_subseq(link, 0, link_prefixlen); + tmpenc = fs_enc_check(*resolvedp, link); + if (tmpenc != linkenc) link = rb_str_conv_enc(link, linkenc, tmpenc); + *resolvedp = link; + *prefixlenp = link_prefixlen; + } + if (realpath_rec(prefixlenp, resolvedp, link_names, testpath, + loopcheck, mode, !*unresolved_firstsep)) + return -1; + RB_GC_GUARD(link_orig); + 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); @@ -4336,11 +4460,11 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum } if (!NIL_P(basedir)) { - RSTRING_GETMEM(basedir, ptr, len); - basedir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(basedir)); + RSTRING_GETMEM(basedir, ptr, len); + basedir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(basedir)); if (ptr != basedir_names) { - resolved = rb_str_subseq(basedir, 0, basedir_names - ptr); - goto root_found; + resolved = rb_str_subseq(basedir, 0, basedir_names - ptr); + goto root_found; } } @@ -4359,38 +4483,38 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum } #ifdef FILE_ALT_SEPARATOR while (prefixptr < ptr) { - if (*prefixptr == FILE_ALT_SEPARATOR) { - *prefixptr = '/'; - } - Inc(prefixptr, pend, enc); + if (*prefixptr == FILE_ALT_SEPARATOR) { + *prefixptr = '/'; + } + Inc(prefixptr, pend, enc); } #endif switch (rb_enc_to_index(enc)) { - case ENCINDEX_ASCII: + case ENCINDEX_ASCII_8BIT: case ENCINDEX_US_ASCII: - rb_enc_associate_index(resolved, rb_filesystem_encindex()); + rb_enc_associate_index(resolved, rb_filesystem_encindex()); } loopcheck = rb_hash_new(); if (curdir_names) { - if (realpath_rec(&prefixlen, &resolved, curdir_names, Qnil, loopcheck, mode, 0)) - return Qnil; + if (realpath_rec(&prefixlen, &resolved, curdir_names, Qnil, loopcheck, mode, 0)) + return Qnil; } if (basedir_names) { - if (realpath_rec(&prefixlen, &resolved, basedir_names, Qnil, loopcheck, mode, 0)) - return Qnil; + if (realpath_rec(&prefixlen, &resolved, basedir_names, Qnil, loopcheck, mode, 0)) + return Qnil; } if (realpath_rec(&prefixlen, &resolved, path_names, Qnil, loopcheck, mode, 1)) - return Qnil; + return Qnil; if (origenc && origenc != rb_enc_get(resolved)) { - if (rb_enc_str_asciionly_p(resolved)) { - rb_enc_associate(resolved, origenc); - } - else { - resolved = rb_str_conv_enc(resolved, NULL, origenc); - } + if (rb_enc_str_asciionly_p(resolved)) { + rb_enc_associate(resolved, origenc); + } + else { + resolved = rb_str_conv_enc(resolved, NULL, origenc); + } } RB_GC_GUARD(unresolved_path); @@ -4400,6 +4524,21 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum static VALUE rb_file_join(VALUE ary); +#ifndef HAVE_REALPATH +static VALUE +rb_check_realpath_emulate_try(VALUE arg) +{ + VALUE *args = (VALUE *)arg; + return rb_check_realpath_emulate(args[0], args[1], (rb_encoding *)args[2], RB_REALPATH_CHECK); +} + +static VALUE +rb_check_realpath_emulate_rescue(VALUE arg, VALUE exc) +{ + return Qnil; +} +#endif /* HAVE_REALPATH */ + static VALUE rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode) { @@ -4447,7 +4586,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)) { @@ -4456,17 +4595,28 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_enc_associate(resolved, origenc); } - if (rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) { + if (is_broken_string(resolved)) { rb_enc_associate(resolved, rb_filesystem_encoding()); - if (rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) { + if (is_broken_string(resolved)) { rb_enc_associate(resolved, rb_ascii8bit_encoding()); } } RB_GC_GUARD(unresolved_path); return resolved; -#else - return rb_check_realpath_emulate(basedir, path, origenc, mode); +#else /* !HAVE_REALPATH */ + if (mode == RB_REALPATH_CHECK) { + VALUE arg[3]; + arg[0] = basedir; + arg[1] = path; + arg[2] = (VALUE)origenc; + + return rb_rescue(rb_check_realpath_emulate_try, (VALUE)arg, + rb_check_realpath_emulate_rescue, Qnil); + } + else { + return rb_check_realpath_emulate(basedir, path, origenc, mode); + } #endif /* HAVE_REALPATH */ } @@ -4474,7 +4624,7 @@ VALUE rb_realpath_internal(VALUE basedir, VALUE path, int strict) { const enum rb_realpath_mode mode = - strict ? RB_REALPATH_STRICT : RB_REALPATH_DIR; + strict ? RB_REALPATH_STRICT : RB_REALPATH_DIR; return rb_check_realpath_internal(basedir, path, rb_enc_get(path), mode); } @@ -4538,27 +4688,27 @@ rmext(const char *p, long l0, long l1, const char *e, long l2, rb_encoding *enc) c = rb_enc_codepoint_len(e, e + l2, &len1, enc); if (rb_enc_ascget(e + len1, e + l2, &len2, enc) == '*' && len1 + len2 == l2) { - if (c == '.') return l0; - s = p; - e = p + l1; - last = e; - while (s < e) { - if (rb_enc_codepoint_len(s, e, &len1, enc) == c) last = s; - s += len1; - } - return last - p; + if (c == '.') return l0; + s = p; + e = p + l1; + last = e; + while (s < e) { + if (rb_enc_codepoint_len(s, e, &len1, enc) == c) last = s; + s += len1; + } + return last - p; } 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 #define fncomp strncmp #endif if (fncomp(s, e, l2) == 0) { - return l1-l2; + return l1-l2; } return 0; } @@ -4578,51 +4728,51 @@ ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encodin root = name; #endif while (isdirsep(*name)) - name++; + name++; if (!*name) { - p = name - 1; - f = 1; + p = name - 1; + f = 1; #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC - if (name != root) { - /* has slashes */ - } + if (name != root) { + /* has slashes */ + } #ifdef DOSISH_DRIVE_LETTER - else if (*p == ':') { - p++; - f = 0; - } -#endif + else if (*p == ':') { + p++; + f = 0; + } +#endif /* DOSISH_DRIVE_LETTER */ #ifdef DOSISH_UNC - else { - p = "/"; - } -#endif -#endif + else { + p = "/"; + } +#endif /* DOSISH_UNC */ +#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */ } else { - if (!(p = strrdirsep(name, end, enc))) { - p = name; - } - else { - while (isdirsep(*p)) p++; /* skip last / */ - } + if (!(p = strrdirsep(name, end, enc))) { + p = name; + } + else { + while (isdirsep(*p)) p++; /* skip last / */ + } #if USE_NTFS - n = ntfs_tail(p, end, enc) - p; + n = ntfs_tail(p, end, enc) - p; #else - n = chompdirsep(p, end, enc) - p; + n = chompdirsep(p, end, enc) - p; #endif - for (q = p; q - p < n && *q == '.'; q++); - for (e = 0; q - p < n; Inc(q, end, enc)) { - if (*q == '.') e = q; - } - if (e) f = e - p; - else f = n; + for (q = p; q - p < n && *q == '.'; q++); + for (e = 0; q - p < n; Inc(q, end, enc)) { + if (*q == '.') e = q; + } + if (e) f = e - p; + else f = n; } if (baselen) - *baselen = f; + *baselen = f; if (alllen) - *alllen = n; + *alllen = n; return p; } @@ -4653,33 +4803,33 @@ rb_file_s_basename(int argc, VALUE *argv, VALUE _) fext = Qnil; if (rb_check_arity(argc, 1, 2) == 2) { - fext = argv[1]; - StringValue(fext); - enc = check_path_encoding(fext); + fext = argv[1]; + StringValue(fext); + enc = check_path_encoding(fext); } fname = argv[0]; FilePathStringValue(fname); if (NIL_P(fext) || !(enc = rb_enc_compatible(fname, fext))) { - enc = rb_enc_get(fname); - fext = Qnil; + enc = rb_enc_get(fname); + fext = Qnil; } if ((n = RSTRING_LEN(fname)) == 0 || !*(name = RSTRING_PTR(fname))) - return rb_str_new_shared(fname); + return rb_str_new_shared(fname); p = ruby_enc_find_basename(name, &f, &n, enc); if (n >= 0) { - if (NIL_P(fext)) { - f = n; - } - else { - const char *fp; - fp = StringValueCStr(fext); - if (!(f = rmext(p, f, n, fp, RSTRING_LEN(fext), enc))) { - f = n; - } - RB_GC_GUARD(fext); - } - if (f == RSTRING_LEN(fname)) return rb_str_new_shared(fname); + if (NIL_P(fext)) { + f = n; + } + else { + const char *fp; + fp = StringValueCStr(fext); + if (!(f = rmext(p, f, n, fp, RSTRING_LEN(fext), enc))) { + f = n; + } + RB_GC_GUARD(fext); + } + if (f == RSTRING_LEN(fname)) return rb_str_new_shared(fname); } basename = rb_str_new(p, f); @@ -4713,7 +4863,7 @@ rb_file_s_dirname(int argc, VALUE *argv, VALUE klass) { int n = 1; if ((argc = rb_check_arity(argc, 1, 2)) > 1) { - n = NUM2INT(argv[1]); + n = NUM2INT(argv[1]); } return rb_file_dirname_n(argv[0], n); } @@ -4741,58 +4891,58 @@ rb_file_dirname_n(VALUE fname, int n) root = skiproot(name, end, enc); #ifdef DOSISH_UNC if (root > name + 1 && isdirsep(*name)) - root = skipprefix(name = root - 2, end, enc); + root = skipprefix(name = root - 2, end, enc); #else if (root > name + 1) - name = root - 1; + name = root - 1; #endif if (n > (end - root + 1) / 2) { - p = root; + 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 = seps[i]; - ALLOCV_END(sepsv); - break; - } + 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 = seps[i]; + ALLOCV_END(sepsv); + break; + } } if (p == name) - return rb_usascii_str_new2("."); + return rb_usascii_str_new2("."); #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); - rb_str_cat(dirname, top, p - top); + const char *top = skiproot(name + 2, end, enc); + dirname = rb_str_new(name, 3); + rb_str_cat(dirname, top, p - top); } else #endif dirname = rb_str_new(name, p - name); #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && root == name + 2 && p - name == 2) - rb_str_cat(dirname, ".", 1); + rb_str_cat(dirname, ".", 1); #endif rb_enc_copy(dirname, fname); return dirname; @@ -4807,7 +4957,7 @@ rb_file_dirname_n(VALUE fname, int n) * dotfile top 0 * end with dot dot 1 * .ext dot len of .ext - * .ext:stream dot len of .ext without :stream (NT only) + * .ext:stream dot len of .ext without :stream (NTFS only) * */ const char * @@ -4817,48 +4967,48 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) p = strrdirsep(name, end, enc); /* get the last path component */ if (!p) - p = name; + p = name; else - do name = ++p; while (isdirsep(*p)); + do name = ++p; while (isdirsep(*p)); e = 0; while (*p && *p == '.') p++; while (*p) { - if (*p == '.' || istrailinggarbage(*p)) { + if (*p == '.' || istrailinggarbage(*p)) { #if USE_NTFS - const char *last = p++, *dot = last; - while (istrailinggarbage(*p)) { - if (*p == '.') dot = p; - p++; - } - if (!*p || isADS(*p)) { - p = last; - break; - } - if (*last == '.' || dot > last) e = dot; - continue; + const char *last = p++, *dot = last; + while (istrailinggarbage(*p)) { + if (*p == '.') dot = p; + p++; + } + if (!*p || isADS(*p)) { + p = last; + break; + } + if (*last == '.' || dot > last) e = dot; + continue; #else - e = p; /* get the last dot of the last component */ -#endif - } + e = p; /* get the last dot of the last component */ +#endif /* USE_NTFS */ + } #if USE_NTFS - else if (isADS(*p)) { - break; - } + else if (isADS(*p)) { + break; + } #endif - else if (isdirsep(*p)) - break; - Inc(p, end, enc); + else if (isdirsep(*p)) + break; + Inc(p, end, enc); } if (len) { - /* no dot, or the only dot is first or end? */ - if (!e || e == name) - *len = 0; - else if (e+1 == p) - *len = 1; - else - *len = p - e; + /* no dot, or the only dot is first or end? */ + if (!e || e == name) + *len = 0; + else if (e+1 == p) + *len = 1; + else + *len = p - e; } return e; } @@ -4901,7 +5051,7 @@ rb_file_s_extname(VALUE klass, VALUE 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); + return rb_str_new(0, 0); extname = rb_str_subseq(fname, e - name, len); /* keep the dot, too! */ return extname; } @@ -4912,7 +5062,7 @@ 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" * */ @@ -4961,53 +5111,53 @@ rb_file_join(VALUE ary) len = 1; for (i=0; i<RARRAY_LEN(ary); i++) { - tmp = RARRAY_AREF(ary, i); - if (RB_TYPE_P(tmp, T_STRING)) { - check_path_encoding(tmp); - len += RSTRING_LEN(tmp); - } - else { - len += 10; - } + tmp = RARRAY_AREF(ary, i); + if (RB_TYPE_P(tmp, T_STRING)) { + check_path_encoding(tmp); + len += RSTRING_LEN(tmp); + } + else { + len += 10; + } } len += RARRAY_LEN(ary) - 1; result = rb_str_buf_new(len); RBASIC_CLEAR_CLASS(result); for (i=0; i<RARRAY_LEN(ary); i++) { - tmp = RARRAY_AREF(ary, i); - switch (OBJ_BUILTIN_TYPE(tmp)) { - case T_STRING: - if (!checked) check_path_encoding(tmp); - StringValueCStr(tmp); - break; - case T_ARRAY: - if (ary == tmp) { - rb_raise(rb_eArgError, "recursive array"); - } - else { - tmp = rb_exec_recursive(file_inspect_join, ary, tmp); - } - break; - default: - FilePathStringValue(tmp); - checked = FALSE; - } - RSTRING_GETMEM(result, name, len); - if (i == 0) { - rb_enc_copy(result, tmp); - } - else { - tail = chompdirsep(name, name + len, rb_enc_get(result)); - if (RSTRING_PTR(tmp) && isdirsep(RSTRING_PTR(tmp)[0])) { - rb_str_set_len(result, tail - name); - } - else if (!*tail) { - rb_str_cat(result, "/", 1); - } - } - enc = fs_enc_check(result, tmp); - rb_str_buf_append(result, tmp); - rb_enc_associate(result, enc); + tmp = RARRAY_AREF(ary, i); + switch (OBJ_BUILTIN_TYPE(tmp)) { + case T_STRING: + if (!checked) check_path_encoding(tmp); + StringValueCStr(tmp); + break; + case T_ARRAY: + if (ary == tmp) { + rb_raise(rb_eArgError, "recursive array"); + } + else { + tmp = rb_exec_recursive(file_inspect_join, ary, tmp); + } + break; + default: + FilePathStringValue(tmp); + checked = FALSE; + } + RSTRING_GETMEM(result, name, len); + if (i == 0) { + rb_enc_copy(result, tmp); + } + else { + tail = chompdirsep(name, name + len, rb_enc_get(result)); + if (RSTRING_PTR(tmp) && isdirsep(RSTRING_PTR(tmp)[0])) { + rb_str_set_len(result, tail - name); + } + else if (!*tail) { + rb_str_cat(result, "/", 1); + } + } + enc = fs_enc_check(result, tmp); + rb_str_buf_append(result, tmp); + rb_enc_associate(result, enc); } RBASIC_SET_CLASS_RAW(result, rb_cString); @@ -5031,41 +5181,17 @@ rb_file_s_join(VALUE klass, VALUE args) return rb_file_join(args); } -#if defined(HAVE_TRUNCATE) || defined(HAVE_CHSIZE) +#if defined(HAVE_TRUNCATE) struct truncate_arg { const char *path; -#if defined(HAVE_TRUNCATE) -#define NUM2POS(n) NUM2OFFT(n) - off_t pos; -#else -#define NUM2POS(n) NUM2LONG(n) - long pos; -#endif + rb_off_t pos; }; static void * nogvl_truncate(void *ptr) { struct truncate_arg *ta = ptr; -#ifdef HAVE_TRUNCATE return (void *)(VALUE)truncate(ta->path, ta->pos); -#else /* defined(HAVE_CHSIZE) */ - { - int tmpfd = rb_cloexec_open(ta->path, 0, 0); - - if (tmpfd < 0) - return (void *)-1; - rb_update_max_fd(tmpfd); - if (chsize(tmpfd, ta->pos) < 0) { - int e = errno; - close(tmpfd); - errno = e; - return (void *)-1; - } - close(tmpfd); - return 0; - } -#endif } /* @@ -5089,32 +5215,24 @@ rb_file_s_truncate(VALUE klass, VALUE path, VALUE len) struct truncate_arg ta; int r; - ta.pos = NUM2POS(len); + ta.pos = NUM2OFFT(len); FilePathValue(path); 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); + rb_sys_fail_path(path); return INT2FIX(0); -#undef NUM2POS } #else #define rb_file_s_truncate rb_f_notimplement #endif -#if defined(HAVE_FTRUNCATE) || defined(HAVE_CHSIZE) +#if defined(HAVE_FTRUNCATE) struct ftruncate_arg { int fd; -#if defined(HAVE_FTRUNCATE) -#define NUM2POS(n) NUM2OFFT(n) - off_t pos; -#else -#define NUM2POS(n) NUM2LONG(n) - long pos; -#endif + rb_off_t pos; }; static VALUE @@ -5122,11 +5240,7 @@ nogvl_ftruncate(void *ptr) { struct ftruncate_arg *fa = ptr; -#ifdef HAVE_FTRUNCATE return (VALUE)ftruncate(fa->fd, fa->pos); -#else /* defined(HAVE_CHSIZE) */ - return (VALUE)chsize(fa->fd, fa->pos); -#endif } /* @@ -5149,18 +5263,17 @@ rb_file_truncate(VALUE obj, VALUE len) rb_io_t *fptr; struct ftruncate_arg fa; - fa.pos = NUM2POS(len); + fa.pos = NUM2OFFT(len); GetOpenFile(obj, fptr); if (!(fptr->mode & FMODE_WRITABLE)) { - rb_raise(rb_eIOError, "not opened for writing"); + rb_raise(rb_eIOError, "not opened for writing"); } rb_io_flush_raw(obj, 0); fa.fd = fptr->fd; if ((int)rb_thread_io_blocking_region(nogvl_ftruncate, &fa, fa.fd) < 0) { - rb_sys_fail_path(fptr->pathv); + rb_sys_fail_path(fptr->pathv); } return INT2FIX(0); -#undef NUM2POS } #else #define rb_file_truncate rb_f_notimplement @@ -5193,54 +5306,57 @@ rb_thread_flock(void *data) #ifdef __CYGWIN__ if (GetLastError() == ERROR_NOT_LOCKED) { - ret = 0; - errno = old_errno; + ret = 0; + errno = old_errno; } #endif 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`. + * + * <br> + * + * | 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. + * + * <br> * * 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 + * ``` * */ @@ -5256,33 +5372,33 @@ rb_file_flock(VALUE obj, VALUE operation) op[0] = fptr->fd; if (fptr->mode & FMODE_WRITABLE) { - rb_io_flush_raw(obj, 0); + rb_io_flush_raw(obj, 0); } while ((int)rb_thread_io_blocking_region(rb_thread_flock, op, fptr->fd) < 0) { - int e = errno; - switch (e) { - case EAGAIN: - case EACCES: + int e = errno; + switch (e) { + case EAGAIN: + case EACCES: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN - case EWOULDBLOCK: + case EWOULDBLOCK: #endif - if (op1 & LOCK_NB) return Qfalse; + if (op1 & LOCK_NB) return Qfalse; - time.tv_sec = 0; - time.tv_usec = 100 * 1000; /* 0.1 sec */ - rb_thread_wait_for(time); - rb_io_check_closed(fptr); - continue; + time.tv_sec = 0; + time.tv_usec = 100 * 1000; /* 0.1 sec */ + rb_thread_wait_for(time); + rb_io_check_closed(fptr); + continue; - case EINTR: + case EINTR: #if defined(ERESTART) - case ERESTART: + case ERESTART: #endif - break; + break; - default: - rb_syserr_fail_path(e, fptr->pathv); - } + default: + rb_syserr_fail_path(e, fptr->pathv); + } } return INT2FIX(0); } @@ -5295,70 +5411,92 @@ test_check(int n, int argc, VALUE *argv) n+=1; rb_check_arity(argc, n, n); for (i=1; i<n; i++) { - if (!RB_TYPE_P(argv[i], T_FILE)) { - FilePathValue(argv[i]); - } + if (!RB_TYPE_P(argv[i], T_FILE)) { + FilePathValue(argv[i]); + } } } #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 \CF{setgid} bit - * | | set (false under NT) - * "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 effecive 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 effecive 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 @@ -5372,128 +5510,128 @@ rb_f_test(int argc, VALUE *argv, VALUE _) goto unknown; } if (strchr("bcdefgGkloOprRsSuwWxXz", cmd)) { - CHECK(1); - switch (cmd) { - case 'b': - return rb_file_blockdev_p(0, argv[1]); + CHECK(1); + switch (cmd) { + case 'b': + return rb_file_blockdev_p(0, argv[1]); - case 'c': - return rb_file_chardev_p(0, argv[1]); + case 'c': + return rb_file_chardev_p(0, argv[1]); - case 'd': - return rb_file_directory_p(0, argv[1]); + case 'd': + return rb_file_directory_p(0, argv[1]); - case 'e': - return rb_file_exist_p(0, argv[1]); + case 'e': + return rb_file_exist_p(0, argv[1]); - case 'f': - return rb_file_file_p(0, argv[1]); + case 'f': + return rb_file_file_p(0, argv[1]); - case 'g': - return rb_file_sgid_p(0, argv[1]); + case 'g': + return rb_file_sgid_p(0, argv[1]); - case 'G': - return rb_file_grpowned_p(0, argv[1]); + case 'G': + return rb_file_grpowned_p(0, argv[1]); - case 'k': - return rb_file_sticky_p(0, argv[1]); + case 'k': + return rb_file_sticky_p(0, argv[1]); - case 'l': - return rb_file_symlink_p(0, argv[1]); + case 'l': + return rb_file_symlink_p(0, argv[1]); - case 'o': - return rb_file_owned_p(0, argv[1]); + case 'o': + return rb_file_owned_p(0, argv[1]); - case 'O': - return rb_file_rowned_p(0, argv[1]); + case 'O': + return rb_file_rowned_p(0, argv[1]); - case 'p': - return rb_file_pipe_p(0, argv[1]); + case 'p': + return rb_file_pipe_p(0, argv[1]); - case 'r': - return rb_file_readable_p(0, argv[1]); + case 'r': + return rb_file_readable_p(0, argv[1]); - case 'R': - return rb_file_readable_real_p(0, argv[1]); + case 'R': + return rb_file_readable_real_p(0, argv[1]); - case 's': - return rb_file_size_p(0, argv[1]); + case 's': + return rb_file_size_p(0, argv[1]); - case 'S': - return rb_file_socket_p(0, argv[1]); + case 'S': + return rb_file_socket_p(0, argv[1]); - case 'u': - return rb_file_suid_p(0, argv[1]); + case 'u': + return rb_file_suid_p(0, argv[1]); - case 'w': - return rb_file_writable_p(0, argv[1]); + case 'w': + return rb_file_writable_p(0, argv[1]); - case 'W': - return rb_file_writable_real_p(0, argv[1]); + case 'W': + return rb_file_writable_real_p(0, argv[1]); - case 'x': - return rb_file_executable_p(0, argv[1]); + case 'x': + return rb_file_executable_p(0, argv[1]); - case 'X': - return rb_file_executable_real_p(0, argv[1]); + case 'X': + return rb_file_executable_real_p(0, argv[1]); - case 'z': - return rb_file_zero_p(0, argv[1]); - } + case 'z': + return rb_file_zero_p(0, argv[1]); + } } if (strchr("MAC", cmd)) { - struct stat st; - VALUE fname = argv[1]; - - CHECK(1); - if (rb_stat(fname, &st) == -1) { - int e = errno; - FilePathValue(fname); - rb_syserr_fail_path(e, fname); - } - - switch (cmd) { - case 'A': - return stat_atime(&st); - case 'M': - return stat_mtime(&st); - case 'C': - return stat_ctime(&st); - } + struct stat st; + VALUE fname = argv[1]; + + CHECK(1); + if (rb_stat(fname, &st) == -1) { + int e = errno; + FilePathValue(fname); + rb_syserr_fail_path(e, fname); + } + + switch (cmd) { + case 'A': + return stat_atime(&st); + case 'M': + return stat_mtime(&st); + case 'C': + return stat_ctime(&st); + } } if (cmd == '-') { - CHECK(2); - return rb_file_identical_p(0, argv[1], argv[2]); + CHECK(2); + return rb_file_identical_p(0, argv[1], argv[2]); } if (strchr("=<>", cmd)) { - struct stat st1, st2; + struct stat st1, st2; struct timespec t1, t2; - CHECK(2); - if (rb_stat(argv[1], &st1) < 0) return Qfalse; - if (rb_stat(argv[2], &st2) < 0) return Qfalse; + CHECK(2); + if (rb_stat(argv[1], &st1) < 0) return Qfalse; + if (rb_stat(argv[2], &st2) < 0) return Qfalse; t1 = stat_mtimespec(&st1); t2 = stat_mtimespec(&st2); - switch (cmd) { - case '=': - if (t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec) return Qtrue; - return Qfalse; + switch (cmd) { + case '=': + if (t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec) return Qtrue; + return Qfalse; - case '>': - if (t1.tv_sec > t2.tv_sec) return Qtrue; - if (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec) return Qtrue; - return Qfalse; + case '>': + if (t1.tv_sec > t2.tv_sec) return Qtrue; + if (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec) return Qtrue; + return Qfalse; - case '<': - if (t1.tv_sec < t2.tv_sec) return Qtrue; - if (t1.tv_sec == t2.tv_sec && t1.tv_nsec < t2.tv_nsec) return Qtrue; - return Qfalse; - } + case '<': + if (t1.tv_sec < t2.tv_sec) return Qtrue; + if (t1.tv_sec == t2.tv_sec && t1.tv_nsec < t2.tv_nsec) return Qtrue; + return Qfalse; + } } unknown: /* unknown command */ @@ -5537,20 +5675,19 @@ rb_stat_s_alloc(VALUE klass) static VALUE rb_stat_init(VALUE obj, VALUE fname) { - struct stat st, *nst; + struct stat st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); if (STAT(StringValueCStr(fname), &st) == -1) { - rb_sys_fail_path(fname); + 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; } @@ -5559,19 +5696,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; } @@ -5752,7 +5885,7 @@ rb_stat_rowned(VALUE obj) * stat.grpowned? -> true or false * * Returns true if the effective group id of the process is the same as - * the group id of <i>stat</i>. On Windows NT, returns <code>false</code>. + * the group id of <i>stat</i>. On Windows, returns <code>false</code>. * * File.stat("testfile").grpowned? #=> true * File.stat("/etc/passwd").grpowned? #=> false @@ -5789,11 +5922,11 @@ rb_stat_r(VALUE obj) #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; @@ -5822,11 +5955,11 @@ rb_stat_R(VALUE obj) #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); + return RBOOL(st->st_mode & S_IRGRP); #endif #ifdef S_IROTH if (!(st->st_mode & S_IROTH)) return Qfalse; @@ -5853,12 +5986,10 @@ 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; + return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif + return Qnil; } /* @@ -5882,11 +6013,11 @@ rb_stat_w(VALUE obj) #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; @@ -5915,11 +6046,11 @@ rb_stat_W(VALUE obj) #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); + return RBOOL(st->st_mode & S_IWGRP); #endif #ifdef S_IWOTH if (!(st->st_mode & S_IWOTH)) return Qfalse; @@ -5943,15 +6074,13 @@ rb_stat_W(VALUE obj) static VALUE rb_stat_ww(VALUE obj) { -#ifdef S_IROTH +#ifdef S_IWOTH 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; + return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif + return Qnil; } /* @@ -5974,16 +6103,16 @@ rb_stat_x(VALUE 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; @@ -6006,16 +6135,16 @@ rb_stat_X(VALUE 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); + return RBOOL(st->st_mode & S_IXGRP); #endif #ifdef S_IXOTH if (!(st->st_mode & S_IXOTH)) return Qfalse; @@ -6067,14 +6196,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) { - 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); @@ -6180,14 +6309,14 @@ rb_file_s_mkfifo(int argc, VALUE *argv, VALUE _) ma.mode = 0666; rb_check_arity(argc, 1, 2); if (argc > 1) { - ma.mode = NUM2MODET(argv[1]); + ma.mode = NUM2MODET(argv[1]); } path = argv[0]; 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)) { - rb_sys_fail_path(path); + if (IO_WITHOUT_GVL(nogvl_mkfifo, &ma)) { + rb_sys_fail_path(path); } return INT2FIX(0); } @@ -6237,16 +6366,16 @@ path_check_0(VALUE path) char *p = 0, *s; if (!rb_is_absolute_path(p0)) { - char *buf = ruby_getcwd(); - VALUE newpath; + char *buf = ruby_getcwd(); + VALUE newpath; - newpath = rb_str_new2(buf); - xfree(buf); + newpath = rb_str_new2(buf); + xfree(buf); - rb_str_cat2(newpath, "/"); - rb_str_cat2(newpath, p0); - path = newpath; - p0 = RSTRING_PTR(path); + 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); @@ -6254,28 +6383,28 @@ path_check_0(VALUE path) #ifndef S_IWOTH # define S_IWOTH 002 #endif - if (STAT(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) + if (STAT(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) #ifdef S_ISVTX - && !(p && (st.st_mode & 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%" + && !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", + PRI_MODET_PREFIX"o", #else - "o", + "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'; + 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 @@ -6295,13 +6424,13 @@ rb_path_check(const char *path) 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; + 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; @@ -6316,18 +6445,18 @@ ruby_is_fd_loadable(int fd) struct stat st; if (fstat(fd, &st) < 0) - return 0; + return 0; if (S_ISREG(st.st_mode)) - return 1; + return 1; if (S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode)) - return -1; + return -1; if (S_ISDIR(st.st_mode)) - errno = EISDIR; + errno = EISDIR; else - errno = ENXIO; + errno = ENXIO; return 0; #endif @@ -6344,13 +6473,17 @@ rb_file_load_ok(const char *path) */ int mode = (O_RDONLY | #if defined O_NONBLOCK - O_NONBLOCK | + O_NONBLOCK | #elif defined O_NDELAY - O_NDELAY | + O_NDELAY | #endif - 0); + 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); @@ -6369,6 +6502,10 @@ is_explicit_relative(const char *path) static VALUE copy_path_class(VALUE path, VALUE orig) { + int encidx = rb_enc_get_index(orig); + if (encidx == ENCINDEX_ASCII_8BIT || encidx == ENCINDEX_US_ASCII) + encidx = rb_filesystem_encindex(); + rb_enc_associate_index(path, encidx); str_shrink(path); RBASIC_SET_CLASS(path, rb_obj_class(orig)); OBJ_FREEZE(path); @@ -6386,24 +6523,24 @@ rb_find_file_ext(VALUE *filep, const char *const *ext) if (!ext[0]) return 0; if (f[0] == '~') { - fname = file_expand_path_1(fname); - f = RSTRING_PTR(fname); - *filep = fname; - expanded = 1; + fname = file_expand_path_1(fname); + f = RSTRING_PTR(fname); + *filep = fname; + expanded = 1; } if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) { - if (!expanded) fname = file_expand_path_1(fname); - fnlen = RSTRING_LEN(fname); - for (i=0; ext[i]; i++) { - rb_str_cat2(fname, ext[i]); - if (rb_file_load_ok(RSTRING_PTR(fname))) { - *filep = copy_path_class(fname, *filep); - return (int)(i+1); - } - rb_str_set_len(fname, fnlen); - } - return 0; + if (!expanded) fname = file_expand_path_1(fname); + fnlen = RSTRING_LEN(fname); + for (i=0; ext[i]; i++) { + rb_str_cat2(fname, ext[i]); + if (rb_file_load_ok(RSTRING_PTR(fname))) { + *filep = copy_path_class(fname, *filep); + return (int)(i+1); + } + rb_str_set_len(fname, fnlen); + } + return 0; } RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); @@ -6415,19 +6552,19 @@ rb_find_file_ext(VALUE *filep, const char *const *ext) tmp = rb_str_tmp_new(MAXPATHLEN + 2); rb_enc_associate_index(tmp, rb_usascii_encindex()); for (j=0; ext[j]; j++) { - rb_str_cat2(fname, ext[j]); - for (i = 0; i < RARRAY_LEN(load_path); i++) { - VALUE str = RARRAY_AREF(load_path, i); + rb_str_cat2(fname, ext[j]); + for (i = 0; i < RARRAY_LEN(load_path); i++) { + VALUE str = RARRAY_AREF(load_path, i); RB_GC_GUARD(str) = rb_get_path(str); - if (RSTRING_LEN(str) == 0) continue; - rb_file_expand_path_internal(fname, str, 0, 0, tmp); - if (rb_file_load_ok(RSTRING_PTR(tmp))) { - *filep = copy_path_class(tmp, *filep); - return (int)(j+1); - } - } - rb_str_set_len(fname, fnlen); + if (RSTRING_LEN(str) == 0) continue; + rb_file_expand_path_internal(fname, str, 0, 0, tmp); + if (rb_file_load_ok(RSTRING_PTR(tmp))) { + *filep = copy_path_class(tmp, *filep); + return (int)(j+1); + } + } + rb_str_set_len(fname, fnlen); } rb_str_resize(tmp, 0); RB_GC_GUARD(load_path); @@ -6442,51 +6579,49 @@ rb_find_file(VALUE path) int expanded = 0; if (f[0] == '~') { - tmp = file_expand_path_1(path); - path = copy_path_class(tmp, path); - f = RSTRING_PTR(path); - expanded = 1; + tmp = file_expand_path_1(path); + path = copy_path_class(tmp, path); + f = RSTRING_PTR(path); + expanded = 1; } if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) { - if (!rb_file_load_ok(f)) return 0; - if (!expanded) - path = copy_path_class(file_expand_path_1(path), path); - return path; + if (!rb_file_load_ok(f)) return 0; + if (!expanded) + path = copy_path_class(file_expand_path_1(path), path); + return path; } RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); if (load_path) { - long i; + long i; - tmp = rb_str_tmp_new(MAXPATHLEN + 2); - rb_enc_associate_index(tmp, rb_usascii_encindex()); - for (i = 0; i < RARRAY_LEN(load_path); i++) { - VALUE str = RARRAY_AREF(load_path, i); + tmp = rb_str_tmp_new(MAXPATHLEN + 2); + rb_enc_associate_index(tmp, rb_usascii_encindex()); + for (i = 0; i < RARRAY_LEN(load_path); i++) { + VALUE str = RARRAY_AREF(load_path, i); RB_GC_GUARD(str) = rb_get_path(str); - if (RSTRING_LEN(str) > 0) { - rb_file_expand_path_internal(path, str, 0, 0, tmp); - f = RSTRING_PTR(tmp); - if (rb_file_load_ok(f)) goto found; - } - } - rb_str_resize(tmp, 0); - return 0; + if (RSTRING_LEN(str) > 0) { + rb_file_expand_path_internal(path, str, 0, 0, tmp); + f = RSTRING_PTR(tmp); + if (rb_file_load_ok(f)) goto found; + } + } + rb_str_resize(tmp, 0); + return 0; } else { - return 0; /* no path, no load */ + return 0; /* no path, no load */ } found: return copy_path_class(tmp, path); } -static void -define_filetest_function(const char *name, VALUE (*func)(ANYARGS), int argc) -{ - rb_define_module_function(rb_mFileTest, name, func, argc); - rb_define_singleton_method(rb_cFile, name, func, argc); -} +#define define_filetest_function(name, func, argc) do { \ + rb_define_module_function(rb_mFileTest, name, func, argc); \ + rb_define_singleton_method(rb_cFile, name, func, argc); \ +} while(false) const char ruby_null_device[] = #if defined DOSISH @@ -6501,174 +6636,804 @@ const char ruby_null_device[] = ; /* - * A File is an abstraction of any file object accessible by the - * program and is closely associated with class IO. File includes - * the methods of module FileTest as class methods, allowing you to - * write (for example) <code>File.exist?("foo")</code>. - * - * In the description of File methods, - * <em>permission bits</em> are a platform-specific - * set of bits that indicate permissions of a file. On Unix-based - * systems, permissions are viewed as a set of three octets, for the - * owner, the group, and the rest of the world. For each of these - * entities, permissions may be set to read, write, or execute the - * file: - * - * The permission bits <code>0644</code> (in octal) would thus be - * interpreted as read/write for owner, and read-only for group and - * other. Higher-order bits may also be used to indicate the type of - * file (plain, directory, pipe, socket, and so on) and various other - * special features. If the permissions are for a directory, the - * meaning of the execute bit changes; when set the directory can be - * searched. - * - * On non-Posix operating systems, there may be only the ability to - * make a file read-only or read-write. In this case, the remaining - * permission bits will be synthesized to resemble typical values. For - * instance, on Windows NT the default permission bits are - * <code>0644</code>, which means read/write for owner, read-only for - * all others. The only change that can be made is to make the file + * A \File object is a representation of a file in the underlying platform. + * + * \Class \File extends module FileTest, supporting such singleton methods + * as <tt>File.exist?</tt>. + * + * == About the Examples + * + * Many examples here use these variables: + * + * :include: doc/examples/files.rdoc + * + * == Access Modes + * + * 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: + * + * - Begins with a 1- or 2-character + * {read/write mode}[rdoc-ref:File@Read-2FWrite+Mode]. + * - May also contain a 1-character {data mode}[rdoc-ref:File@Data+Mode]. + * - May also contain a 1-character + * {file-create mode}[rdoc-ref:File@File-Create+Mode]. + * + * ==== Read/Write Mode + * + * The read/write +mode+ determines: + * + * - Whether the file is to be initially truncated. + * + * - Whether reading is allowed, and if so: + * + * - The initial read position in the file. + * - Where in the file reading can occur. + * + * - Whether writing is allowed, and if so: + * + * - The initial write position in the file. + * - Where in the file writing can occur. + * + * These tables summarize: + * + * Read/Write Modes for Existing File + * + * |------|-----------|----------|----------|----------|-----------| + * | R/W | Initial | | Initial | | Initial | + * | Mode | Truncate? | Read | Read Pos | Write | Write Pos | + * |------|-----------|----------|----------|----------|-----------| + * | 'r' | No | Anywhere | 0 | Error | - | + * | 'w' | Yes | Error | - | Anywhere | 0 | + * | 'a' | No | Error | - | End only | End | + * | 'r+' | No | Anywhere | 0 | Anywhere | 0 | + * | 'w+' | Yes | Anywhere | 0 | Anywhere | 0 | + * | 'a+' | No | Anywhere | End | End only | End | + * |------|-----------|----------|----------|----------|-----------| + * + * Read/Write Modes for \File To Be Created + * + * |------|----------|----------|----------|-----------| + * | R/W | | Initial | | Initial | + * | Mode | Read | Read Pos | Write | Write Pos | + * |------|----------|----------|----------|-----------| + * | 'w' | Error | - | Anywhere | 0 | + * | 'a' | Error | - | End only | 0 | + * | 'w+' | Anywhere | 0 | Anywhere | 0 | + * | 'a+' | Anywhere | 0 | End only | End | + * |------|----------|----------|----------|-----------| + * + * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed + * for a non-existent file (exception raised). + * + * In the tables: + * + * - +Anywhere+ means that methods IO#rewind, IO#pos=, and IO#seek + * may be used to change the file's position, + * so that allowed reading or writing may occur anywhere in the file. + * - <tt>End only</tt> means that writing can occur only at end-of-file, + * and that methods IO#rewind, IO#pos=, and IO#seek do not affect writing. + * - +Error+ means that an exception is raised if disallowed reading or writing + * is attempted. + * + * ===== Read/Write Modes for Existing \File + * + * - <tt>'r'</tt>: + * + * - \File is not initially truncated: + * + * f = File.new('t.txt') # => #<File:t.txt> + * f.size == 0 # => false + * + * - File's initial read position is 0: + * + * f.pos # => 0 + * + * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: + * + * f.readline # => "First line\n" + * f.readline # => "Second line\n" + * + * f.rewind + * f.readline # => "First line\n" + * + * f.pos = 1 + * f.readline # => "irst line\n" + * + * f.seek(1, :CUR) + * f.readline # => "econd line\n" + * + * - Writing is not allowed: + * + * f.write('foo') # Raises IOError. + * + * - <tt>'w'</tt>: + * + * - \File is initially truncated: + * + * path = 't.tmp' + * File.write(path, text) + * f = File.new(path, 'w') + * f.size == 0 # => true + * + * - File's initial write position is 0: + * + * f.pos # => 0 + * + * - \File may be written anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.write('foo') + * f.flush + * File.read(path) # => "foo" + * f.pos # => 3 + * + * f.write('bar') + * f.flush + * File.read(path) # => "foobar" + * f.pos # => 6 + * + * f.rewind + * f.write('baz') + * f.flush + * File.read(path) # => "bazbar" + * f.pos # => 3 + * + * f.pos = 3 + * f.write('foo') + * f.flush + * File.read(path) # => "bazfoo" + * f.pos # => 6 + * + * f.seek(-3, :END) + * f.write('bam') + * f.flush + * File.read(path) # => "bazbam" + * f.pos # => 6 + * + * f.pos = 8 + * f.write('bah') # Zero padding as needed. + * f.flush + * File.read(path) # => "bazbam\u0000\u0000bah" + * f.pos # => 11 + * + * - Reading is not allowed: + * + * f.read # Raises IOError. + * + * - <tt>'a'</tt>: + * + * - \File is not initially truncated: + * + * path = 't.tmp' + * File.write(path, 'foo') + * f = File.new(path, 'a') + * f.size == 0 # => false + * + * - File's initial position is 0 (but is ignored): + * + * f.pos # => 0 + * + * - \File may be written only at end-of-file; + * IO#rewind, IO#pos=, IO#seek do not affect writing: + * + * f.write('bar') + * f.flush + * File.read(path) # => "foobar" + * f.write('baz') + * f.flush + * File.read(path) # => "foobarbaz" + * + * f.rewind + * f.write('bat') + * f.flush + * File.read(path) # => "foobarbazbat" + * + * - Reading is not allowed: + * + * f.read # Raises IOError. + * + * - <tt>'r+'</tt>: + * + * - \File is not initially truncated: + * + * path = 't.tmp' + * File.write(path, text) + * f = File.new(path, 'r+') + * f.size == 0 # => false + * + * - File's initial read position is 0: + * + * f.pos # => 0 + * + * - \File may be read or written anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.readline # => "First line\n" + * f.readline # => "Second line\n" + * + * f.rewind + * f.readline # => "First line\n" + * + * f.pos = 1 + * f.readline # => "irst line\n" + * + * f.seek(1, :CUR) + * f.readline # => "econd line\n" + * + * f.rewind + * f.write('WWW') + * f.flush + * File.read(path) + * # => "WWWst line\nSecond line\nFourth line\nFifth line\n" + * + * f.pos = 10 + * f.write('XXX') + * f.flush + * File.read(path) + * # => "WWWst lineXXXecond line\nFourth line\nFifth line\n" + * + * f.seek(-6, :END) + * # => 0 + * f.write('YYY') + * # => 3 + * f.flush + * # => #<File:t.tmp> + * File.read(path) + * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n" + * + * f.seek(2, :END) + * f.write('ZZZ') # Zero padding as needed. + * f.flush + * File.read(path) + * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n\u0000\u0000ZZZ" + * + * + * - <tt>'a+'</tt>: + * + * - \File is not initially truncated: + * + * path = 't.tmp' + * File.write(path, 'foo') + * f = File.new(path, 'a+') + * f.size == 0 # => false + * + * - File's initial read position is 0: + * + * f.pos # => 0 + * + * - \File may be written only at end-of-file; + * IO#rewind, IO#pos=, IO#seek do not affect writing: + * + * f.write('bar') + * f.flush + * File.read(path) # => "foobar" + * f.write('baz') + * f.flush + * File.read(path) # => "foobarbaz" + * + * f.rewind + * f.write('bat') + * f.flush + * File.read(path) # => "foobarbazbat" + * + * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek: + * + * f.rewind + * f.read # => "foobarbazbat" + * + * f.pos = 3 + * f.read # => "barbazbat" + * + * f.seek(-3, :END) + * f.read # => "bat" + * + * ===== Read/Write Modes for \File To Be Created + * + * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed + * for a non-existent file (exception raised). + * + * - <tt>'w'</tt>: + * + * - File's initial write position is 0: + * + * path = 't.tmp' + * FileUtils.rm_f(path) + * f = File.new(path, 'w') + * f.pos # => 0 + * + * - \File may be written anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.write('foo') + * f.flush + * File.read(path) # => "foo" + * f.pos # => 3 + * + * f.write('bar') + * f.flush + * File.read(path) # => "foobar" + * f.pos # => 6 + * + * f.rewind + * f.write('baz') + * f.flush + * File.read(path) # => "bazbar" + * f.pos # => 3 + * + * f.pos = 3 + * f.write('foo') + * f.flush + * File.read(path) # => "bazfoo" + * f.pos # => 6 + * + * f.seek(-3, :END) + * f.write('bam') + * f.flush + * File.read(path) # => "bazbam" + * f.pos # => 6 + * + * f.pos = 8 + * f.write('bah') # Zero padding as needed. + * f.flush + * File.read(path) # => "bazbam\u0000\u0000bah" + * f.pos # => 11 + * + * - Reading is not allowed: + * + * f.read # Raises IOError. + * + * - <tt>'a'</tt>: + * + * - File's initial write position is 0: + * + * path = 't.tmp' + * FileUtils.rm_f(path) + * f = File.new(path, 'a') + * f.pos # => 0 + * + * - Writing occurs only at end-of-file: + * + * f.write('foo') + * f.pos # => 3 + * f.write('bar') + * f.pos # => 6 + * f.flush + * File.read(path) # => "foobar" + * + * f.rewind + * f.write('baz') + * f.flush + * File.read(path) # => "foobarbaz" + * + * - Reading is not allowed: + * + * f.read # Raises IOError. + * + * - <tt>'w+'</tt>: + * + * - File's initial position is 0: + * + * path = 't.tmp' + * FileUtils.rm_f(path) + * f = File.new(path, 'w+') + * f.pos # => 0 + * + * - \File may be written anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.write('foo') + * f.flush + * File.read(path) # => "foo" + * f.pos # => 3 + * + * f.write('bar') + * f.flush + * File.read(path) # => "foobar" + * f.pos # => 6 + * + * f.rewind + * f.write('baz') + * f.flush + * File.read(path) # => "bazbar" + * f.pos # => 3 + * + * f.pos = 3 + * f.write('foo') + * f.flush + * File.read(path) # => "bazfoo" + * f.pos # => 6 + * + * f.seek(-3, :END) + * f.write('bam') + * f.flush + * File.read(path) # => "bazbam" + * f.pos # => 6 + * + * f.pos = 8 + * f.write('bah') # Zero padding as needed. + * f.flush + * File.read(path) # => "bazbam\u0000\u0000bah" + * f.pos # => 11 + * + * - \File may be read anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.rewind + * # => 0 + * f.read + * # => "bazbam\u0000\u0000bah" + * + * f.pos = 3 + * # => 3 + * f.read + * # => "bam\u0000\u0000bah" + * + * f.seek(-3, :END) + * # => 0 + * f.read + * # => "bah" + * + * - <tt>'a+'</tt>: + * + * - File's initial write position is 0: + * + * path = 't.tmp' + * FileUtils.rm_f(path) + * f = File.new(path, 'a+') + * f.pos # => 0 + * + * - Writing occurs only at end-of-file: + * + * f.write('foo') + * f.pos # => 3 + * f.write('bar') + * f.pos # => 6 + * f.flush + * File.read(path) # => "foobar" + * + * f.rewind + * f.write('baz') + * f.flush + * File.read(path) # => "foobarbaz" + * + * - \File may be read anywhere (even past end-of-file); + * see IO#rewind, IO#pos=, IO#seek: + * + * f.rewind + * f.read # => "foobarbaz" + * + * f.pos = 3 + * f.read # => "barbaz" + * + * f.seek(-3, :END) + * f.read # => "baz" + * + * f.pos = 800 + * f.read # => "" + * + * ==== \Data Mode + * + * To specify whether data is to be treated as text or as binary data, + * either of the following may be suffixed to any of the string read/write modes + * above: + * + * - <tt>'t'</tt>: Text data; sets the default external encoding + * to <tt>Encoding::UTF_8</tt>; + * on Windows, enables conversion between EOL and CRLF + * and enables interpreting <tt>0x1A</tt> as an end-of-file marker. + * - <tt>'b'</tt>: Binary data; sets the default external encoding + * to <tt>Encoding::ASCII_8BIT</tt>; + * on Windows, suppresses conversion between EOL and CRLF + * and disables interpreting <tt>0x1A</tt> as an end-of-file marker. + * + * If neither is given, the stream defaults to text data. + * + * Examples: + * + * File.new('t.txt', 'rt') + * File.new('t.dat', 'rb') + * + * When the data mode is specified, the read/write mode may not be omitted, + * and the data mode must precede the file-create mode, if given: + * + * File.new('t.dat', 'b') # Raises an exception. + * File.new('t.dat', 'rxb') # Raises an exception. + * + * ==== \File-Create Mode + * + * The following may be suffixed to any writable string mode above: + * + * - <tt>'x'</tt>: Creates the file if it does not exist; + * raises an exception if the file exists. + * + * Example: + * + * File.new('t.tmp', 'wx') + * + * When the file-create mode is specified, the read/write mode may not be omitted, + * and the file-create mode must follow the data mode: + * + * File.new('t.dat', 'x') # Raises an exception. + * File.new('t.dat', 'rxb') # Raises an exception. + * + * === \Integer Access Modes + * + * When mode is an integer it must be one or more of the following constants, + * which may be combined by the bitwise OR operator <tt>|</tt>: + * + * - +File::RDONLY+: Open for reading only. + * - +File::WRONLY+: Open for writing only. + * - +File::RDWR+: Open for reading and writing. + * - +File::APPEND+: Open for appending only. + * + * Examples: + * + * File.new('t.txt', File::RDONLY) + * File.new('t.tmp', File::RDWR | File::CREAT | File::EXCL) + * + * Note: Method IO#set_encoding does not allow the mode to be specified as an integer. + * + * === File-Create Mode Specified as an \Integer + * + * These constants may also be ORed into the integer mode: + * + * - +File::CREAT+: Create file if it does not exist. + * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists. + * + * === \Data Mode Specified as an \Integer + * + * \Data mode cannot be specified as an integer. + * When the stream access mode is given as an integer, + * the data mode is always text, never binary. + * + * Note that although there is a constant +File::BINARY+, + * setting its value in an integer stream mode has no effect; + * this is because, as documented in File::Constants, + * the +File::BINARY+ value disables line code conversion, + * but does not change the external encoding. + * + * === Encodings + * + * Any of the string modes above may specify encodings - + * either external encoding only or both external and internal encodings - + * by appending one or both encoding names, separated by colons: + * + * f = File.new('t.dat', 'rb') + * f.external_encoding # => #<Encoding:ASCII-8BIT> + * f.internal_encoding # => nil + * f = File.new('t.dat', 'rb:UTF-16') + * f.external_encoding # => #<Encoding:UTF-16 (dummy)> + * f.internal_encoding # => nil + * f = File.new('t.dat', 'rb:UTF-16:UTF-16') + * f.external_encoding # => #<Encoding:UTF-16 (dummy)> + * f.internal_encoding # => #<Encoding:UTF-16> + * f.close + * + * The numerous encoding names are available in array Encoding.name_list: + * + * Encoding.name_list.take(3) # => ["ASCII-8BIT", "UTF-8", "US-ASCII"] + * + * When the external encoding is set, strings read are tagged by that encoding + * when reading, and strings written are converted to that encoding when + * writing. + * + * When both external and internal encodings are set, + * strings read are converted from external to internal encoding, + * and strings written are converted from internal to external encoding. + * For further details about transcoding input and output, + * see {Encodings}[rdoc-ref:encodings.rdoc@Encodings]. + * + * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt> + * or <tt>'BOM|UTF16-BE'</tt>, + * Ruby checks for a Unicode BOM in the input document + * to help determine the encoding. + * For UTF-16 encodings the file open mode must be binary. + * If the BOM is found, + * it is stripped and the external encoding from the BOM is used. + * + * Note that the BOM-style encoding option is case insensitive, + * so <tt>'bom|utf-8'</tt> is also valid. + * + * == \File Permissions + * + * A \File object has _permissions_, an octal integer representing + * the permissions of an actual file in the underlying platform. + * + * Note that file permissions are quite different from the _mode_ + * of a file stream (\File object). + * + * In a \File object, the permissions are available thus, + * where method +mode+, despite its name, returns permissions: + * + * f = File.new('t.txt') + * f.lstat.mode.to_s(8) # => "100644" + * + * On a Unix-based operating system, + * the three low-order octal digits represent the permissions + * for owner (6), group (4), and world (4). + * The triplet of bits in each octal digit represent, respectively, + * read, write, and execute permissions. + * + * Permissions <tt>0644</tt> thus represent read-write access for owner + * and read-only access for group and world. + * See man pages {open(2)}[https://www.unix.com/man-page/bsd/2/open] + * and {chmod(2)}[https://www.unix.com/man-page/bsd/2/chmod]. + * + * For a directory, the meaning of the execute bit changes: + * when set, the directory can be searched. + * + * Higher-order bits in permissions may indicate the type of file + * (plain, directory, pipe, socket, etc.) and various other special features. + * + * On non-Posix operating systems, permissions may include only read-only or read-write, + * in which case, the remaining permission will resemble typical values. + * On Windows, for instance, the default permissions are <code>0644</code>; + * The only change that can be made is to make the file * read-only, which is reported as <code>0444</code>. * - * Various constants for the methods in File can be found in File::Constants. + * For a method that actually creates a file in the underlying platform + * (as opposed to merely creating a \File object), + * permissions may be specified: + * + * File.new('t.tmp', File::CREAT, 0644) + * File.new('t.tmp', File::CREAT, 0444) + * + * Permissions may also be changed: + * + * f = File.new('t.tmp', File::CREAT, 0444) + * f.chmod(0644) + * f.chmod(0444) + * + * == \File \Constants + * + * 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: * - * - Inherits from {class IO}[IO.html#class-IO-label-What-27s+Here], + * - Inherits from {class IO}[rdoc-ref:IO@What-27s+Here], * in particular, methods for creating, reading, and writing files - * - Includes {module FileTest}[FileTest.html#module-FileTest-label-What-27s+Here]. + * - Includes module FileTest, * which provides dozens of additional methods. * * Here, class \File provides methods that are useful for: * - * - {Creating}[#class-File-label-Creating] - * - {Querying}[#class-File-label-Querying] - * - {Settings}[#class-File-label-Settings] - * - {Other}[#class-File-label-Other] + * - {Creating}[rdoc-ref:File@Creating] + * - {Querying}[rdoc-ref:File@Querying] + * - {Settings}[rdoc-ref:File@Settings] + * - {Other}[rdoc-ref:File@Other] * * === Creating * - * - ::new:: Opens the file at the given path; returns the file. - * - ::open:: Same as ::new, but when given a block will yield the file to the block, - * and close the file upon exiting the block. - * - ::link:: Creates a new name for an existing file using a hard link. - * - ::mkfifo:: Returns the FIFO file created at the given path. - * - ::symlink:: Creates a symbolic link for the given file path. + * - ::new: Opens the file at the given path; returns the file. + * - ::open: Same as ::new, but when given a block will yield the file to the block, + * and close the file upon exiting the block. + * - ::link: Creates a new name for an existing file using a hard link. + * - ::mkfifo: Returns the FIFO file created at the given path. + * - ::symlink: Creates a symbolic link for the given file path. * * === Querying * * _Paths_ * - * - ::absolute_path:: Returns the absolute file path for the given path. - * - ::absolute_path?:: Returns whether the given path is the absolute file path. - * - ::basename:: Returns the last component of the given file path. - * - ::dirname:: Returns all but the last component of the given file path. - * - ::expand_path:: Returns the absolute file path for the given path, - * expanding <tt>~</tt> for a home directory. - * - ::extname:: Returns the file extension for the given file path. - * - ::fnmatch? (aliased as ::fnmatch):: Returns whether the given file path - * matches the given pattern. - * - ::join:: Joins path components into a single path string. - * - ::path:: Returns the string representation of the given path. - * - ::readlink:: Returns the path to the file at the given symbolic link. - * - ::realdirpath:: Returns the real path for the given file path, - * where the last component need not exist. - * - ::realpath:: Returns the real path for the given file path, - * where all components must exist. - * - ::split:: Returns an array of two strings: the directory name and basename - * of the file at the given path. - * - #path (aliased as #to_path):: Returns the string representation of the given path. + * - ::absolute_path: Returns the absolute file path for the given path. + * - ::absolute_path?: Returns whether the given path is the absolute file path. + * - ::basename: Returns the last component of the given file path. + * - ::dirname: Returns all but the last component of the given file path. + * - ::expand_path: Returns the absolute file path for the given path, + * expanding <tt>~</tt> for a home directory. + * - ::extname: Returns the file extension for the given file path. + * - ::fnmatch? (aliased as ::fnmatch): Returns whether the given file path + * matches the given pattern. + * - ::join: Joins path components into a single path string. + * - ::path: Returns the string representation of the given path. + * - ::readlink: Returns the path to the file at the given symbolic link. + * - ::realdirpath: Returns the real path for the given file path, + * where the last component need not exist. + * - ::realpath: Returns the real path for the given file path, + * where all components must exist. + * - ::split: Returns an array of two strings: the directory name and basename + * of the file at the given path. + * - #path (aliased as #to_path): Returns the string representation of the given path. * * _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 - * 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 - * to the content of +self+. + * - ::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 + * to the content of +self+. * * _Types_ * - * - ::blockdev?:: Returns whether the file at the given path is a block device. - * - ::chardev?:: Returns whether the file at the given path is a character device. - * - ::directory?:: Returns whether the file at the given path is a diretory. - * - ::executable?:: Returns whether the file at the given path is executable - * by the effective user and group of the current process. - * - ::executable_real?:: Returns whether the file at the given path is executable - * by the real user and group of the current process. - * - ::exist?:: Returns whether the file at the given path exists. - * - ::file?:: Returns whether the file at the given path is a regular file. - * - ::ftype:: Returns a string giving the type of the file at the given path. - * - ::grpowned?:: Returns whether the effective group of the current process - * owns the file at the given path. - * - ::identical?:: Returns whether the files at two given paths are identical. - * - ::lstat:: Returns the File::Stat object for the last symbolic link - * in the given path. - * - ::owned?:: Returns whether the effective user of the current process - * owns the file at the given path. - * - ::pipe?:: Returns whether the file at the given path is a pipe. - * - ::readable?:: Returns whether the file at the given path is readable - * by the effective user and group of the current process. - * - ::readable_real?:: Returns whether the file at the given path is readable - * by the real user and group of the current process. - * - ::setgid?:: Returns whether the setgid bit is set for the file at the given path. - * - ::setuid?:: Returns whether the setuid bit is set for the file at the given path. - * - ::socket?:: Returns whether the file at the given path is a socket. - * - ::stat:: Returns the File::Stat object for the file at the given path. - * - ::sticky?:: Returns whether the file at the given path has its sticky bit set. - * - ::symlink?:: Returns whether the file at the given path is a symbolic link. - * - ::umask:: Returns the umask value for the current process. - * - ::world_readable?:: Returns whether the file at the given path is readable - * by others. - * - ::world_writable?:: Returns whether the file at the given path is writable - * by others. - * - ::writable?:: Returns whether the file at the given path is writable - * by the effective user and group of the current process. - * - ::writable_real?:: Returns whether the file at the given path is writable - * by the real user and group of the current process. - * - #lstat:: Returns the File::Stat object for the last symbolic link - * in the path for +self+. + * - ::blockdev?: Returns whether the file at the given path is a block device. + * - ::chardev?: Returns whether the file at the given path is a character device. + * - ::directory?: Returns whether the file at the given path is a directory. + * - ::executable?: Returns whether the file at the given path is executable + * by the effective user and group of the current process. + * - ::executable_real?: Returns whether the file at the given path is executable + * by the real user and group of the current process. + * - ::exist?: Returns whether the file at the given path exists. + * - ::file?: Returns whether the file at the given path is a regular file. + * - ::ftype: Returns a string giving the type of the file at the given path. + * - ::grpowned?: Returns whether the effective group of the current process + * owns the file at the given path. + * - ::identical?: Returns whether the files at two given paths are identical. + * - ::lstat: Returns the File::Stat object for the last symbolic link + * in the given path. + * - ::owned?: Returns whether the effective user of the current process + * owns the file at the given path. + * - ::pipe?: Returns whether the file at the given path is a pipe. + * - ::readable?: Returns whether the file at the given path is readable + * by the effective user and group of the current process. + * - ::readable_real?: Returns whether the file at the given path is readable + * by the real user and group of the current process. + * - ::setgid?: Returns whether the setgid bit is set for the file at the given path. + * - ::setuid?: Returns whether the setuid bit is set for the file at the given path. + * - ::socket?: Returns whether the file at the given path is a socket. + * - ::stat: Returns the File::Stat object for the file at the given path. + * - ::sticky?: Returns whether the file at the given path has its sticky bit set. + * - ::symlink?: Returns whether the file at the given path is a symbolic link. + * - ::umask: Returns the umask value for the current process. + * - ::world_readable?: Returns whether the file at the given path is readable + * by others. + * - ::world_writable?: Returns whether the file at the given path is writable + * by others. + * - ::writable?: Returns whether the file at the given path is writable + * by the effective user and group of the current process. + * - ::writable_real?: Returns whether the file at the given path is writable + * by the real user and group of the current process. + * - #lstat: Returns the File::Stat object for the last symbolic link + * in the path for +self+. * * _Contents_ * - * - ::empty? (aliased as ::zero?):: Returns whether the file at the given path - * exists and is empty. - * - ::size:: Returns the size (bytes) of the file at the given path. - * - ::size?:: Returns +nil+ if there is no file at the given path, - * or if that file is empty; otherwise returns the file size (bytes). - * - #size:: Returns the size (bytes) of +self+. + * - ::empty? (aliased as ::zero?): Returns whether the file at the given path + * exists and is empty. + * - ::size: Returns the size (bytes) of the file at the given path. + * - ::size?: Returns +nil+ if there is no file at the given path, + * or if that file is empty; otherwise returns the file size (bytes). + * - #size: Returns the size (bytes) of +self+. * * === Settings * - * - ::chmod:: Changes permissions of the file at the given path. - * - ::chown:: Change ownership of the file at the given path. - * - ::lchmod:: Changes permissions of the last symbolic link in the given path. - * - ::lchown:: Change ownership of the last symbolic in the given path. - * - ::lutime:: For each given file path, sets the access time and modification time - * of the last symbolic link in the path. - * - ::rename:: Moves the file at one given path to another given path. - * - ::utime:: Sets the access time and modification time of each file - * at the given paths. - * - #flock:: Locks or unlocks +self+. + * - ::chmod: Changes permissions of the file at the given path. + * - ::chown: Change ownership of the file at the given path. + * - ::lchmod: Changes permissions of the last symbolic link in the given path. + * - ::lchown: Change ownership of the last symbolic in the given path. + * - ::lutime: For each given file path, sets the access time and modification time + * of the last symbolic link in the path. + * - ::rename: Moves the file at one given path to another given path. + * - ::utime: Sets the access time and modification time of each file + * at the given paths. + * - #flock: Locks or unlocks +self+. * * === Other * - * - ::truncate:: Truncates the file at the given file path to the given size. - * - ::unlink (aliased as ::delete):: Deletes the file for each given file path. - * - #truncate:: Truncates +self+ to the given size. + * - ::truncate: Truncates the file at the given file path to the given size. + * - ::unlink (aliased as ::delete): Deletes the file for each given file path. + * - #truncate: Truncates +self+ to the given size. * */ void Init_File(void) { +#if defined(__APPLE__) && defined(HAVE_WORKING_FORK) + rb_CFString_class_initialize_before_fork(); +#endif + VALUE separator; rb_mFileTest = rb_define_module("FileTest"); @@ -6676,7 +7441,6 @@ Init_File(void) define_filetest_function("directory?", rb_file_directory_p, 1); define_filetest_function("exist?", rb_file_exist_p, 1); - define_filetest_function("exists?", rb_file_exists_p, 1); define_filetest_function("readable?", rb_file_readable_p, 1); define_filetest_function("readable_real?", rb_file_readable_real_p, 1); define_filetest_function("world_readable?", rb_file_world_readable_p, 1); @@ -6766,7 +7530,7 @@ Init_File(void) rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0); rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0); rb_define_method(rb_cFile, "birthtime", rb_file_birthtime, 0); - rb_define_method(rb_cFile, "size", rb_file_size, 0); + rb_define_method(rb_cFile, "size", file_size, 0); rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1); rb_define_method(rb_cFile, "chown", rb_file_chown, 2); @@ -6777,97 +7541,402 @@ 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: * - * If the underlying platform doesn't define a constant the corresponding - * Ruby constant is not defined. + * 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. + * + * === 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_method(rb_cFile, "path", rb_file_path, 0); - rb_define_method(rb_cFile, "to_path", rb_file_path, 0); rb_define_global_function("test", rb_f_test, -1); rb_cStat = rb_define_class_under(rb_cFile, "Stat", rb_cObject); |