Ruby 4.1.0dev (2026-10-26 revision 86fb7913b115e6dbc3546563c677a9b8914877ea)
file.c (86fb7913b115e6dbc3546563c677a9b8914877ea)
1/**********************************************************************
2
3 file.c -
4
5 $Author$
6 created at: Mon Nov 15 12:24:34 JST 1993
7
8 Copyright (C) 1993-2007 Yukihiro Matsumoto
9 Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
10 Copyright (C) 2000 Information-technology Promotion Agency, Japan
11
12**********************************************************************/
13
14#include "ruby/internal/config.h"
16
17#ifdef _WIN32
18# include "missing/file.h"
19# include "ruby.h"
20#endif
21
22#include <ctype.h>
23#include <time.h>
24
25#ifdef __CYGWIN__
26# include <windows.h>
27#endif
28
29#ifdef __APPLE__
30# if !(defined(__has_feature) && defined(__has_attribute))
31/* Maybe a bug in SDK of Xcode 10.2.1 */
32/* In this condition, <os/availability.h> does not define
33 * API_AVAILABLE and similar, but __API_AVAILABLE and similar which
34 * are defined in <Availability.h> */
35# define API_AVAILABLE(...)
36# define API_DEPRECATED(...)
37# endif
38# include <CoreFoundation/CFString.h>
39#endif
40
41#ifdef HAVE_UNISTD_H
42# include <unistd.h>
43#endif
44
45#ifdef HAVE_SYS_TIME_H
46# include <sys/time.h>
47#endif
48
49#ifdef HAVE_SYS_FILE_H
50# include <sys/file.h>
51#else
52int flock(int, int);
53#endif
54
55#ifdef HAVE_SYS_PARAM_H
56# include <sys/param.h>
57#endif
58#ifndef MAXPATHLEN
59# define MAXPATHLEN 1024
60#endif
61
62#ifdef HAVE_UTIME_H
63# include <utime.h>
64#elif defined HAVE_SYS_UTIME_H
65# include <sys/utime.h>
66#endif
67
68#ifdef HAVE_PWD_H
69# include <pwd.h>
70#endif
71
72#ifdef HAVE_SYS_SYSMACROS_H
73# include <sys/sysmacros.h>
74#endif
75
76#include <sys/types.h>
77#include <sys/stat.h>
78
79#ifdef HAVE_SYS_MKDEV_H
80# include <sys/mkdev.h>
81#endif
82
83#if defined(HAVE_FCNTL_H)
84# include <fcntl.h>
85#endif
86
87#if defined(HAVE_SYS_TIME_H)
88# include <sys/time.h>
89#endif
90
91#if !defined HAVE_LSTAT && !defined lstat
92# define lstat stat
93#endif
94
95/* define system APIs */
96#ifdef _WIN32
97# include "win32/file.h"
98# undef chmod
99# define chmod(p, m) rb_w32_uchmod((p), (m))
100# undef chown
101# define chown(p, o, g) rb_w32_uchown((p), (o), (g))
102# undef lchown
103# define lchown(p, o, g) rb_w32_ulchown((p), (o), (g))
104# undef link
105# define link(f, t) rb_w32_ulink((f), (t))
106# undef readlink
107# define readlink(f, t, l) rb_w32_ureadlink((f), (t), (l))
108# undef symlink
109# define symlink(s, l) rb_w32_usymlink((s), (l))
110
111# ifdef HAVE_REALPATH
112/* Don't use native realpath(3) on Windows, as the check for
113 absolute paths does not work for drive letters. */
114# undef HAVE_REALPATH
115# endif
116#endif /* _WIN32 */
117
118#ifdef HAVE_STRUCT_STATX_STX_BTIME
119# define ST_(name) stx_ ## name
120typedef struct statx_timestamp stat_timestamp;
121#else
122# define ST_(name) st_ ## name
123typedef struct timespec stat_timestamp;
124#endif
125
126#if defined _WIN32 || defined __APPLE__
127# define USE_OSPATH 1
128# define TO_OSPATH(str) rb_str_encode_ospath(str)
129#else
130# define USE_OSPATH 0
131# define TO_OSPATH(str) (str)
132#endif
133
134/* utime may fail if time is out-of-range for the FS [ruby-dev:38277] */
135#if defined DOSISH || defined __CYGWIN__
136# define UTIME_EINVAL
137#endif
138
139/* Solaris 10 realpath(3) doesn't support File.realpath */
140#if defined HAVE_REALPATH && defined __sun && defined __SVR4
141#undef HAVE_REALPATH
142#endif
143
144#ifdef HAVE_REALPATH
145# include <limits.h>
146# include <stdlib.h>
147#endif
148
149#include "dln.h"
150#include "encindex.h"
151#include "id.h"
152#include "internal.h"
153#include "internal/compilers.h"
154#include "internal/dir.h"
155#include "internal/encoding.h"
156#include "internal/error.h"
157#include "internal/file.h"
158#include "internal/io.h"
159#include "internal/load.h"
160#include "internal/object.h"
161#include "internal/process.h"
162#include "internal/thread.h"
163#include "internal/vm.h"
164#include "ruby/encoding.h"
165#include "ruby/thread.h"
166#include "ruby/util.h"
167
168#define UIANY2NUM(x) \
169 ((sizeof(x) <= sizeof(unsigned int)) ? \
170 UINT2NUM((unsigned)(x)) : \
171 (sizeof(x) <= sizeof(unsigned long)) ? \
172 ULONG2NUM((unsigned long)(x)) : \
173 ULL2NUM((unsigned LONG_LONG)(x)))
174
178
179static VALUE
180file_path_convert(VALUE name)
181{
182#ifndef _WIN32 /* non Windows == Unix */
183 int fname_encidx = ENCODING_GET(name);
184 int fs_encidx;
185 if (ENCINDEX_US_ASCII != fname_encidx &&
186 ENCINDEX_ASCII_8BIT != fname_encidx &&
187 (fs_encidx = rb_filesystem_encindex()) != fname_encidx &&
188 rb_default_internal_encoding() &&
189 !rb_enc_str_asciionly_p(name)) {
190 /* Don't call rb_filesystem_encoding() before US-ASCII and ASCII-8BIT */
191 /* fs_encoding should be ascii compatible */
192 rb_encoding *fname_encoding = rb_enc_from_index(fname_encidx);
193 rb_encoding *fs_encoding = rb_enc_from_index(fs_encidx);
194 name = rb_str_conv_enc(name, fname_encoding, fs_encoding);
195 }
196#endif
197 return name;
198}
199
200static void
201check_path_encoding(VALUE str)
202{
203 if (RB_UNLIKELY(!rb_str_enc_fastpath(str))) {
204 rb_encoding *enc = rb_str_enc_get(str);
205 if (!rb_enc_asciicompat(enc)) {
206 rb_raise(rb_eEncCompatError, "path name must be ASCII-compatible (%s): %"PRIsVALUE,
207 rb_enc_name(enc), rb_str_inspect(str));
208 }
209 }
210}
211
212VALUE
213rb_get_path_check_to_string(VALUE obj)
214{
215 VALUE tmp;
216 ID to_path;
217
218 if (RB_TYPE_P(obj, T_STRING)) {
219 return obj;
220 }
221 CONST_ID(to_path, "to_path");
222 tmp = rb_check_funcall_default(obj, to_path, 0, 0, obj);
223 StringValue(tmp);
224 return tmp;
225}
226
227VALUE
228rb_get_path_check_convert(VALUE obj)
229{
230 obj = file_path_convert(obj);
231 rb_get_path_check_no_convert(obj);
232 return rb_str_new_frozen(obj);
233}
234
235/* TODO: name */
236VALUE
237rb_get_path_check_no_convert(VALUE obj)
238{
239 check_path_encoding(obj);
240 if (!rb_str_to_cstr(obj)) {
241 rb_raise(rb_eArgError, "path name contains null byte");
242 }
243
244 return obj;
245}
246
247VALUE
248rb_get_path_no_checksafe(VALUE obj)
249{
250 return rb_get_path(obj);
251}
252
253VALUE
254rb_get_path(VALUE obj)
255{
256 return rb_get_path_check_convert(rb_get_path_check_to_string(obj));
257}
258
259static inline VALUE
260check_path(VALUE obj, const char **cstr)
261{
262 VALUE str = rb_get_path_check_convert(rb_get_path_check_to_string(obj));
263#if RUBY_DEBUG
264 str = rb_str_new_frozen(str);
265#endif
266 *cstr = RSTRING_PTR(str);
267 return str;
268}
269
270#define CheckPath(str, cstr) RB_GC_GUARD(str) = check_path(str, &cstr);
271
272VALUE
273rb_str_encode_ospath(VALUE path)
274{
275#if USE_OSPATH
276 int encidx = ENCODING_GET(path);
277 if (encidx != ENCINDEX_ASCII_8BIT && encidx != ENCINDEX_UTF_8) {
278 rb_encoding *enc = rb_enc_from_index(encidx);
279 rb_encoding *utf8 = rb_utf8_encoding();
280 path = rb_str_conv_enc(path, enc, utf8);
281 }
282#endif /* USE_OSPATH */
283 return path;
284}
285
286#ifdef __APPLE__
287# define NORMALIZE_UTF8PATH 1
288
289# ifdef HAVE_WORKING_FORK
290static CFMutableStringRef
291mutable_CFString_new(CFStringRef *s, const char *ptr, rb_len_t len)
292{
293 const CFAllocatorRef alloc = kCFAllocatorDefault;
294 *s = CFStringCreateWithBytesNoCopy(alloc, (const UInt8 *)ptr, len,
295 kCFStringEncodingUTF8, FALSE,
296 kCFAllocatorNull);
297 return CFStringCreateMutableCopy(alloc, len, *s);
298}
299
300# define mutable_CFString_release(m, s) (CFRelease(m), CFRelease(s))
301
302static void
303rb_CFString_class_initialize_before_fork(void)
304{
305 /*
306 * Since macOS 13, CFString family API used in
307 * rb_str_append_normalized_ospath may internally use Objective-C classes
308 * (NSTaggedPointerString and NSPlaceholderMutableString) for small strings.
309 *
310 * On the other hand, Objective-C classes should not be used for the first
311 * time in a fork()'ed but not exec()'ed process. Violations for this rule
312 * can result deadlock during class initialization, so Objective-C runtime
313 * conservatively crashes on such cases by default.
314 *
315 * Therefore, we need to use CFString API to initialize Objective-C classes
316 * used internally *before* fork().
317 *
318 * For future changes, please note that this initialization process cannot
319 * be done in ctor because NSTaggedPointerString in CoreFoundation is enabled
320 * after CFStringInitializeTaggedStrings(), which is called during loading
321 * Objective-C runtime after ctor.
322 * For more details, see https://bugs.ruby-lang.org/issues/18912
323 */
324
325 /* Enough small but non-empty ASCII string to fit in NSTaggedPointerString. */
326 const char small_str[] = "/";
327 rb_len_t len = sizeof(small_str) - 1;
328 CFStringRef s;
329 /*
330 * Touch `CFStringCreateWithBytesNoCopy` *twice* because the implementation
331 * shipped with macOS 15.0 24A5331b does not return `NSTaggedPointerString`
332 * instance for the first call (totally not sure why). CoreFoundation
333 * shipped with macOS 15.1 does not have this issue.
334 */
335 for (int i = 0; i < 2; i++) {
336 CFMutableStringRef m = mutable_CFString_new(&s, small_str, len);
337 mutable_CFString_release(m, s);
338 }
339}
340# endif /* HAVE_WORKING_FORK */
341
342static VALUE
343rb_str_append_normalized_ospath(VALUE str, const char *ptr, rb_len_t len)
344{
345 CFIndex buflen = 0;
346 CFRange all;
347 CFStringRef s;
348 CFMutableStringRef m = mutable_CFString_new(&s, ptr, len);
349 rb_len_t oldlen = RSTRING_LEN(str);
350
351 CFStringNormalize(m, kCFStringNormalizationFormC);
352 all = CFRangeMake(0, CFStringGetLength(m));
353 CFStringGetBytes(m, all, kCFStringEncodingUTF8, '?', FALSE, NULL, 0, &buflen);
354 rb_str_modify_expand(str, buflen);
355 CFStringGetBytes(m, all, kCFStringEncodingUTF8, '?', FALSE,
356 (UInt8 *)(RSTRING_PTR(str) + oldlen), buflen, &buflen);
357 rb_str_set_len(str, oldlen + buflen);
358 mutable_CFString_release(m, s);
359 return str;
360}
361
362VALUE
363rb_str_normalize_ospath(const char *ptr, rb_len_t len)
364{
365 const char *p = ptr;
366 const char *e = ptr + len;
367 const char *p1 = p;
368 rb_encoding *enc = rb_utf8_encoding();
369 VALUE str = rb_utf8_str_new(ptr, len);
370 if (RB_LIKELY(rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT)) {
371 return str;
372 }
373 else {
374 str = rb_str_buf_new(len);
375 rb_enc_associate(str, enc);
376 }
377
378 while (p < e) {
379 int l, c;
380 int r = rb_enc_precise_mbclen(p, e, enc);
381 if (!MBCLEN_CHARFOUND_P(r)) {
382 /* invalid byte shall not happen but */
383 RBIMPL_ATTR_NONSTRING() static const char invalid[3] = "\xEF\xBF\xBD";
384 rb_str_append_normalized_ospath(str, p1, p-p1);
385 rb_str_cat(str, invalid, sizeof(invalid));
386 p += 1;
387 p1 = p;
388 continue;
389 }
391 c = rb_enc_mbc_to_codepoint(p, e, enc);
392 if ((0x2000 <= c && c <= 0x2FFF) || (0xF900 <= c && c <= 0xFAFF) ||
393 (0x2F800 <= c && c <= 0x2FAFF)) {
394 if (p - p1 > 0) {
395 rb_str_append_normalized_ospath(str, p1, p-p1);
396 }
397 rb_str_cat(str, p, l);
398 p += l;
399 p1 = p;
400 }
401 else {
402 p += l;
403 }
404 }
405 if (p - p1 > 0) {
406 rb_str_append_normalized_ospath(str, p1, p-p1);
407 }
408
409 return str;
410}
411
412static int
413ignored_char_p(const char *p, const char *e, rb_encoding *enc)
414{
415 unsigned char c;
416 if (p+3 > e) return 0;
417 switch ((unsigned char)*p) {
418 case 0xe2:
419 switch ((unsigned char)p[1]) {
420 case 0x80:
421 c = (unsigned char)p[2];
422 /* c >= 0x200c && c <= 0x200f */
423 if (c >= 0x8c && c <= 0x8f) return 3;
424 /* c >= 0x202a && c <= 0x202e */
425 if (c >= 0xaa && c <= 0xae) return 3;
426 return 0;
427 case 0x81:
428 c = (unsigned char)p[2];
429 /* c >= 0x206a && c <= 0x206f */
430 if (c >= 0xaa && c <= 0xaf) return 3;
431 return 0;
432 }
433 break;
434 case 0xef:
435 /* c == 0xfeff */
436 if ((unsigned char)p[1] == 0xbb &&
437 (unsigned char)p[2] == 0xbf)
438 return 3;
439 break;
440 }
441 return 0;
442}
443#else /* !__APPLE__ */
444# define NORMALIZE_UTF8PATH 0
445#endif /* __APPLE__ */
446
447#define apply2args(n) (rb_check_arity(argc, n, UNLIMITED_ARGUMENTS), argc-=n)
448
450 const char *ptr;
451 VALUE path;
452};
453
454struct apply_arg {
455 int i;
456 int argc;
457 int errnum;
458 int (*func)(const char *, void *);
459 void *arg;
460 struct apply_filename fn[FLEX_ARY_LEN];
461};
462
463static void *
464no_gvl_apply2files(void *ptr)
465{
466 struct apply_arg *aa = ptr;
467
468 for (aa->i = 0; aa->i < aa->argc; aa->i++) {
469 if (aa->func(aa->fn[aa->i].ptr, aa->arg) < 0) {
470 aa->errnum = errno;
471 break;
472 }
473 }
474 return 0;
475}
476
477#ifdef UTIME_EINVAL
478NORETURN(static void utime_failed(struct apply_arg *));
479static int utime_internal(const char *, void *);
480#endif
481
482static VALUE
483apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg)
484{
485 VALUE v;
486 const size_t size = sizeof(struct apply_filename);
487 const rb_len_t len = (rb_len_t)(offsetof(struct apply_arg, fn) + (size * argc));
488 struct apply_arg *aa = ALLOCV(v, len);
489
490 aa->errnum = 0;
491 aa->argc = argc;
492 aa->arg = arg;
493 aa->func = func;
494
495 for (aa->i = 0; aa->i < argc; aa->i++) {
496 VALUE path = rb_get_path(argv[aa->i]);
497
498 path = rb_str_encode_ospath(path);
499 aa->fn[aa->i].ptr = RSTRING_PTR(path);
500 aa->fn[aa->i].path = path;
501 }
502
503 IO_WITHOUT_GVL(no_gvl_apply2files, aa);
504 if (aa->errnum) {
505#ifdef UTIME_EINVAL
506 if (func == utime_internal) {
507 utime_failed(aa);
508 }
509#endif
510 rb_syserr_fail_path(aa->errnum, aa->fn[aa->i].path);
511 }
512 if (v) {
513 ALLOCV_END(v);
514 }
515 return LONG2FIX(argc);
516}
517
518static stat_timestamp stat_atimespec(const struct stat *st);
519static stat_timestamp stat_mtimespec(const struct stat *st);
520static stat_timestamp stat_ctimespec(const struct stat *st);
521
522static const rb_data_type_t stat_data_type = {
523 "stat",
524 {
525 NULL,
527 NULL, // No external memory to report
528 },
529 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
530};
531
532struct rb_stat {
533 rb_io_stat_data stat;
534 bool initialized;
535};
536
537static struct rb_stat *
538stat_alloc(VALUE klass, VALUE *obj)
539{
540 struct rb_stat *rb_st;
541 *obj = TypedData_Make_Struct(klass, struct rb_stat, &stat_data_type, rb_st);
542 return rb_st;
543}
544
545VALUE
546rb_stat_new(const struct stat *st)
547{
548 VALUE obj;
549 struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj);
550 if (st) {
551#if RUBY_USE_STATX
552# define CP(m) .stx_ ## m = st->st_ ## m
553# define CP_32(m) .stx_ ## m = (uint32_t)st->st_ ## m
554# define CP_TS(m) .stx_ ## m = stat_ ## m ## spec(st)
555 rb_st->stat = (struct statx){
556 .stx_mask = STATX_BASIC_STATS,
557 CP(mode),
558 CP_32(nlink),
559 CP(uid),
560 CP(gid),
561 CP_TS(atime),
562 CP_TS(mtime),
563 CP_TS(ctime),
564 CP(ino),
565 CP(size),
566 CP(blocks),
567 };
568# undef CP
569# undef CP_TS
570#else
571 rb_st->stat = *st;
572#endif
573 rb_st->initialized = true;
574 }
575
576 return obj;
577}
578
579#ifndef rb_statx_new
580VALUE
581rb_statx_new(const rb_io_stat_data *st)
582{
583 VALUE obj;
584 struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj);
585 if (st) {
586 rb_st->stat = *st;
587 rb_st->initialized = true;
588 }
589 return obj;
590}
591#endif
592
593static rb_io_stat_data*
594get_stat(VALUE self)
595{
596 struct rb_stat* rb_st;
597 TypedData_Get_Struct(self, struct rb_stat, &stat_data_type, rb_st);
598 if (!rb_st->initialized) rb_raise(rb_eTypeError, "uninitialized File::Stat");
599 return &rb_st->stat;
600}
601
602#if RUBY_USE_STATX
603static stat_timestamp
604statx_mtimespec(const rb_io_stat_data *st)
605{
606 return st->stx_mtime;
607}
608#else
609# define statx_mtimespec stat_mtimespec
610#endif
611
612/*
613 * :markup: markdown
614 *
615 * call-seq:
616 * self <=> other -> -1, 0, 1, or nil
617 *
618 * Compares the [snapshots](rdoc-ref:File::Stat@Snapshot)
619 * in `self` and `other`, by comparing their modification times
620 * `self.mtime` and `other.mtime`.
621 *
622 * Returns:
623 *
624 * - `-1`, if `self.mtime` is earlier.
625 * - `0`, if the two values are equal.
626 * - `1`, if `self.mtime` is later.
627 * - `nil`, if `other` is not a \File::Stat object.
628 *
629 * Examples:
630 *
631 * ```ruby
632 * stat0 = File.stat('/etc')
633 * stat1 = File.stat('/tmp')
634 * stat0.mtime # => 2026-10-02 07:52:03.151556038 -0500
635 * stat1.mtime # => 2026-10-03 12:22:25.719215899 -0500
636 * stat0 <=> stat1 # => -1
637 * stat0 <=> stat0.dup # => 0
638 * stat1 <=> stat0 # => 1
639 * stat0 <=> :foo # => nil
640 * ```
641 *
642 * \Class \File::Stat includes module Comparable,
643 * each of whose methods uses \File::Stat#<=> for comparison.
644 */
645
646static VALUE
647rb_stat_cmp(VALUE self, VALUE other)
648{
649 if (rb_obj_is_kind_of(other, rb_obj_class(self))) {
650 stat_timestamp ts1 = statx_mtimespec(get_stat(self));
651 stat_timestamp ts2 = statx_mtimespec(get_stat(other));
652 if (ts1.tv_sec == ts2.tv_sec) {
653 if (ts1.tv_nsec == ts2.tv_nsec) return INT2FIX(0);
654 if (ts1.tv_nsec < ts2.tv_nsec) return INT2FIX(-1);
655 return INT2FIX(1);
656 }
657 if (ts1.tv_sec < ts2.tv_sec) return INT2FIX(-1);
658 return INT2FIX(1);
659 }
660 return Qnil;
661}
662
663#define ST2UINT(val) ((val) & ~(~1UL << (sizeof(val) * CHAR_BIT - 1)))
664
665#ifndef NUM2DEVT
666# define NUM2DEVT(v) NUM2UINT(v)
667#endif
668#ifndef DEVT2NUM
669# define DEVT2NUM(v) UINT2NUM(v)
670#endif
671#ifndef PRI_DEVT_PREFIX
672# define PRI_DEVT_PREFIX ""
673#endif
674
675/*
676 * :markup: markdown
677 *
678 * call-seq:
679 * dev -> integer
680 *
681 * Returns the device number for the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
682 *
683 * ```ruby
684 * File.stat('/etc').dev # => 66306
685 * File.stat('/dev').dev # => 5
686 * ```
687 *
688 */
689
690static VALUE
691rb_stat_dev(VALUE self)
692{
693#if RUBY_USE_STATX
694 unsigned int m = get_stat(self)->stx_dev_major;
695 unsigned int n = get_stat(self)->stx_dev_minor;
696 return ULL2NUM(makedev(m, n));
697#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T
698 return DEVT2NUM(get_stat(self)->st_dev);
699#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_LONG
700 return ULONG2NUM(get_stat(self)->st_dev);
701#else
702 return ULL2NUM(get_stat(self)->st_dev);
703#endif
704}
705
706/*
707 * :markup: markdown
708 *
709 * call-seq:
710 * dev_major -> integer
711 *
712 * Returns `nil` on Windows.
713 *
714 * On other systems, returns the major part of the device number
715 * for the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
716 *
717 * ```ruby
718 * File.stat('/etc').dev_major # => 259
719 * File.stat('/dev').dev_major # => 0
720 * ```
721 *
722 */
723
724static VALUE
725rb_stat_dev_major(VALUE self)
726{
727#if RUBY_USE_STATX
728 return UINT2NUM(get_stat(self)->stx_dev_major);
729#elif defined(major)
730 return UINT2NUM(major(get_stat(self)->st_dev));
731#else
732 return Qnil;
733#endif
734}
735
736/*
737 * :markup: markdown
738 *
739 * call-seq:
740 * dev_minor -> integer
741 *
742 * Returns `nil` on Windows.
743 *
744 * On other systems, returns the minor part of the device number
745 * for the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
746 *
747 * ```ruby
748 * File.stat('/etc').dev_minor # => 2
749 * File.stat('/dev').dev_minor # => 5
750 * ```
751 *
752 */
753
754static VALUE
755rb_stat_dev_minor(VALUE self)
756{
757#if RUBY_USE_STATX
758 return UINT2NUM(get_stat(self)->stx_dev_minor);
759#elif defined(minor)
760 return UINT2NUM(minor(get_stat(self)->st_dev));
761#else
762 return Qnil;
763#endif
764}
765
766/*
767 * :markup: markdown
768 *
769 * call-seq:
770 * ino -> integer
771 *
772 * Returns the inode value for the entry
773 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
774 *
775 * ```ruby
776 * File.stat('/etc').ino # => 11141121
777 * File.stat('/etc/passwd').ino # => 11141413
778 * ```
779 *
780 */
781
782static VALUE
783rb_stat_ino(VALUE self)
784{
785 rb_io_stat_data *ptr = get_stat(self);
786#ifdef HAVE_STRUCT_STAT_ST_INOHIGH
787 /* assume INTEGER_PACK_LSWORD_FIRST and st_inohigh is just next of st_ino */
788 return rb_integer_unpack(&ptr->st_ino, 2,
789 SIZEOF_STRUCT_STAT_ST_INO, 0,
792#else
793 return UIANY2NUM(ptr->ST_(ino));
794#endif
795}
796
797/*
798 * :markup: markdown
799 *
800 * call-seq:
801 * mode -> integer
802 *
803 * Returns an integer representing the permissions of the entry
804 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`;
805 * see [Filesystem Modes](rdoc-ref:file/filesystem_modes.md).
806 *
807 * The integer is most usefully formatted as six octal digits:
808 *
809 * ```ruby
810 * '%06o' % File.stat('/etc').mode # => "040755"
811 * '%06o' % File.stat('/etc/passwd').mode # => "100644"
812 * ```
813 *
814 */
815
816static VALUE
817rb_stat_mode(VALUE self)
818{
819 return UINT2NUM(ST2UINT(get_stat(self)->ST_(mode)));
820}
821
822/*
823 * :markup: markdown
824 *
825 * call-seq:
826 * nlink -> integer
827 *
828 * Returns the number of [hard links](rdoc-ref:file/hard_links.md) to the entry
829 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
830 *
831 * ```ruby
832 * filepath = '/tmp/t.txt'
833 * File.write(filepath, 'foo') # Creates hard link.
834 * stat0 = File.stat(filepath) # Take snapshot.
835 * stat0.nlink # => 1 # One hard link.
836 * linkpath = '/tmp/t.bak'
837 * File.link(filepath, linkpath) # Creates another hard link.
838 * stat0.nlink # => 1 # Snapshot not updated.
839 * stat1 = File.stat(filepath) # Take new snapshot.
840 * stat1.nlink # => 2 # Updated nlink count.
841 * File.unlink(linkpath) # Remove one hard link.
842 * stat1.nlink # => 2 # Snapshot not updated.
843 * stat2 = File.stat(filepath) # Take new shapshot.
844 * stat2.nlink # => 1 # Updated nlink count.
845 * File.unlink(filepath) # Clean up.
846 * ```
847 *
848 */
849
850static VALUE
851rb_stat_nlink(VALUE self)
852{
853 /* struct stat::st_nlink is nlink_t in POSIX. Not the case for Windows. */
854 const rb_io_stat_data *ptr = get_stat(self);
855
856 return UIANY2NUM(ptr->ST_(nlink));
857}
858
859/*
860 * :markup: markdown
861 *
862 * call-seq:
863 * uid -> integer
864 *
865 * Returns the integer id of the owner of the entry
866 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
867 *
868 * ```ruby
869 * Process.uid # => 0 # Superuser; can change owner.
870 * filepath = '/tmp/t.tmp'
871 * File.write(filepath, 'foo') # Create file.
872 * stat0 = File.stat(filepath) # Take snapshot.
873 * stat0.uid # => 0 # Snapshot has uid.
874 * File.chown(1000, 1000, filepath) # Change owner.
875 * stat0.uid # => 0 # Snapshot not updated.
876 * stat1 = File.stat(filepath) # Take new snapshot.
877 * stat1.uid # => 1000 # Snapshot has new uid.
878 * File.delete(filepath) # Clean up.
879 * ```
880 *
881 */
882
883static VALUE
884rb_stat_uid(VALUE self)
885{
886 return UIDT2NUM(get_stat(self)->ST_(uid));
887}
888
889/*
890 * :markup: markdown
891 *
892 * call-seq:
893 * gid -> integer
894 *
895 * On Windows, returns `0`.
896 *
897 * On other systems, returns the group identifier of the owner of the entry
898 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
899 *
900 * ```ruby
901 * File.stat('/etc').gid # => 0
902 * filepath = '/tmp/t.tmp'
903 * File.write(filepath, 'foo')
904 * File.stat(filepath).gid # => 1000
905 * File.delete(filepath) # Clean up.
906 * ```
907 *
908 */
909
910static VALUE
911rb_stat_gid(VALUE self)
912{
913 return GIDT2NUM(get_stat(self)->ST_(gid));
914}
915
916/*
917 * :markup: markdown
918 *
919 * call-seq:
920 * rdev -> integer or nil
921 *
922 * Returns the integer device type of the entry
923 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`.
924 *
925 * On a non-Windows system, returns a filesystem-dependent device type;
926 * the returned value is non-zero only for character-special
927 * and block-special devices.
928 *
929 * In these hexadecimal-formatted examples:
930 *
931 * - The two-low order digits contain the device-type minor value,
932 * which identifies a specific instance, port, or partition managed by a device driver;
933 * see #rdev_minor.
934 * - The higher-order digits contain the device-type major value,
935 * which identifies the device driver;
936 * see #rdev_major.
937 *
938 * ```ruby
939 * # Character-special devices.
940 * '%08x' % File.stat('/dev/null').rdev # => "00000103"
941 * '%08x' % File.stat('/dev/zero').rdev # => "00000105"
942 * '%08x' % File.stat('/dev/random').rdev # => "00000108"
943 * '%08x' % File.stat('/dev/tty').rdev # => "00000500"
944 * '%08x' % File.stat('/dev/tty0').rdev # => "00000400"
945 * '%08x' % File.stat('/dev/tty1').rdev # => "00000401"
946 * # Block-special devices.
947 * '%08x' % File.stat('/dev/nvme0n1').rdev # => "00010300"
948 * '%08x' % File.stat('/dev/nvme0n1p1').rdev # => "00010301"
949 * '%08x' % File.stat('/dev/nvme0n1p2').rdev # => "00010302"
950 * ```
951 *
952 * On Windows, returns a number corresponding to the given device:
953 *
954 * ```ruby
955 * File.stat('C:/').rdev # => 2
956 * File.stat('C:/Users').rdev # => 2
957 * File.stat('D:/').rdev # => 3
958 * ```
959 *
960 */
961
962static VALUE
963rb_stat_rdev(VALUE self)
964{
965#if RUBY_USE_STATX
966 unsigned int m = get_stat(self)->stx_rdev_major;
967 unsigned int n = get_stat(self)->stx_rdev_minor;
968 return ULL2NUM(makedev(m, n));
969#elif !defined(HAVE_STRUCT_STAT_ST_RDEV)
970 return Qnil;
971#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T
972 return DEVT2NUM(get_stat(self)->ST_(rdev));
973#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG
974 return ULONG2NUM(get_stat(self)->ST_(rdev));
975#else
976 return ULL2NUM(get_stat(self)->ST_(rdev));
977#endif
978}
979
980/*
981 * :markup: markdown
982 *
983 * call-seq:
984 * rdev_major -> integer
985 *
986 * Returns the integer major portion of the device type of the entry
987 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`;
988 * see also #rdev, which returns the entire device type (major and minor).
989 *
990 * On a non-Windows system, returns a filesystem-dependent major device type,
991 * which identifies the device driver;
992 * the returned value is non-zero only for character-special
993 * and block-special devices:
994 *
995 * ```ruby
996 * # Character-special devices.
997 * '%04x' % File.stat('/dev/null').rdev_major # => "0001"
998 * '%04x' % File.stat('/dev/zero').rdev_major # => "0001"
999 * '%04x' % File.stat('/dev/random').rdev_major # => "0001"
1000 * '%04x' % File.stat('/dev/tty').rdev_major # => "0005"
1001 * '%04x' % File.stat('/dev/tty0').rdev_major # => "0004"
1002 * '%04x' % File.stat('/dev/tty1').rdev_major # => "0004"
1003 * # Block-special devices.
1004 * '%04x' % File.stat('/dev/nvme0n1').rdev_major # => "0103"
1005 * '%04x' % File.stat('/dev/nvme0n1p1').rdev_major # => "0103"
1006 * '%04x' % File.stat('/dev/nvme0n1p2').rdev_major # => "0103"
1007 * ```
1008 *
1009 * On Windows, returns `nil`.
1010 */
1011
1012static VALUE
1013rb_stat_rdev_major(VALUE self)
1014{
1015#if RUBY_USE_STATX
1016 return UINT2NUM(get_stat(self)->stx_rdev_major);
1017#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major)
1018 return UINT2NUM(major(get_stat(self)->ST_(rdev)));
1019#else
1020 return Qnil;
1021#endif
1022}
1023
1024/*
1025 * :markup: markdown
1026 *
1027 * call-seq:
1028 * rdev_minor -> integer
1029 *
1030 * Returns the integer minor portion of the device type of the entry
1031 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`;
1032 * see also #rdev, which returns the entire device type (major and minor).
1033 *
1034 * On a non-Windows system, returns a filesystem-dependent minor device type,
1035 * which identifies a specific instance, port, or partition on its device;
1036 * the returned value is non-zero only for character-special
1037 * and block-special devices:
1038 *
1039 * ```ruby
1040 * # Character-special devices.
1041 * '%02x' % File.stat('/dev/null').rdev_minor # => "03"
1042 * '%02x' % File.stat('/dev/zero').rdev_minor # => "05"
1043 * '%02x' % File.stat('/dev/random').rdev_minor # => "08"
1044 * '%02x' % File.stat('/dev/tty').rdev_minor # => "00"
1045 * '%02x' % File.stat('/dev/tty0').rdev_minor # => "00"
1046 * '%02x' % File.stat('/dev/tty1').rdev_minor # => "01"
1047 * # Block-special devices.
1048 * '%02x' % File.stat('/dev/nvme0n1').rdev_minor # => "00"
1049 * '%02x' % File.stat('/dev/nvme0n1p1').rdev_minor # => "01"
1050 * '%02x' % File.stat('/dev/nvme0n1p2').rdev_minor # => "02"
1051 * ```
1052 *
1053 * On Windows, returns `nil`.
1054 */
1055
1056static VALUE
1057rb_stat_rdev_minor(VALUE self)
1058{
1059#if RUBY_USE_STATX
1060 return UINT2NUM(get_stat(self)->stx_rdev_minor);
1061#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor)
1062 return UINT2NUM(minor(get_stat(self)->ST_(rdev)));
1063#else
1064 return Qnil;
1065#endif
1066}
1067
1068/*
1069 * :markup: markdown
1070 *
1071 * call-seq:
1072 * size -> integer
1073 *
1074 * Returns the size (in bytes) of the entry
1075 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
1076 *
1077 * ```ruby
1078 * File.stat('/etc/passwd').size # => 3092 # Regular file.
1079 * File.stat('/etc').size # => 12288 # Directory.
1080 * # When the file size changes.
1081 * filepath = '/tmp/t.tmp'
1082 * file = File.new(filepath, 'w+')
1083 * file.write('foo')
1084 * file.size # => 3
1085 * stat0 = File.stat(filepath) # Take snapshot.
1086 * stat0.size # => 3
1087 * file.write('bar') # Change file size.
1088 * file.size # => 6
1089 * stat0.size # => 3 # Snapshot not updated.
1090 * stat1 = File.stat(filepath) # Take new snapshot.
1091 * stat1.size # => 6 # Snapshot has updated size.
1092 * File.delete(filepath) # Clean up.
1093 * ```
1094 *
1095 */
1096
1097static VALUE
1098rb_stat_size(VALUE self)
1099{
1100 return OFFT2NUM(get_stat(self)->ST_(size));
1101}
1102
1103/*
1104 * :markup: markdown
1105 *
1106 * call-seq:
1107 * blksize -> integer or nil
1108 *
1109 * Returns the block size of the filesystem
1110 * of the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`,
1111 * or `nil` on Windows:
1112 *
1113 * ```ruby
1114 * File.stat('/etc').blksize # => 4096
1115 * File.stat('/etc/passwd').blksize # => 4096
1116 * File.stat('nosuch').blksize # Raises Errno::ENOENT: No such file or directory.
1117 * ```
1118 *
1119 */
1120
1121static VALUE
1122rb_stat_blksize(VALUE self)
1123{
1124#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
1125 return ULONG2NUM(get_stat(self)->ST_(blksize));
1126#else
1127 return Qnil;
1128#endif
1129}
1130
1131/*
1132 * :markup: markdown
1133 *
1134 * call-seq:
1135 * blocks -> integer or nil
1136 *
1137 * Returns the number of filesystem blocks allocated for the entry
1138 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
1139 *
1140 * ```ruby
1141 * File.stat('/etc').blocks # => 24
1142 * File.stat('/etc/passwd').blocks # => 8
1143 * ```
1144 *
1145 * Returns `nil` on Windows.
1146 */
1147
1148static VALUE
1149rb_stat_blocks(VALUE self)
1150{
1151#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
1152# if SIZEOF_STRUCT_STAT_ST_BLOCKS > SIZEOF_LONG
1153 return ULL2NUM(get_stat(self)->ST_(blocks));
1154# else
1155 return ULONG2NUM(get_stat(self)->ST_(blocks));
1156# endif
1157#else
1158 return Qnil;
1159#endif
1160}
1161
1162static stat_timestamp
1163stat_atimespec(const struct stat *st)
1164{
1165 stat_timestamp ts;
1166 ts.tv_sec = st->st_atime;
1167#if defined(HAVE_STRUCT_STAT_ST_ATIM)
1168 ts.tv_nsec = (uint32_t)st->st_atim.tv_nsec;
1169#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
1170 ts.tv_nsec = (uint32_t)st->st_atimespec.tv_nsec;
1171#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC)
1172 ts.tv_nsec = (uint32_t)st->st_atimensec;
1173#else
1174 ts.tv_nsec = 0
1175#endif
1176 return ts;
1177}
1178
1179#if RUBY_USE_STATX
1180static stat_timestamp
1181statx_atimespec(const rb_io_stat_data *st)
1182{
1183 return st->stx_atime;
1184}
1185#else
1186# define statx_atimespec stat_atimespec
1187#endif
1188
1189static VALUE
1190stat_time(const stat_timestamp ts)
1191{
1192 return rb_time_nano_new(ts.tv_sec, ts.tv_nsec);
1193}
1194
1195static VALUE
1196stat_atime(const struct stat *st)
1197{
1198 return stat_time(stat_atimespec(st));
1199}
1200
1201static stat_timestamp
1202stat_mtimespec(const struct stat *st)
1203{
1204 stat_timestamp ts;
1205 ts.tv_sec = st->st_mtime;
1206#if defined(HAVE_STRUCT_STAT_ST_MTIM)
1207 ts.tv_nsec = (uint32_t)st->st_mtim.tv_nsec;
1208#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
1209 ts.tv_nsec = (uint32_t)st->st_mtimespec.tv_nsec;
1210#elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC)
1211 ts.tv_nsec = (uint32_t)st->st_mtimensec;
1212#else
1213 ts.tv_nsec = 0;
1214#endif
1215 return ts;
1216}
1217
1218static VALUE
1219stat_mtime(const struct stat *st)
1220{
1221 return stat_time(stat_mtimespec(st));
1222}
1223
1224static stat_timestamp
1225stat_ctimespec(const struct stat *st)
1226{
1227 stat_timestamp ts;
1228 ts.tv_sec = st->st_ctime;
1229#if defined(HAVE_STRUCT_STAT_ST_CTIM)
1230 ts.tv_nsec = (uint32_t)st->st_ctim.tv_nsec;
1231#elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC)
1232 ts.tv_nsec = (uint32_t)st->st_ctimespec.tv_nsec;
1233#elif defined(HAVE_STRUCT_STAT_ST_CTIMENSEC)
1234 ts.tv_nsec = (uint32_t)st->st_ctimensec;
1235#else
1236 ts.tv_nsec = 0;
1237#endif
1238 return ts;
1239}
1240
1241#if RUBY_USE_STATX
1242static stat_timestamp
1243statx_ctimespec(const rb_io_stat_data *st)
1244{
1245 return st->stx_ctime;
1246}
1247#else
1248# define statx_ctimespec stat_ctimespec
1249#endif
1250
1251static VALUE
1252stat_ctime(const struct stat *st)
1253{
1254 return stat_time(stat_ctimespec(st));
1255}
1256
1257#define HAVE_STAT_BIRTHTIME
1258#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC)
1259static VALUE
1260statx_birthtime(const rb_io_stat_data *st)
1261{
1262 const stat_timestamp *ts = &st->ST_(birthtimespec);
1263 return rb_time_nano_new(ts->tv_sec, ts->tv_nsec);
1264}
1265#elif defined(HAVE_STRUCT_STATX_STX_BTIME)
1266static VALUE statx_birthtime(const rb_io_stat_data *st);
1267#elif defined(_WIN32)
1268# define statx_birthtime stat_ctime
1269#else
1270# undef HAVE_STAT_BIRTHTIME
1271#endif /* defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) */
1272
1273/*
1274 * :markup: markdown
1275 *
1276 * call-seq:
1277 * atime -> time
1278 *
1279 * Returns a new Time object containing the access time
1280 * of the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`.
1281 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
1282 *
1283 * Access time for a file is established when it is created,
1284 * and may be updated when the file content is read:
1285 *
1286 * ```ruby
1287 * filepath = '/tmp/t.tmp'
1288 * File.write(filepath, 'foo')
1289 * File.atime(filepath) # => 2026-10-04 14:58:58.805179514 -0500
1290 * stat0 = File.stat(filepath) # Take snapshot.
1291 * stat0.atime # => 2026-10-04 14:58:58.805179514 -0500
1292 * File.read(filepath) # Updates file atime.
1293 * File.atime(filepath) # => 2026-10-04 14:59:47.707059351 -0500
1294 * stat0.atime # => 2026-10-04 14:58:58.805179514 -0500 # Not updated.
1295 * stat1 = File.stat(filepath) # Take new snapshot.
1296 * stat1.atime # => 2026-10-04 14:59:47.707059351 -0500 # Updated.
1297 * File.delete(filepath) # Clean up.
1298 * ```
1299 *
1300 * Access time for a directory is established when it is created,
1301 * and may be updated when its entries are read:
1302 *
1303 * ```ruby
1304 * dirpath = '/tmp/dir'
1305 * Dir.mkdir(dirpath)
1306 * File.atime(dirpath) # => 2026-10-04 15:07:26.194548647 -0500
1307 * stat0 = File.stat(dirpath) # Take snapshot.
1308 * stat0.atime # => 2026-10-04 15:07:26.194548647 -0500
1309 * Dir.entries(dirpath) # Updates directory atime.
1310 * File.atime(dirpath) # => 2026-10-04 15:08:14.818746815 -0500 # Updated.
1311 * stat0.atime # => 2026-10-04 15:07:26.194548647 -0500 # Not updated.
1312 * stat1 = File.stat(dirpath) # Take new shapshot.
1313 * stat1.atime # => 2026-10-04 15:08:14.818746815 -0500 # Updated.
1314 * Dir.rmdir(dirpath) # Clean up.
1315 *
1316 */
1317
1318static VALUE
1319rb_stat_atime(VALUE self)
1320{
1321 return stat_time(statx_atimespec(get_stat(self)));
1322}
1323
1324/*
1325 * :markup: markdown
1326
1327 * call-seq:
1328 * mtime -> time
1329 *
1330 * Returns a new Time object containing
1331 * the [modification time](rdoc-ref:file/timestamps.md@Modification+Time)
1332 * of the object in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
1333 *
1334 * ```ruby
1335 * filepath = '/tmp/t.tmp'
1336 * File.write(filepath, 'foo') # Create file.
1337 * File.mtime(filepath) # => 2026-10-13 10:48:10.477675435 -0500 # File mtime.
1338 * stat0 = File.stat(filepath) # Take snapshot.
1339 * stat0.mtime # => 2026-10-13 10:48:10.477675435 -0500 # Snapshot mtime.
1340 * File.write(filepath, 'bar') # Modify file.
1341 * File.mtime(filepath) # => 2026-10-13 10:48:37.584264035 -0500 # File mtime updated.
1342 * stat0.mtime # => 2026-10-13 10:48:10.477675435 -0500 # Snapshot mtime not updated.
1343 * stat1 = File.stat(filepath) # Take new snapshot.
1344 * stat1.mtime # => 2026-10-13 10:48:37.584264035 -0500 # New snapshot mtime.
1345 * File.delete(filepath) # Clean up.
1346 * ```
1347 *
1348 */
1349
1350static VALUE
1351rb_stat_mtime(VALUE self)
1352{
1353 return stat_time(statx_mtimespec(get_stat(self)));
1354}
1355
1356/*
1357 * :markup: markdown
1358 *
1359 * call-seq:
1360 * ctime -> time
1361 *
1362 * On Windows, returns the birthtime for the entry in `self`.
1363 *
1364 * On other systems, returns the time of the most recent metadata change
1365 * to the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
1366 *
1367 * ```ruby
1368 * dirpath = '/tmp/dir'
1369 * Dir.mkdir(dirpath) # Establishes directory ctime.
1370 * dirstat0 = File.stat(dirpath) # Take directory snapshot.
1371 * dirstat0.ctime # => 2026-10-06 11:07:18.073533542 -0500 # Initial directory ctime.
1372 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
1373 * File.write(filepath, 'foo') # Establishes file ctime; upates directory ctime.
1374 * filestat0 = File.stat(filepath) # Take file snapshot.
1375 * filestat0.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Initial file ctime.
1376 * dirstat0.ctime # => 2026-10-06 11:07:18.073533542 -0500 # Directory snapshot unchanged.
1377 * dirstat1 = File.stat(dirpath) # Take new directory snapshot.
1378 * dirstat1.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Snapshot has new ctime.
1379 * File.write(filepath, 'bar') # Updates file ctime.
1380 * filestat0.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Old file snapshot unchanged.
1381 * filestat1 = File.stat(filepath) # Take new file snapshot.
1382 * filestat1.ctime # => 2026-10-06 11:14:11.19290223 -0500 # Snapshot shows change.
1383 * FileUtils.rmtree(dirpath) # Clean up.
1384 * ```
1385 *
1386 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
1387 */
1388
1389static VALUE
1390rb_stat_ctime(VALUE self)
1391{
1392 return stat_time(statx_ctimespec(get_stat(self)));
1393}
1394
1395#if defined(HAVE_STAT_BIRTHTIME)
1396/*
1397 * :markup: markdown
1398 *
1399 * call-seq:
1400 * birthtime -> new_time
1401 *
1402 * Returns a new Time object containing the creation time
1403 * of the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`.
1404 * See {File System Timestamps}[rdoc-ref:file/timestamps.md]:
1405 *
1406 * ```ruby
1407 * filepath = '/tmp/t.tmp'
1408 * File.write(filepath, 'foo')
1409 * stat = File.stat(filepath) # Take shapshot.
1410 * stat.birthtime # => 2026-10-05 13:14:24.844158315 -0500
1411 * File.delete(filepath)
1412 * stat.birthtime # => 2026-10-05 13:14:24.844158315 -0500 # Unchanged.
1413 * ```
1414 *
1415 */
1416
1417static VALUE
1418rb_stat_birthtime(VALUE self)
1419{
1420 return statx_birthtime(get_stat(self));
1421}
1422#else
1423# define rb_stat_birthtime rb_f_notimplement
1424#endif
1425
1426/*
1427 * :markup: markdown
1428 *
1429 * call-seq:
1430 * inspect -> string
1431 *
1432 * Returns a string representation of the entry
1433 * in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`;
1434 * here we split the returned strings for readability:
1435 *
1436 * ```ruby
1437 * puts File.stat('/etc/passwd').inspect.split(', ')
1438 * ```
1439 *
1440 * Output:
1441 *
1442 * ```text
1443 * #<File::Stat dev=0x10302
1444 * ino=11141413
1445 * mode=0100644
1446 * nlink=1
1447 * uid=0
1448 * gid=0
1449 * rdev=0x0
1450 * size=3092
1451 * blksize=4096
1452 * blocks=8
1453 * atime=2026-10-07 11:25:01.307589569 -0500
1454 * mtime=2025-06-10 11:10:47.358999941 -0500
1455 * ctime=2025-06-10 11:10:47.38899994 -0500>
1456 * ```
1457 *
1458 * ```ruby
1459 * puts File.stat('/etc').inspect.split(', ')
1460 * ```
1461 *
1462 * Output:
1463 *
1464 * ```text
1465 * #<File::Stat dev=0x10302
1466 * ino=11141121
1467 * mode=040755
1468 * nlink=143
1469 * uid=0
1470 * gid=0
1471 * rdev=0x0
1472 * size=12288
1473 * blksize=4096
1474 * blocks=24
1475 * atime=2026-10-04 14:29:46.907775905 -0500
1476 * mtime=2026-10-07 06:13:35.116915307 -0500
1477 * ctime=2026-10-07 06:13:35.116915307 -0500>
1478 * ```
1479 *
1480 */
1481
1482static VALUE
1483rb_stat_inspect(VALUE self)
1484{
1485 VALUE str;
1486 size_t i;
1487 static const struct {
1488 const char *name;
1489 VALUE (*func)(VALUE);
1490 } member[] = {
1491 {"dev", rb_stat_dev},
1492 {"ino", rb_stat_ino},
1493 {"mode", rb_stat_mode},
1494 {"nlink", rb_stat_nlink},
1495 {"uid", rb_stat_uid},
1496 {"gid", rb_stat_gid},
1497 {"rdev", rb_stat_rdev},
1498 {"size", rb_stat_size},
1499 {"blksize", rb_stat_blksize},
1500 {"blocks", rb_stat_blocks},
1501 {"atime", rb_stat_atime},
1502 {"mtime", rb_stat_mtime},
1503 {"ctime", rb_stat_ctime},
1504#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC)
1505 {"birthtime", rb_stat_birthtime},
1506#endif
1507 };
1508
1509 struct rb_stat* rb_st;
1510 TypedData_Get_Struct(self, struct rb_stat, &stat_data_type, rb_st);
1511 if (!rb_st->initialized) {
1512 return rb_sprintf("#<%s: uninitialized>", rb_obj_classname(self));
1513 }
1514
1515 str = rb_str_buf_new2("#<");
1517 rb_str_buf_cat2(str, " ");
1518
1519 for (i = 0; i < sizeof(member)/sizeof(member[0]); i++) {
1520 VALUE v;
1521
1522 if (i > 0) {
1523 rb_str_buf_cat2(str, ", ");
1524 }
1525 rb_str_buf_cat2(str, member[i].name);
1526 rb_str_buf_cat2(str, "=");
1527 v = (*member[i].func)(self);
1528 if (i == 2) { /* mode */
1529 rb_str_catf(str, "0%lo", (unsigned long)NUM2ULONG(v));
1530 }
1531 else if (i == 0 || i == 6) { /* dev/rdev */
1532 rb_str_catf(str, "0x%"PRI_DEVT_PREFIX"x", NUM2DEVT(v));
1533 }
1534 else {
1535 rb_str_append(str, rb_inspect(v));
1536 }
1537 }
1538 rb_str_buf_cat2(str, ">");
1539
1540 return str;
1541}
1542
1543typedef struct no_gvl_stat_data {
1544 struct stat *st;
1545 union {
1546 const char *path;
1547 int fd;
1548 } file;
1550
1551static VALUE
1552no_gvl_fstat(void *data)
1553{
1554 no_gvl_stat_data *arg = data;
1555 return (VALUE)fstat(arg->file.fd, arg->st);
1556}
1557
1558static int
1559fstat_without_gvl(rb_io_t *fptr, struct stat *st)
1560{
1561 no_gvl_stat_data data;
1562
1563 data.file.fd = fptr->fd;
1564 data.st = st;
1565
1566 return (int)rb_io_blocking_region(fptr, no_gvl_fstat, &data);
1567}
1568
1569static void *
1570no_gvl_stat(void * data)
1571{
1572 no_gvl_stat_data *arg = data;
1573 return (void *)(VALUE)stat(arg->file.path, arg->st);
1574}
1575
1576static int
1577stat_without_gvl(const char *path, struct stat *st)
1578{
1579 no_gvl_stat_data data;
1580
1581 data.file.path = path;
1582 data.st = st;
1583
1584 return IO_WITHOUT_GVL_INT(no_gvl_stat, &data);
1585}
1586
1587#if !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \
1588 defined(HAVE_STRUCT_STATX_STX_BTIME)
1589
1590# define STATX(path, st, mask) statx(AT_FDCWD, path, 0, mask, st)
1591
1592# ifndef HAVE_STATX
1593# ifdef HAVE_SYSCALL_H
1594# include <syscall.h>
1595# elif defined HAVE_SYS_SYSCALL_H
1596# include <sys/syscall.h>
1597# endif
1598# if defined __linux__
1599# include <linux/stat.h>
1600static inline int
1601statx(int dirfd, const char *pathname, int flags,
1602 unsigned int mask, struct statx *statxbuf)
1603{
1604 return (int)syscall(__NR_statx, dirfd, pathname, flags, mask, statxbuf);
1605}
1606# endif /* __linux__ */
1607# endif /* HAVE_STATX */
1608
1609typedef struct no_gvl_rb_io_stat_data {
1610 struct statx *stx;
1611 int fd;
1612 const char *path;
1613 int flags;
1614 unsigned int mask;
1615} no_gvl_rb_io_stat_data;
1616
1617static VALUE
1618io_blocking_statx(void *data)
1619{
1620 no_gvl_rb_io_stat_data *arg = data;
1621 return (VALUE)statx(arg->fd, arg->path, arg->flags, arg->mask, arg->stx);
1622}
1623
1624static void *
1625no_gvl_statx(void *data)
1626{
1627 return (void *)io_blocking_statx(data);
1628}
1629
1630static int
1631statx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask)
1632{
1633 no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, 0, mask};
1634
1635 /* call statx(2) with pathname */
1636 return IO_WITHOUT_GVL_INT(no_gvl_statx, &data);
1637}
1638
1639static int
1640lstatx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask)
1641{
1642 no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask};
1643
1644 /* call statx(2) with pathname */
1645 return IO_WITHOUT_GVL_INT(no_gvl_statx, &data);
1646}
1647
1648static int
1649fstatx_without_gvl(rb_io_t *fptr, rb_io_stat_data *stx, unsigned int mask)
1650{
1651 no_gvl_rb_io_stat_data data = {stx, fptr->fd, "", AT_EMPTY_PATH, mask};
1652
1653 /* call statx(2) with fd */
1654 return (int)rb_io_blocking_region(fptr, io_blocking_statx, &data);
1655}
1656
1657#define FSTATX(fd, st) statx(fd, "", AT_EMPTY_PATH, STATX_ALL, st)
1658
1659static int
1660rb_statx(VALUE file, struct statx *stx, unsigned int mask)
1661{
1662 VALUE tmp;
1663 int result;
1664
1665 tmp = rb_check_convert_type_with_id(file, T_FILE, "IO", idTo_io);
1666 if (!NIL_P(tmp)) {
1667 rb_io_t *fptr;
1668
1669 GetOpenFile(tmp, fptr);
1670 result = fstatx_without_gvl(fptr, stx, mask);
1671 file = tmp;
1672 }
1673 else {
1674 FilePathValue(file);
1675 file = rb_str_encode_ospath(file);
1676 result = statx_without_gvl(RSTRING_PTR(file), stx, mask);
1677 }
1678 RB_GC_GUARD(file);
1679 return result;
1680}
1681
1682# define statx_has_birthtime(st) ((st)->stx_mask & STATX_BTIME)
1683
1684NORETURN(static void statx_notimplement(const char *field_name));
1685
1686/* rb_notimplement() shows "function is unimplemented on this machine".
1687 It is not applicable to statx which behavior depends on the filesystem. */
1688static void
1689statx_notimplement(const char *field_name)
1690{
1691 rb_raise(rb_eNotImpError,
1692 "%s is unimplemented on this filesystem",
1693 field_name);
1694}
1695
1696static VALUE
1697statx_birthtime(const rb_io_stat_data *stx)
1698{
1699 if (!statx_has_birthtime(stx)) {
1700 /* birthtime is not supported on the filesystem */
1701 statx_notimplement("birthtime");
1702 }
1703 return rb_time_nano_new((time_t)stx->stx_btime.tv_sec, stx->stx_btime.tv_nsec);
1704}
1705
1706#else
1707
1708# define statx_without_gvl(path, st, mask) stat_without_gvl(path, st)
1709# define fstatx_without_gvl(fptr, st, mask) fstat_without_gvl(fptr, st)
1710# define lstatx_without_gvl(path, st, mask) lstat_without_gvl(path, st)
1711# define rb_statx(file, stx, mask) rb_stat(file, stx)
1712# define STATX(path, st, mask) stat(path, st)
1713
1714#if defined(HAVE_STAT_BIRTHTIME)
1715# define statx_has_birthtime(st) 1
1716#else
1717# define statx_has_birthtime(st) 0
1718#endif
1719
1720#endif /* !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \
1721 defined(HAVE_STRUCT_STATX_STX_BTIME) */
1722
1723#ifndef FSTAT
1724# define FSTAT(fd, st) fstat(fd, st)
1725#endif
1726
1727static int
1728rb_stat(VALUE file, struct stat *st)
1729{
1730 VALUE tmp;
1731 int result;
1732
1733 tmp = rb_check_convert_type_with_id(file, T_FILE, "IO", idTo_io);
1734 if (!NIL_P(tmp)) {
1735 rb_io_t *fptr;
1736
1737 GetOpenFile(tmp, fptr);
1738 result = fstat_without_gvl(fptr, st);
1739 file = tmp;
1740 }
1741 else {
1742 FilePathValue(file);
1743 file = rb_str_encode_ospath(file);
1744 result = stat_without_gvl(RSTRING_PTR(file), st);
1745 }
1746 RB_GC_GUARD(file);
1747 return result;
1748}
1749
1750/*
1751 * :markup: markdown
1752 *
1753 * call-seq:
1754 * File.stat(path) -> stat
1755 *
1756 * Returns a new File::Stat object for the entry at `path`.
1757 * Unlike File.lstat, _does follow_ [symbolic links](file/symbolic_links.md);
1758 * therefore if the entry is a symbolic link,
1759 * the returned object contains information for the target entry, not the symbolic link:
1760 *
1761 * ```ruby
1762 * filepath = '/etc/passwd'
1763 * linkpath = '/tmp/foo'
1764 * File.symlink(filepath, linkpath)
1765 * # Method File.stat follows the symlink, so the birthtimes are the same.
1766 * File.stat(filepath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1767 * File.stat(linkpath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1768 * # Method File.lstat does not follow the symlink, so the birthtimes are different.
1769 * File.lstat(filepath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1770 * File.lstat(linkpath).birthtime # => 2026-09-23 14:01:18.248754979 -0500
1771 * File.delete(linkpath) # Clean up.
1772 * ```
1773 *
1774 */
1775
1776static VALUE
1777rb_file_s_stat(VALUE klass, VALUE fname)
1778{
1779 rb_io_stat_data st;
1780
1781 FilePathValue(fname);
1782 fname = rb_str_encode_ospath(fname);
1783 if (statx_without_gvl(RSTRING_PTR(fname), &st, STATX_ALL) < 0) {
1784 rb_sys_fail_path(fname);
1785 }
1786 return rb_statx_new(&st);
1787}
1788
1789/*
1790 * :markup: markdown
1791 *
1792 * call-seq:
1793 * stat -> stat
1794 *
1795 * Returns a new File::Stat object for `self`.
1796 * Unlike File#lstat, _does follow_ symbolic links;
1797 * therefore if `self` is a symbolic link,
1798 * the returned object contains information for the target entry, not `self`:
1799 *
1800 * ```ruby
1801 * file_path = '/etc/passwd'
1802 * link_path = '/tmp/foo'
1803 * File.symlink(file_path, link_path)
1804 * file = File.new(file_path) # => #<File:/etc/passwd>
1805 * link = File.new(link_path) # => #<File:/tmp/foo>
1806 * # Method stat does follow the symlink, so the birthtimes are the same.
1807 * file.stat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1808 * link.stat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1809 * # Method lstat does not follow the symlink, so the birthtimes are different.
1810 * file.lstat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1811 * link.lstat.birthtime # => 2026-09-23 16:35:59.321566598 -0500
1812 * File.delete(link_path) # Clean up.
1813 * ```
1814 *
1815 */
1816
1817static VALUE
1818rb_io_stat(VALUE obj)
1819{
1820 rb_io_t *fptr;
1821 rb_io_stat_data st;
1822
1823 GetOpenFile(obj, fptr);
1824 if (fstatx_without_gvl(fptr, &st, STATX_ALL) == -1) {
1825 rb_sys_fail_path(fptr->pathv);
1826 }
1827 return rb_statx_new(&st);
1828}
1829
1830#ifdef HAVE_LSTAT
1831static void *
1832no_gvl_lstat(void *ptr)
1833{
1834 no_gvl_stat_data *arg = ptr;
1835 return (void *)(VALUE)lstat(arg->file.path, arg->st);
1836}
1837
1838static int
1839lstat_without_gvl(const char *path, struct stat *st)
1840{
1841 no_gvl_stat_data data;
1842
1843 data.file.path = path;
1844 data.st = st;
1845
1846 return IO_WITHOUT_GVL_INT(no_gvl_lstat, &data);
1847}
1848#endif /* HAVE_LSTAT */
1849
1850/*
1851 * :markup: markdown
1852 *
1853 * call-seq:
1854 * File.lstat(path) -> stat
1855 *
1856 * Returns a new File::Stat object for the entry at `path`.
1857 * Unline File.stat, _does not follow_ [symbolic links](file/symbolic_links.md);
1858 * therefore the returned object contains information for the entry at `path`,
1859 * regardless of whether is a symbolic link:
1860 *
1861 * ```ruby
1862 * filepath = '/etc/passwd'
1863 * linkpath = '/tmp/foo'
1864 * File.symlink(filepath, linkpath)
1865 * # Method File.lstat does not follow the symlink, so the birthtimes are different.
1866 * File.lstat(filepath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1867 * File.lstat(linkpath).birthtime # => 2026-09-23 14:01:18.248754979 -0500
1868 * # Method File.stat follows the symlink, so the birthtimes are the same.
1869 * File.stat(filepath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1870 * File.stat(linkpath).birthtime # => 2025-06-10 11:10:47.358999941 -0500
1871 * File.delete(linkpath) # Clean up.
1872 * ```
1873 *
1874 */
1875
1876static VALUE
1877rb_file_s_lstat(VALUE klass, VALUE fname)
1878{
1879#ifdef HAVE_LSTAT
1880 rb_io_stat_data st;
1881
1882 FilePathValue(fname);
1883 fname = rb_str_encode_ospath(fname);
1884 if (lstatx_without_gvl(StringValueCStr(fname), &st, STATX_ALL) == -1) {
1885 rb_sys_fail_path(fname);
1886 }
1887 return rb_statx_new(&st);
1888#else
1889 return rb_file_s_stat(klass, fname);
1890#endif
1891}
1892
1893/*
1894 * :markup: markdown
1895
1896 * call-seq:
1897 * lstat -> stat
1898 *
1899 * Returns a new File::Stat object for `self`.
1900 * Unlike File#stat, _does not follow_ symbolic links;
1901 * therefore if `self` is a symbolic link,
1902 * the returned object contains information for `self`, not the target entry:
1903 *
1904 * ```ruby
1905 * file_path = '/etc/passwd'
1906 * link_path = '/tmp/foo'
1907 * File.symlink(file_path, link_path)
1908 * file = File.new(file_path) # => #<File:/etc/passwd>
1909 * link = File.new(link_path) # => #<File:/tmp/foo>
1910 * # Method lstat does not follow the symlink, so the birthtimes are different.
1911 * file.lstat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1912 * link.lstat.birthtime # => 2026-09-23 16:35:59.321566598 -0500
1913 * # Method stat does follow the symlink, so the birthtimes are the same.
1914 * file.stat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1915 * link.stat.birthtime # => 2025-06-10 11:10:47.358999941 -0500
1916 * File.delete(link_path) # Clean up.
1917 * ```
1918 *
1919 */
1920
1921static VALUE
1922rb_file_lstat(VALUE obj)
1923{
1924#ifdef HAVE_LSTAT
1925 rb_io_t *fptr;
1926 rb_io_stat_data st;
1927 VALUE path;
1928
1929 GetOpenFile(obj, fptr);
1930 if (NIL_P(fptr->pathv)) return Qnil;
1931 path = rb_str_encode_ospath(fptr->pathv);
1932 if (lstatx_without_gvl(RSTRING_PTR(path), &st, STATX_ALL) == -1) {
1933 rb_sys_fail_path(fptr->pathv);
1934 }
1935 return rb_statx_new(&st);
1936#else
1937 return rb_io_stat(obj);
1938#endif
1939}
1940
1941static int
1942rb_group_member(GETGROUPS_T gid)
1943{
1944#if !defined(HAVE_GETGROUPS)
1945 return FALSE;
1946#else
1947 int rv = FALSE;
1948 int groups;
1949 VALUE v = 0;
1950 GETGROUPS_T *gary;
1951 int anum = -1;
1952
1953 if (getgid() == gid || getegid() == gid)
1954 return TRUE;
1955
1956 groups = getgroups(0, NULL);
1957 gary = ALLOCV_N(GETGROUPS_T, v, groups);
1958 anum = getgroups(groups, gary);
1959 while (--anum >= 0) {
1960 if (gary[anum] == gid) {
1961 rv = TRUE;
1962 break;
1963 }
1964 }
1965 if (v)
1966 ALLOCV_END(v);
1967
1968 return rv;
1969#endif /* !defined(HAVE_GETGROUPS) */
1970}
1971
1972#ifndef S_IXUGO
1973# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH)
1974#endif
1975
1976#if defined(S_IXGRP) && !defined(_WIN32) && !defined(__CYGWIN__)
1977#define USE_GETEUID 1
1978#endif
1979
1980#ifndef HAVE_EACCESS
1981int
1982eaccess(const char *path, int mode)
1983{
1984#ifdef USE_GETEUID
1985 struct stat st;
1986 rb_uid_t euid;
1987
1988 euid = geteuid();
1989
1990 /* no setuid nor setgid. run shortcut. */
1991 if (getuid() == euid && getgid() == getegid())
1992 return access(path, mode);
1993
1994 if (stat(path, &st) < 0)
1995 return -1;
1996
1997 if (euid == 0) {
1998 /* Root can read or write any file. */
1999 if (!(mode & X_OK))
2000 return 0;
2001
2002 /* Root can execute any file that has any one of the execute
2003 bits set. */
2004 if (st.st_mode & S_IXUGO)
2005 return 0;
2006
2007 return -1;
2008 }
2009
2010 if (st.st_uid == euid) /* owner */
2011 mode <<= 6;
2012 else if (rb_group_member(st.st_gid))
2013 mode <<= 3;
2014
2015 if ((int)(st.st_mode & mode) == mode) return 0;
2016
2017 return -1;
2018#else
2019 return access(path, mode);
2020#endif /* USE_GETEUID */
2021}
2022#endif /* HAVE_EACCESS */
2023
2025 const char *path;
2026 int mode;
2027};
2028
2029static void *
2030nogvl_eaccess(void *ptr)
2031{
2032 struct access_arg *aa = ptr;
2033
2034 return (void *)(VALUE)eaccess(aa->path, aa->mode);
2035}
2036
2037static int
2038rb_eaccess(VALUE fname, int mode)
2039{
2040 struct access_arg aa;
2041
2042 FilePathValue(fname);
2043 fname = rb_str_encode_ospath(fname);
2044 aa.path = StringValueCStr(fname);
2045 aa.mode = mode;
2046
2047 return IO_WITHOUT_GVL_INT(nogvl_eaccess, &aa);
2048}
2049
2050static void *
2051nogvl_access(void *ptr)
2052{
2053 struct access_arg *aa = ptr;
2054
2055 return (void *)(VALUE)access(aa->path, aa->mode);
2056}
2057
2058static int
2059rb_access(VALUE fname, int mode)
2060{
2061 struct access_arg aa;
2062
2063 FilePathValue(fname);
2064 fname = rb_str_encode_ospath(fname);
2065 aa.path = StringValueCStr(fname);
2066 aa.mode = mode;
2067
2068 return IO_WITHOUT_GVL_INT(nogvl_access, &aa);
2069}
2070
2071/*
2072 * Document-class: FileTest
2073 *
2074 * FileTest implements file test operations similar to those used in
2075 * File::Stat. It exists as a standalone module, and its methods are
2076 * also insinuated into the File class. (Note that this is not done
2077 * by inclusion: the interpreter cheats).
2078 *
2079 */
2080
2081/*
2082 * :markup: markdown
2083 *
2084 * call-seq:
2085 * File.directory?(object) -> true or false
2086 *
2087 * Returns whether the given `object` represents a directory;
2088 * `object` may be a string path or an IO object:
2089 *
2090 * ```ruby
2091 * File.directory?('/etc') # => true
2092 * File.directory?('/etc/passwd') # => false
2093 * File.directory?($stdin) # => false
2094 * File.directory?('nosuch') # => false
2095 * ```
2096 *
2097 */
2098
2099VALUE
2100rb_file_directory_p(VALUE obj, VALUE fname)
2101{
2102#ifndef S_ISDIR
2103# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
2104#endif
2105
2106 struct stat st;
2107
2108 if (rb_stat(fname, &st) < 0) return Qfalse;
2109 if (S_ISDIR(st.st_mode)) return Qtrue;
2110 return Qfalse;
2111}
2112
2113/*
2114 * :markup: markdown
2115 *
2116 * call-seq:
2117 * File.pipe?(path) -> true or false
2118 *
2119 * Returns whether the entry at the given `path` is a pipe:
2120 *
2121 * ```ruby
2122 * pipepath = '/tmp/pipe'
2123 * File.pipe?(pipepath) # => false
2124 * File.mkfifo(pipepath)
2125 * File.pipe?(pipepath) # => true
2126 * File.pipe?('/etc') # => false
2127 * File.pipe?('/etc/passwd') # => false
2128 * File.delete(pipepath) # Clean up.
2129 * ```
2130 *
2131 */
2132
2133static VALUE
2134rb_file_pipe_p(VALUE obj, VALUE fname)
2135{
2136#ifdef S_IFIFO
2137# ifndef S_ISFIFO
2138# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
2139# endif
2140
2141 struct stat st;
2142
2143 if (rb_stat(fname, &st) < 0) return Qfalse;
2144 if (S_ISFIFO(st.st_mode)) return Qtrue;
2145
2146#endif
2147 return Qfalse;
2148}
2149
2150/*
2151 * :markup: markdown
2152 *
2153 * call-seq:
2154 * File.symlink?(path) -> true or false
2155 *
2156 * Returns whether the entry at `path`
2157 * is a [symbolic link](rdoc-ref:file/symbolic_links.md):
2158 *
2159 * ```ruby
2160 * filepath = '/etc/passwd'
2161 * linkpath = '/tmp/foo'
2162 * File.symlink(filepath, linkpath)
2163 * File.symlink?(filepath) # => false
2164 * File.symlink?(linkpath) # => true
2165 * File.symlink?('.') # => false
2166 * File.delete(linkpath) # Clean up.
2167 * ```
2168 *
2169 */
2170
2171static VALUE
2172rb_file_symlink_p(VALUE obj, VALUE fname)
2173{
2174#ifndef S_ISLNK
2175# ifdef _S_ISLNK
2176# define S_ISLNK(m) _S_ISLNK(m)
2177# else
2178# ifdef _S_IFLNK
2179# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK)
2180# else
2181# ifdef S_IFLNK
2182# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
2183# endif
2184# endif
2185# endif
2186#endif
2187
2188#ifdef S_ISLNK
2189 struct stat st;
2190
2191 FilePathValue(fname);
2192 fname = rb_str_encode_ospath(fname);
2193 if (lstat_without_gvl(StringValueCStr(fname), &st) < 0) return Qfalse;
2194 if (S_ISLNK(st.st_mode)) return Qtrue;
2195#endif
2196
2197 return Qfalse;
2198}
2199
2200/*
2201 * :markup: markdown
2202 *
2203 * call-seq:
2204 * File.socket?(object) -> true or false
2205 *
2206 * Returns whether the given `object` represents a socket;
2207 * the `object` may be a path or an IO object:
2208 *
2209 * ```ruby
2210 * require 'socket'
2211 * socket_path = '/tmp/socket'
2212 * server = UNIXServer.new(socket_path)
2213 * File.socket?(socket_path) # => true
2214 * server.close # Clean up.
2215 * File.delete(socket_path) # Clean up.
2216 * file_path = '/etc/passwd'
2217 * File.file?(file_path) # => true
2218 * File.socket?(file_path) # => false
2219 * File.socket?($stdin) # => false
2220 * File.socket?('nosuch') # => false
2221 * ```
2222 *
2223 */
2224
2225static VALUE
2226rb_file_socket_p(VALUE obj, VALUE fname)
2227{
2228#ifndef S_ISSOCK
2229# ifdef _S_ISSOCK
2230# define S_ISSOCK(m) _S_ISSOCK(m)
2231# else
2232# ifdef _S_IFSOCK
2233# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK)
2234# else
2235# ifdef S_IFSOCK
2236# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
2237# endif
2238# endif
2239# endif
2240#endif
2241
2242#ifdef S_ISSOCK
2243 struct stat st;
2244
2245 if (rb_stat(fname, &st) < 0) return Qfalse;
2246 if (S_ISSOCK(st.st_mode)) return Qtrue;
2247#endif
2248
2249 return Qfalse;
2250}
2251
2252/*
2253 * :markup: markdown
2254 *
2255 * call-seq:
2256 * File.blockdev?(object) -> true or false
2257 *
2258 * Returns whether `object` (a path or IO object)
2259 * represents a block device (i.e., a direct-access device):
2260 *
2261 * ```ruby
2262 * File.blockdev?('/dev/nvme0n1') # => true
2263 * File.blockdev?('/dev/loop0') # => true
2264 * File.blockdev?('/dev/tty') # => false
2265 * File.blockdev?('/dev/null') # => false
2266 * File.blockdev?('nosuch') # => false
2267 * File.blockdev?($stdin) # => false
2268 * ```
2269 *
2270 * The returned value is filesystem-dependent; returns `false` on Windows or MacOS.
2271 */
2272
2273static VALUE
2274rb_file_blockdev_p(VALUE obj, VALUE fname)
2275{
2276#ifndef S_ISBLK
2277# ifdef S_IFBLK
2278# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
2279# else
2280# define S_ISBLK(m) (0) /* anytime false */
2281# endif
2282#endif
2283
2284#ifdef S_ISBLK
2285 struct stat st;
2286
2287 if (rb_stat(fname, &st) < 0) return Qfalse;
2288 if (S_ISBLK(st.st_mode)) return Qtrue;
2289
2290#endif
2291 return Qfalse;
2292}
2293
2294/*
2295 * call-seq:
2296 * File.chardev?(object) -> true or false
2297 *
2298 * Returns whether +object+ (a path or IO object)
2299 * represents a character device (i.e., a sequential-access device):
2300 *
2301 * File.chardev?('/dev/tty') # => true
2302 * File.chardev?('/dev/null') # => true
2303 * File.chardev?($stdin) # => true
2304 * File.chardev?('/dev/nvme0n1') # => false
2305 * File.chardev?('/dev/loop0') # => false
2306 * File.chardev?('nosuch') # => false
2307 *
2308 *
2309 * The returned value is filesystem-dependent; on Windows, always +false+.
2310 */
2311static VALUE
2312rb_file_chardev_p(VALUE obj, VALUE fname)
2313{
2314#ifndef S_ISCHR
2315# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
2316#endif
2317
2318 struct stat st;
2319
2320 if (rb_stat(fname, &st) < 0) return Qfalse;
2321 if (S_ISCHR(st.st_mode)) return Qtrue;
2322
2323 return Qfalse;
2324}
2325
2326/*
2327 * call-seq:
2328 * File.exist?(object) -> true or false
2329 *
2330 * Return whether the specified +object+, a string path or IO object, exists:
2331 *
2332 * # String paths.
2333 * File.exist?('README.md') # => true
2334 * File.exist?('.') # => true
2335 * filepath = 't.tmp'
2336 * File.exist?(filepath) # => false
2337 * File.write(filepath, 'foo')
2338 * File.exist?(filepath) # => true
2339 * # File (IO object).
2340 * file = File.new(filepath)
2341 * File.exist?(file) # => true
2342 * file.close # Clean up.
2343 * File.unlink(filepath) # Clean up.
2344 *
2345 * Follows symbolic links:
2346 *
2347 * # Symbolic links.
2348 * File.symlink('README.md', 'README.link')
2349 * File.symlink('nosuch', 'BROKEN.link')
2350 * File.exist?('README.link') # => true
2351 * File.exist?('BROKEN.link') # => false
2352 * File.unlink('README.link') # Clean up.
2353 * File.unlink('BROKEN.link') # Clean up.
2354 *
2355 */
2356
2357static VALUE
2358rb_file_exist_p(VALUE obj, VALUE fname)
2359{
2360 struct stat st;
2361
2362 if (rb_stat(fname, &st) < 0) return Qfalse;
2363 return Qtrue;
2364}
2365
2366/*
2367 * :markup: markdown
2368 *
2369 * call-seq:
2370 * File.readable?(path) -> true or false
2371 *
2372 * Returns whether the entry at the given `path`
2373 * exists and is readable by the owner and group of the current process;
2374 * see [Permissions](rdoc-ref:file/filesystem_modes.md@Permissions):
2375 *
2376 * ```ruby
2377 * path = '/tmp/secret.txt'
2378 * File.write(path, 'foo')
2379 * File.readable?(path) # => true
2380 * File.chmod(0o000, path)
2381 * File.readable?(path) # => false
2382 * File.delete(path) # Clean up.
2383 * File.readable?('nosuch') # => false
2384 * ```
2385 *
2386 */
2387
2388static VALUE
2389rb_file_readable_p(VALUE obj, VALUE fname)
2390{
2391 return RBOOL(rb_eaccess(fname, R_OK) >= 0);
2392}
2393
2394/*
2395 * :markup: markdown
2396 *
2397 * call-seq:
2398 * File.readable_real?(path) -> true or false
2399 *
2400 * Like File.readable?, but checks against the real owner and group ids
2401 * instead of the effective ids.
2402 */
2403
2404static VALUE
2405rb_file_readable_real_p(VALUE obj, VALUE fname)
2406{
2407 return RBOOL(rb_access(fname, R_OK) >= 0);
2408}
2409
2410#ifndef S_IRUGO
2411# define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH)
2412#endif
2413
2414#ifndef S_IWUGO
2415# define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH)
2416#endif
2417
2418/*
2419 * :markup: markdown
2420 *
2421 * call-seq:
2422 * File.world_readable?(object) -> integer or nil
2423 *
2424 * Returns the integer [permissions](rdoc-ref:file/filesystem_modes.md@Permissions)
2425 * for the given `object`
2426 * if it exists and is readable by others;
2427 * otherwise, returns `nil`:
2428 *
2429 * ```ruby
2430 * filepath = '/tmp/t.tmp'
2431 * File.world_readable?(filepath) # => nil # Non-existent.
2432 * File.write(filepath, 'foo') # Create file.
2433 * File.world_readable?(filepath).to_s(8) # => "664" # World-readable.
2434 * File.chmod(0o000, filepath) # Change to unreadable.
2435 * File.world_readable?(filepath) # => nil # Not world-readable.
2436 * File.delete(filepath) # Clean up.
2437 * File.world_readable?('.').to_s(8) # => "775" # Directory.
2438 * File.world_readable?($stdin) # => nil # IO object.
2439 * ```
2440 *
2441 */
2442
2443static VALUE
2444rb_file_world_readable_p(VALUE obj, VALUE fname)
2445{
2446#ifdef S_IROTH
2447 struct stat st;
2448
2449 if (rb_stat(fname, &st) < 0) return Qnil;
2450 if ((st.st_mode & (S_IROTH)) == S_IROTH) {
2451 return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
2452 }
2453#endif
2454 return Qnil;
2455}
2456
2457/*
2458 * :markup: markdown
2459 *
2460 * call-seq:
2461 * File.writable?(object) -> true or false
2462 *
2463 * Returns whether given `object` exists and is writable by the owner and group
2464 * in the current process:
2465 *
2466 * ```ruby
2467 * filepath = '/tmp/secret.txt'
2468 * File.writable?(filepath) # => false # Non-existent.
2469 * File.write(filepath, 'foo') # Create file.
2470 * File.writable?(filepath) # => true # Writable.
2471 * File.chmod(0o000, filepath) # Make non-writable.
2472 * File.writable?(filepath) # => false # Not writable.
2473 * File.delete(filepath) # Clean up.
2474 * File.writable?('/etc') # => false # Directory.
2475 * File.writable?($stdin) # => false # IO object.
2476 * ```
2477 *
2478 * Note that filesystem security features may cause this method to return `true`
2479 * even when the file is not writable by the owner and group.
2480 */
2481
2482static VALUE
2483rb_file_writable_p(VALUE obj, VALUE fname)
2484{
2485 return RBOOL(rb_eaccess(fname, W_OK) >= 0);
2486}
2487
2488/*
2489 * :markup: markdown
2490 *
2491 * call-seq:
2492 * File.writable_real?(object) -> true or false
2493 *
2494 * Like File.writable?, but checks against the real owner and group
2495 * instead of the effective owner and group.
2496 *
2497 * Note that filesystem security features may cause this method to return `true`
2498 * even when the object is not writable by the real owner and group.
2499 */
2500
2501static VALUE
2502rb_file_writable_real_p(VALUE obj, VALUE fname)
2503{
2504 return RBOOL(rb_access(fname, W_OK) >= 0);
2505}
2506
2507/*
2508 * :markup: markdown
2509 *
2510 * call-seq:
2511 * File.world_writable?(object) -> integer or nil
2512 *
2513 * If the given `object` exists and is writable by others,
2514 * returns the integer [permissions](rdoc-ref:file/filesystem_modes.md@Permissions)
2515 * for the entry;
2516 * otherwise, returns `nil`:
2517 *
2518 * ```ruby
2519 * filepath = '/tmp/t.tmp'
2520 * File.world_writable?(filepath) # => nil # Non-existent.
2521 * File.write(filepath, 'foo') # Create file.
2522 * File.world_writable?(filepath) # => nil # Not world-writable.
2523 * File.chmod(0o777, filepath) # Make world-writable.
2524 * File.world_writable?(filepath).to_s(8) # => "777" # World-writable.
2525 * File.delete(filepath) # Clean up.
2526 * File.world_writable?('/tmp').to_s(8) # => "777" # Directory.
2527 * File.world_writable?($stdin) # => nil # IO object.
2528 * ```
2529 *
2530 */
2531
2532static VALUE
2533rb_file_world_writable_p(VALUE obj, VALUE fname)
2534{
2535#ifdef S_IWOTH
2536 struct stat st;
2537
2538 if (rb_stat(fname, &st) < 0) return Qnil;
2539 if ((st.st_mode & (S_IWOTH)) == S_IWOTH) {
2540 return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
2541 }
2542#endif
2543 return Qnil;
2544}
2545
2546/*
2547 * :markup: markdown
2548 *
2549 * call-seq:
2550 * File.executable?(path) -> true or false
2551 *
2552 * Returns whether the filesystem entry at the given `path`
2553 * is [executable](rdoc-ref:file/filesystem_modes.md@Executable+Files)
2554 * by the effective owner/group of the current process:
2555 *
2556 * ```ruby
2557 * File.executable?('/bin/ruby') # => true
2558 * File.executable?('/etc') # => true
2559 * File.executable?('/etc/passwd') # => false
2560 * File.executable?('nosuch') # => false
2561 * ```
2562 *
2563 * Note that some filesystem settings may cause this method to return `true`
2564 * even though the entry is not executable by the effective owner/group.
2565 */
2566
2567static VALUE
2568rb_file_executable_p(VALUE obj, VALUE fname)
2569{
2570 return RBOOL(rb_eaccess(fname, X_OK) >= 0);
2571}
2572
2573/*
2574 * :markup: markdown
2575 *
2576 * call-seq:
2577 * File.executable_real?(path) -> true or false
2578 *
2579 * Like File.executable?, but checks against the real owner/group
2580 * instead of the effective owner/group.
2581 */
2582
2583static VALUE
2584rb_file_executable_real_p(VALUE obj, VALUE fname)
2585{
2586 return RBOOL(rb_access(fname, X_OK) >= 0);
2587}
2588
2589#ifndef S_ISREG
2590# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
2591#endif
2592
2593/*
2594 * :markup: markdown
2595 *
2596 * call-seq:
2597 * File.file?(object) -> true or false
2598 *
2599 * Returns whether the given `object`, a string path or IO object,
2600 * represents a filesystem entry that exists and is a regular file;
2601 * see File.ftype:
2602 *
2603 * ```ruby
2604 * File.file?('/etc/passwd') # => true
2605 * File.file?('/etc') # => false
2606 * File.file?($stdin) # => false
2607 * File.file?('nosuch') # => false
2608 * ```
2609 *
2610 */
2611
2612static VALUE
2613rb_file_file_p(VALUE obj, VALUE fname)
2614{
2615 struct stat st;
2616
2617 if (rb_stat(fname, &st) < 0) return Qfalse;
2618 return RBOOL(S_ISREG(st.st_mode));
2619}
2620
2621/*
2622 * :markup: markdown
2623
2624 * call-seq:
2625 * File.zero?(object) -> true or false
2626 * File.empty?(object) -> true or false
2627 *
2628 * Returns whether the given `object` exists and has size zero.
2629 *
2630 * The given `object` may be the path to a file:
2631 *
2632 * ```ruby
2633 * filepath = '/tmp/t.tmp'
2634 * File.write(filepath, 'foo') # File has non-zero size.
2635 * File.zero?(filepath) # => false
2636 * File.truncate(filepath, 0) # File has zero size.
2637 * File.zero?(filepath) # => true
2638 * File.delete(filepath) # Clean up.
2639 * ```
2640 *
2641 * The given `object` may be the path to a directory:
2642 *
2643 * ```ruby
2644 * dirpath = '/tmp/foo'
2645 * Dir.mkdir(dirpath)
2646 * Dir.new(dirpath).children.size # => 0
2647 * # Size is filesystem-dependent; may or may not be zero.
2648 * File.size(dirpath) # => 4096
2649 * File.zero?(dirpath) # => false
2650 * filepath = '/tmp/foo/t.tmp' # => "/tmp/foo/t.tmp"
2651 * File.write(filepath, 'foo') # Add a child.
2652 * Dir.new(dirpath).children.size # => 1
2653 * File.size(dirpath) # => 4096
2654 * File.zero?(dirpath) # => false
2655 * FileUtils.rm_rf(dirpath) # Clean up.
2656 * ```
2657 *
2658 * The given `object` may be an IO object:
2659 *
2660 * ```ruby
2661 * File.zero?($stdin) # => true
2662 * ```
2663 *
2664 * The given object may be none of the above:
2665 *
2666 * ```ruby
2667 * File.zero?('nosuch') # => false
2668 * ```
2669 *
2670 */
2671
2672static VALUE
2673rb_file_zero_p(VALUE obj, VALUE fname)
2674{
2675 struct stat st;
2676
2677 if (rb_stat(fname, &st) < 0) return Qfalse;
2678 return RBOOL(st.st_size == 0);
2679}
2680
2681/*
2682 * :markup: markdown
2683 *
2684 * call-seq:
2685 * File.size?(object) -> integer or nil
2686 *
2687 * Returns the size in bytes of the given `object`
2688 * if the entry exists and has non-zero size, `nil` otherwise;
2689 * the `object` may be a string path or an IO object:
2690 *
2691 * ```ruby
2692 * # Regular file.
2693 * filepath = '/tmp/t.tmp'
2694 * File.write(filepath, 'foo')
2695 * File.size?(filepath) # => 3 # Non-zero size.
2696 * File.write(filepath, '')
2697 * File.size?(filepath) # => nil # Zero size.
2698 * File.delete(filepath) # Clean up.
2699 * File.size?(filepath) # => nil # Non-existent.
2700 * # Directory.
2701 * dirpath = '/tmp/dir'
2702 * Dir.mkdir(dirpath)
2703 * File.size?(dirpath) # => 4096 # Non-zero size.
2704 * Dir.rmdir(dirpath) # Clean up.
2705 * File.size?(dirpath) # => nil # Non-existent.
2706 * ```
2707 *
2708 */
2709
2710static VALUE
2711rb_file_size_p(VALUE obj, VALUE fname)
2712{
2713 struct stat st;
2714
2715 if (rb_stat(fname, &st) < 0) return Qnil;
2716 if (st.st_size == 0) return Qnil;
2717 return OFFT2NUM(st.st_size);
2718}
2719
2720/*
2721 * :markup: markdown
2722 *
2723 * call-seq:
2724 * File.owned?(object) -> true or false
2725 *
2726 * Returns whether the given `object` represents a filesystem entry or IO object
2727 * that exists and is owned by the owner of the current process:
2728 *
2729 * ```ruby
2730 * dirpath = '/tmp/dir'
2731 * Dir.mkdir(dirpath)
2732 * File.owned?(dirpath) # => true
2733 * filepath = File.join(dirpath, 't.tmp')
2734 * File.write(filepath, 'foo')
2735 * File.owned?(filepath) # => true
2736 * File.owned?($stdin) # => true
2737 * File.owned?('/tmp') # => false
2738 * File.owned?('nosuch') # => false
2739 * FileUtils.rm_rf(dirpath) # Clean up.
2740 *
2741 * ```
2742 *
2743 */
2744
2745static VALUE
2746rb_file_owned_p(VALUE obj, VALUE fname)
2747{
2748 struct stat st;
2749
2750 if (rb_stat(fname, &st) < 0) return Qfalse;
2751 return RBOOL(st.st_uid == geteuid());
2752}
2753
2754static VALUE
2755rb_file_rowned_p(VALUE obj, VALUE fname)
2756{
2757 struct stat st;
2758
2759 if (rb_stat(fname, &st) < 0) return Qfalse;
2760 return RBOOL(st.st_uid == getuid());
2761}
2762
2763/*
2764 * :markup: markdown
2765
2766 * call-seq:
2767 * File.grpowned?(object) -> true or false
2768 *
2769 * Returns `false` on Windows.
2770 *
2771 * On other systems returns whether the effective group id of the calling process
2772 * is the owner of the given `object`.
2773 *
2774 * The given +object+ may be the string path or an IO object:
2775 *
2776 * ```ruby
2777 * filepath = '/tmp/t.tmp'
2778 * File.write(filepath, 'foo')
2779 * File.grpowned?(filepath) # => true
2780 * File.grpowned?('/etc') # => false
2781 * File.grpowned?('/etc/passwd') # => false
2782 * File.grpowned?($stdin) # => false
2783 * File.grpowned?('nosuch') # => false
2784 * File.delete(filepath) # Clean up.
2785 * ```
2786 *
2787 */
2788
2789static VALUE
2790rb_file_grpowned_p(VALUE obj, VALUE fname)
2791{
2792#ifndef _WIN32
2793 struct stat st;
2794
2795 if (rb_stat(fname, &st) < 0) return Qfalse;
2796 if (rb_group_member(st.st_gid)) return Qtrue;
2797#endif
2798 return Qfalse;
2799}
2800
2801#if defined(S_ISUID) || defined(S_ISGID) || defined(S_ISVTX)
2802static VALUE
2803check3rdbyte(VALUE fname, int mode)
2804{
2805 struct stat st;
2806
2807 if (rb_stat(fname, &st) < 0) return Qfalse;
2808 return RBOOL(st.st_mode & mode);
2809}
2810#endif
2811
2812/*
2813 * :markup: markdown
2814 *
2815 * call-seq:
2816 * File.setuid?(object) -> true or false
2817 *
2818 * Returns whether the setuid bit is set
2819 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
2820 * for the given `object`, which may be a path or an IO object:
2821 *
2822 * ```ruby
2823 * path = '/tmp/t.tmp'
2824 * File.write(path, 'foo')
2825 * mode = File.stat(path).mode.to_s(8) # => "100664"
2826 * File.setuid?(path) # => false
2827 * File.chmod(0o4644, path) # Set the bit.
2828 * mode = File.stat(path).mode.to_s(8) # => "104644"
2829 * File.setuid?(path) # => true
2830 * File.delete(path) # Clean up.
2831 * File.setuid?($stdin) # => false
2832 * ```
2833 *
2834 * On Windows, the bit is never set; the method always returns `false`.
2835 */
2836
2837static VALUE
2838rb_file_suid_p(VALUE obj, VALUE fname)
2839{
2840#ifdef S_ISUID
2841 return check3rdbyte(fname, S_ISUID);
2842#else
2843 return Qfalse;
2844#endif
2845}
2846
2847/*
2848 * :markup: markdown
2849 *
2850 * call-seq:
2851 * File.setgid?(object) -> true or false
2852 *
2853 * Returns whether the setgid bit is set
2854 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
2855 * for the given `object`, which may be a path or an IO object:
2856 *
2857 * ```ruby
2858 * path = '/tmp/t.tmp'
2859 * File.write(path, 'foo')
2860 * mode = File.stat(path).mode.to_s(8) # => "100664"
2861 * File.setgid?(path) # => false
2862 * File.chmod(0o2644, path) # Set the bit.
2863 * mode = File.stat(path).mode.to_s(8) # => "102644"
2864 * File.setgid?(path) # => true
2865 * File.delete(path) # Clean up.
2866 * File.setgid?($stdin) # => false
2867 * ```
2868 *
2869 * On Windows, the bit is never set; the method always returns `false`.
2870 */
2871
2872static VALUE
2873rb_file_sgid_p(VALUE obj, VALUE fname)
2874{
2875#ifdef S_ISGID
2876 return check3rdbyte(fname, S_ISGID);
2877#else
2878 return Qfalse;
2879#endif
2880}
2881
2882/*
2883 * :markup: markdown
2884
2885 * call-seq:
2886 * File.sticky?(object) -> true or false
2887 *
2888 * Returns whether the sticky bit is set
2889 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
2890 * for the given `object`, which may be a path or an IO object:
2891 *
2892 * ```ruby
2893 * filepath = '/tmp/t.tmp'
2894 * File.write(filepath, 'foo')
2895 * mode = File.stat(filepath).mode.to_s(8) # => "100664"
2896 * File.sticky?(filepath) # => false
2897 * File.chmod(01644, filepath) # Set sticky bit.
2898 * mode = File.stat(filepath).mode.to_s(8) # => "101644"
2899 * File.sticky?(filepath) # => true
2900 * File.delete(filepath) # Clean up.
2901 * File.sticky?($stdin) # => false
2902 * File.sticky?('nosuch') # => false
2903 * ```
2904 *
2905 * Returns `false` on Windows.
2906 */
2907
2908static VALUE
2909rb_file_sticky_p(VALUE obj, VALUE fname)
2910{
2911#ifdef S_ISVTX
2912 return check3rdbyte(fname, S_ISVTX);
2913#else
2914 return Qfalse;
2915#endif
2916}
2917
2918/*
2919 * call-seq:
2920 * File.identical?(object_0, object_1) -> true or false
2921 *
2922 * Returns whether the given objects represent filesystem entries that are identical;
2923 * each object may be a string path or an IO object:
2924 *
2925 * # Paths.
2926 * File.identical?('README.md', 'README.md') # => true # Same path.
2927 * File.identical?('README.md', './README.md') # => true # Same entry.
2928 * File.identical?('.', '.') # => true # Directory.
2929 * File.identical?('README.md', 'LEGAL') # => false
2930 * File.identical?('README.md', 'nosuch') # => false # Non-existent entry.
2931 * # Links and File object.
2932 * File.link('README.md', 'link') # Symbolic link.
2933 * File.symlink('README.md', 'symlink') # Hard link.
2934 * file = File.open('README.md', 'r') # File object.
2935 * File.identical?('README.md', 'link') # => true
2936 * File.identical?('README.md', 'symlink') # => true
2937 * File.identical?('README.md', file) # => true
2938 * # Clean up.
2939 * File.unlink('link')
2940 * File.unlink('symlink')
2941 * file.close
2942 *
2943 */
2944
2945static VALUE
2946rb_file_identical_p(VALUE obj, VALUE fname1, VALUE fname2)
2947{
2948#ifndef _WIN32
2949 struct stat st1, st2;
2950
2951 if (rb_stat(fname1, &st1) < 0) return Qfalse;
2952 if (rb_stat(fname2, &st2) < 0) return Qfalse;
2953 if (st1.st_dev != st2.st_dev) return Qfalse;
2954 if (st1.st_ino != st2.st_ino) return Qfalse;
2955 return Qtrue;
2956#else
2957 extern VALUE rb_w32_file_identical_p(VALUE, VALUE);
2958 return rb_w32_file_identical_p(fname1, fname2);
2959#endif
2960}
2961
2962/*
2963 * :markup: markdown
2964 *
2965 * call-seq:
2966 * File.size(object) -> integer
2967 *
2968 * Returns the size in bytes of the given `object`,
2969 * which may be a string path or an IO object:
2970 *
2971 * ```ruby
2972 * File.size('/etc/passwd') # => 3092 # Regular file.
2973 * File.size('/etc') # => 12288 # Directory.
2974 * File.size($stdin) # => 0 # IO object.
2975 * ```
2976 *
2977 */
2978
2979static VALUE
2980rb_file_s_size(VALUE klass, VALUE fname)
2981{
2982 struct stat st;
2983
2984 if (rb_stat(fname, &st) < 0) {
2985 int e = errno;
2986 FilePathValue(fname);
2987 rb_syserr_fail_path(e, fname);
2988 }
2989 return OFFT2NUM(st.st_size);
2990}
2991
2992static VALUE
2993rb_file_ftype(mode_t mode)
2994{
2995 const char *t;
2996
2997 if (S_ISREG(mode)) {
2998 t = "file";
2999 }
3000 else if (S_ISDIR(mode)) {
3001 t = "directory";
3002 }
3003 else if (S_ISCHR(mode)) {
3004 t = "characterSpecial";
3005 }
3006#ifdef S_ISBLK
3007 else if (S_ISBLK(mode)) {
3008 t = "blockSpecial";
3009 }
3010#endif
3011#ifdef S_ISFIFO
3012 else if (S_ISFIFO(mode)) {
3013 t = "fifo";
3014 }
3015#endif
3016#ifdef S_ISLNK
3017 else if (S_ISLNK(mode)) {
3018 t = "link";
3019 }
3020#endif
3021#ifdef S_ISSOCK
3022 else if (S_ISSOCK(mode)) {
3023 t = "socket";
3024 }
3025#endif
3026 else {
3027 t = "unknown";
3028 }
3029
3030 return rb_fstring_cstr(t);
3031}
3032
3033/*
3034 * :markup: markdown
3035 *
3036 * call-seq:
3037 * File.ftype(path) -> string
3038 *
3039 * Returns the string type of the object at `path`, one of:
3040 *
3041 * - `'file'`.
3042 * - `'directory'`.
3043 * - `'characterSpecial'`.
3044 * - `'blockSpecial'`.
3045 * - `'fifo'`.
3046 * - `'link'`.
3047 * - `'socket'`.
3048 *
3049 * Examples:
3050 *
3051 * ```ruby
3052 * File.ftype('/etc/passwd') # => "file"
3053 * File.ftype('/etc') # => "directory"
3054 * File.ftype("/dev/null") # => "characterSpecial"
3055 * File.ftype("/dev/loop0") # => "blockSpecial"
3056 *
3057 * File.mkfifo('/tmp/pipe', 0o666)
3058 * File.ftype('/tmp/pipe') # => "fifo"
3059 *
3060 * File.symlink('/etc/passwd', '/tmp/link')
3061 * File.ftype('/tmp/link') # => "link"
3062 *
3063 * require 'socket'
3064 * UNIXServer.new('/tmp/socket')
3065 * File.ftype('/tmp/socket') # => "socket"
3066 *
3067 * File.delete('/tmp/link', '/tmp/pipe', '/tmp/socket') # Clean up.
3068 * ```
3069 *
3070 * Returns `'unknown'` if the type cannot be determined.
3071 */
3072
3073static VALUE
3074rb_file_s_ftype(VALUE klass, VALUE fname)
3075{
3076 struct stat st;
3077
3078 FilePathValue(fname);
3079 fname = rb_str_encode_ospath(fname);
3080 if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) {
3081 rb_sys_fail_path(fname);
3082 }
3083
3084 return rb_file_ftype(st.st_mode);
3085}
3086
3087/*
3088 * :markup: markdown
3089
3090 * call-seq:
3091 * File.atime(object) -> time
3092 *
3093 * Returns a new Time object containing the time of the most recent
3094 * access to the given `object`.
3095 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
3096 *
3097 * Access time for a file is established when it is created,
3098 * and may be updated when the file content is read:
3099 *
3100 * ```ruby
3101 * filepath = '/tmp/t.tmp'
3102 * File.exist?(filepath) # => false
3103 * File.atime(filepath) # Raises Errno::ENOENT: No such file or directory.
3104 * File.write(filepath, 'foo') # Establishes access time.
3105 * File.atime(filepath) # => 2026-10-04 14:23:12.421395099 -0500
3106 * File.read(filepath) # Updates access time.
3107 * File.atime(filepath) # => 2026-10-04 14:23:21.848886816 -0500
3108 * File.delete(filepath) # Clean up.
3109 * ```
3110 *
3111 * Access time for a directory is established when it is created,
3112 * and may updated when its entries are read:
3113 *
3114 * ```ruby
3115 * dirpath = '/tmp/foo'
3116 * File.exist?(dirpath) # => false
3117 * File.atime(dirpath) # Raises Errno::ENOENT: No such file or directory.
3118 * Dir.mkdir(dirpath) # Establishes access time.
3119 * File.atime(dirpath) # => 2026-10-04 14:35:53.940716253 -0500
3120 * Dir.entries(dirpath) # Updates access time.
3121 * File.atime(dirpath) # => 2026-10-04 14:36:24.316018872 -0500
3122 * Dir.rmdir(dirpath) # Clean up.
3123 * ```
3124 *
3125 * Argument `object` may be a string path (as above),
3126 * a File object, or a Dir object:
3127 *
3128 * ```ruby
3129 * File.atime(File.new('/etc/passwd')) # => 2026-10-04 11:25:01.259278928 -0500
3130 * File.atime('.') # => 2026-10-04 14:40:42.141637825 -0500
3131 * ```
3132 *
3133 */
3134
3135static VALUE
3136rb_file_s_atime(VALUE klass, VALUE fname)
3137{
3138 struct stat st;
3139
3140 if (rb_stat(fname, &st) < 0) {
3141 int e = errno;
3142 FilePathValue(fname);
3143 rb_syserr_fail_path(e, fname);
3144 }
3145 return stat_time(stat_atimespec(&st));
3146}
3147
3148/*
3149 * :markup: markdown
3150 *
3151 * call-seq:
3152 * atime -> time
3153 *
3154 * Returns a new Time object containing the time of the most recent
3155 * access to `self`.
3156 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
3157 *
3158 * Access time for a file is established when it is created,
3159 * and may be updated when the file content is read:
3160 *
3161 * ```
3162 * filepath = '/tmp/t.tmp'
3163 * File.exist?(filepath) # => false
3164 * File.write(filepath, 'foo') # Establishes access time.
3165 * file = File.new(filepath)
3166 * file.atime # => 2026-10-04 14:48:30.323100327 -0500
3167 * file.read # Updates access time.
3168 * file.atime # => 2026-10-04 14:49:01.104340829 -0500
3169 * # Clean up.
3170 * file.close
3171 * File.delete(filepath)
3172 * ```
3173 *
3174 */
3175
3176static VALUE
3177rb_file_atime(VALUE obj)
3178{
3179 rb_io_t *fptr;
3180 struct stat st;
3181
3182 GetOpenFile(obj, fptr);
3183 if (fstat(fptr->fd, &st) == -1) {
3184 rb_sys_fail_path(fptr->pathv);
3185 }
3186 return stat_time(stat_atimespec(&st));
3187}
3188
3189/*
3190 * :markup: markdown
3191 *
3192 * call-seq:
3193 * File.mtime(object) -> time
3194 *
3195 * Returns a new Time object containing
3196 * the [modification time](rdoc-ref:file/timestamps.md@Modification+Time)
3197 * of the given `object`, which may be a string path or an IO object;
3198 *
3199 * ```ruby
3200 * # Create directory; establishes directory mtime.
3201 * dirpath = '/tmp/dir'
3202 * Dir.mkdir(dirpath)
3203 * File.mtime(dirpath) # => 2026-10-12 12:24:08.542476612 -0500
3204 * # Create file therein; establishes file mtime, updates directory mtime.
3205 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
3206 * File.write(filepath, 'foo')
3207 * File.mtime(filepath) # => 2026-10-12 12:28:41.261675035 -0500
3208 * File.mtime(dirpath) # => 2026-10-12 12:28:41.261675035 -0500
3209 * # Modify file; updates file mtime, does not update directory mtime.
3210 * File.write(filepath, 'bar')
3211 * File.mtime(filepath) # => 2026-10-12 12:30:09.113910789 -0500
3212 * File.mtime(dirpath) # => 2026-10-12 12:28:41.261675035 -0500
3213 * FileUtils.rm_rf(dirpath) # Clean up.
3214 * File.mtime($stdout) # => 2026-10-12 12:31:28 -0500
3215 * $stdout.flush
3216 * File.mtime($stdout) # => 2026-10-12 12:31:36 -0500
3217 * ```
3218 *
3219 */
3220
3221static VALUE
3222rb_file_s_mtime(VALUE klass, VALUE fname)
3223{
3224 struct stat st;
3225
3226 if (rb_stat(fname, &st) < 0) {
3227 int e = errno;
3228 FilePathValue(fname);
3229 rb_syserr_fail_path(e, fname);
3230 }
3231 return stat_time(stat_mtimespec(&st));
3232}
3233
3234/*
3235 * :markup: markdown
3236 *
3237 * call-seq:
3238 * mtime -> time
3239 *
3240 * Returns a new Time object containing
3241 * the [modification time](rdoc-ref:file/timestamps.md@Modification+Time)
3242 * for `self`:
3243 *
3244 * ```ruby
3245 * filepath = '/tmp/t.tmp'
3246 * file = File.new(filepath, 'w+')
3247 * file.mtime # => 2026-10-13 10:27:02.446070725 -0500
3248 * file.write('foo')
3249 * file.flush
3250 * file.mtime # => 2026-10-13 10:27:49.252424006 -0500
3251 * # Clean up.
3252 * file.close
3253 * File.delete(filepath)
3254 * ```
3255 *
3256 */
3257
3258static VALUE
3259rb_file_mtime(VALUE obj)
3260{
3261 rb_io_t *fptr;
3262 struct stat st;
3263
3264 GetOpenFile(obj, fptr);
3265 if (fstat(fptr->fd, &st) == -1) {
3266 rb_sys_fail_path(fptr->pathv);
3267 }
3268 return stat_time(stat_mtimespec(&st));
3269}
3270
3271/*
3272 * :markup: markdown
3273 *
3274 * call-seq:
3275 * File.ctime(object) -> time
3276 *
3277 * On Windows, returns the birthtime for `object`.
3278 *
3279 * On other systems,
3280 * returns a new Time object containing the time of the most recent
3281 * metadata change to the entry represented by `object`,
3282 * which is a string path or an IO object;
3283 * see [File System Timestamps](rdoc-ref:file/timestamps.md):
3284 *
3285 * ```ruby
3286 * dirpath = '/tmp/dir'
3287 * Dir.mkdir(dirpath)
3288 * File.ctime(dirpath) # => 2026-10-05 16:24:40.347201215 -0500
3289 * # Create file therein; file ctime established; directory ctime updated.
3290 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
3291 * File.write(filepath, 'foo')
3292 * File.ctime(dirpath) # => 2026-10-05 16:25:34.790762181 -0500
3293 * File.ctime(filepath) # => 2026-10-05 16:25:34.790762181 -0500
3294 * # Write file; file ctime updated; directory ctime not updated.
3295 * File.write(filepath, 'bar')
3296 * File.ctime(dirpath) # => 2026-10-05 16:25:34.790762181 -0500
3297 * File.ctime(filepath) # => 2026-10-05 16:26:23.030432041 -0500
3298 * # Read file; neither ctime updated.
3299 * File.read(filepath)
3300 * File.ctime(dirpath) # => 2026-10-05 16:25:34.790762181 -0500
3301 * File.ctime(filepath) # => 2026-10-05 16:26:23.030432041 -0500
3302 * FileUtils.rm_rf(dirpath) # Clean up.
3303 * ```
3304 *
3305 */
3306
3307static VALUE
3308rb_file_s_ctime(VALUE klass, VALUE fname)
3309{
3310 struct stat st;
3311
3312 if (rb_stat(fname, &st) < 0) {
3313 int e = errno;
3314 FilePathValue(fname);
3315 rb_syserr_fail_path(e, fname);
3316 }
3317 return stat_time(stat_ctimespec(&st));
3318}
3319
3320/*
3321 * :markup: markdown
3322 *
3323 * call-seq:
3324 * ctime -> time
3325 *
3326 * On Windows, returns the birthtime for the entry in `self`.
3327 *
3328 * On other systems,
3329 * returns a new Time object containing the time of the most recent
3330 * metadata change to the entry in `self`;
3331 * see [File System Timestamps](rdoc-ref:file/timestamps.md):
3332 *
3333 * ```ruby
3334 * # A directory.
3335 * dirpath = '/tmp/dir'
3336 * Dir.mkdir(dirpath) # Establishes directory ctime.
3337 * dir = File.new(dirpath) # (Dir does not have method ctime; we use File.)
3338 * dir.ctime # => 2026-10-05 17:23:49.406452569 -0500
3339 * # A file therein.
3340 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
3341 * File.write(filepath, 'foo') # Establishes file ctime.
3342 * file = File.new(filepath)
3343 * file.ctime # => 2026-10-05 17:26:26.467758515 -0500
3344 * dir.ctime # => 2026-10-05 17:26:26.467758515 -0500
3345 * File.write(filepath, 'bar') # Updates file ctime; not directory ctime.
3346 * file.ctime # => 2026-10-05 17:28:26.307497599 -0500
3347 * dir.ctime # => 2026-10-05 17:26:26.467758515 -0500
3348 * FileUtils.rm_rf(dirpath) # Clean up.
3349 * ```
3350 *
3351 */
3352
3353static VALUE
3354rb_file_ctime(VALUE obj)
3355{
3356 rb_io_t *fptr;
3357 struct stat st;
3358
3359 GetOpenFile(obj, fptr);
3360 if (fstat(fptr->fd, &st) == -1) {
3361 rb_sys_fail_path(fptr->pathv);
3362 }
3363 return stat_time(stat_ctimespec(&st));
3364}
3365
3366#if defined(HAVE_STAT_BIRTHTIME)
3367/*
3368 * :markup: markdown
3369 *
3370 * call-seq:
3371 * File.birthtime(path) -> time
3372 *
3373 * Returns a new Time object containing the creation time
3374 * of the entry at the given `path`;
3375 * see [File System Timestamps](rdoc-ref:file/timestamps.md):
3376 *
3377 * ```ruby
3378 * filepath = '/tmp/t.tmp'
3379 * File.birthtime(filepath) # Raises Errno::ENOENT: No such file or directory.
3380 * File.write(filepath, 'foo')
3381 * File.birthtime(filepath) # => 2026-10-05 13:41:02.330337383 -0500
3382 * File.delete(filepath) # Clean up.
3383 * ```
3384 *
3385 */
3386
3387static VALUE
3388rb_file_s_birthtime(VALUE klass, VALUE fname)
3389{
3390 rb_io_stat_data st;
3391
3392 if (rb_statx(fname, &st, STATX_BTIME) < 0) {
3393 int e = errno;
3394 FilePathValue(fname);
3395 rb_syserr_fail_path(e, fname);
3396 }
3397 return statx_birthtime(&st);
3398}
3399#else
3400# define rb_file_s_birthtime rb_f_notimplement
3401#endif
3402
3403#if defined(HAVE_STAT_BIRTHTIME)
3404/*
3405 * call-seq:
3406 * birthtime -> new_time
3407 *
3408 * Returns a new Time object containing the create time for +self+:
3409 *
3410 * filepath = 't.tmp'
3411 * File.write(filepath, 'foo')
3412 * file = File.new(filepath)
3413 * file.birthtime # => 2026-04-14 15:53:45.002656 -0500
3414 * File.write(filepath, 'bar')
3415 * file.birthtime # => 2026-04-14 15:53:45.002656 -0500
3416 * file.close
3417 * File.delete(filepath)
3418 * file.birthtime # Raises IOError: closed stream
3419 *
3420 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
3421 */
3422
3423static VALUE
3424rb_file_birthtime(VALUE obj)
3425{
3426 rb_io_t *fptr;
3427 rb_io_stat_data st;
3428
3429 GetOpenFile(obj, fptr);
3430 if (fstatx_without_gvl(fptr, &st, STATX_BTIME) == -1) {
3431 rb_sys_fail_path(fptr->pathv);
3432 }
3433 return statx_birthtime(&st);
3434}
3435#else
3436# define rb_file_birthtime rb_f_notimplement
3437#endif
3438
3439rb_off_t
3440rb_file_size(VALUE file)
3441{
3442 if (RB_TYPE_P(file, T_FILE)) {
3443 rb_io_t *fptr;
3444 struct stat st;
3445
3446 RB_IO_POINTER(file, fptr);
3447 if (fptr->mode & FMODE_WRITABLE) {
3448 rb_io_flush_raw(file, 0);
3449 }
3450
3451 if (fstat(fptr->fd, &st) == -1) {
3452 rb_sys_fail_path(fptr->pathv);
3453 }
3454
3455 return st.st_size;
3456 }
3457 else {
3458 return NUM2OFFT(rb_funcall(file, idSize, 0));
3459 }
3460}
3461
3462/*
3463 * :markup: markdown
3464 *
3465 * call-seq:
3466 * size -> integer
3467 *
3468 * Returns the size of `self` in bytes:
3469 *
3470 * ```ruby
3471 * File.new('/etc/passwd').size # => 3092 # Regular file.
3472 * File.new('/etc').size # => 12288 # Directory.
3473 * ```
3474 *
3475 */
3476
3477static VALUE
3478file_size(VALUE self)
3479{
3480 return OFFT2NUM(rb_file_size(self));
3481}
3482
3484 const char *path;
3485 mode_t mode;
3486};
3487
3488static void *
3489nogvl_chmod(void *ptr)
3490{
3491 struct nogvl_chmod_data *data = ptr;
3492 int ret = chmod(data->path, data->mode);
3493 return (void *)(VALUE)ret;
3494}
3495
3496static int
3497rb_chmod(const char *path, mode_t mode)
3498{
3499 struct nogvl_chmod_data data = {
3500 .path = path,
3501 .mode = mode,
3502 };
3503 return IO_WITHOUT_GVL_INT(nogvl_chmod, &data);
3504}
3505
3506static int
3507chmod_internal(const char *path, void *mode)
3508{
3509 return chmod(path, *(mode_t *)mode);
3510}
3511
3512/*
3513 * call-seq:
3514 * File.chmod(mode, *paths) -> integer
3515 *
3516 * Changes the modes of each of the entries at each the given +paths+;
3517 * returns the count of the given +paths+.
3518 * See {Filesystem Modes}[rdoc-ref:file/filesystem_modes.md]
3519 * and especially {Setting a Mode}[rdoc-ref:file/filesystem_modes.md@Setting+a+Mode].
3520 *
3521 * file0path = '/tmp/t0.tmp'
3522 * file1path = '/tmp/t1.tmp'
3523 * File.write(file0path, 'foo')
3524 * File.write(file1path, 'bar')
3525 * '%06o' % File.stat(file0path).mode # => "100664"
3526 * '%06o' % File.stat(file1path).mode # => "100664"
3527 * File.chmod(0o755, file0path, file1path)
3528 * '%06o' % File.stat(file0path).mode # => "100755"
3529 * '%06o' % File.stat(file1path).mode # => "100755"
3530 * File.delete(file0path, file1path) # Clean up.
3531 *
3532 */
3533
3534static VALUE
3535rb_file_s_chmod(int argc, VALUE *argv, VALUE _)
3536{
3537 mode_t mode;
3538
3539 apply2args(1);
3540 mode = NUM2MODET(*argv++);
3541
3542 return apply2files(chmod_internal, argc, argv, &mode);
3543}
3544
3545#ifdef HAVE_FCHMOD
3546struct nogvl_fchmod_data {
3547 int fd;
3548 mode_t mode;
3549};
3550
3551static VALUE
3552io_blocking_fchmod(void *ptr)
3553{
3554 struct nogvl_fchmod_data *data = ptr;
3555 int ret = fchmod(data->fd, data->mode);
3556 return (VALUE)ret;
3557}
3558
3559static int
3560rb_fchmod(struct rb_io* io, mode_t mode)
3561{
3562 (void)rb_chmod; /* suppress unused-function warning when HAVE_FCHMOD */
3563 struct nogvl_fchmod_data data = {.fd = io->fd, .mode = mode};
3564 return (int)rb_thread_io_blocking_region(io, io_blocking_fchmod, &data);
3565}
3566#endif
3567
3568/*
3569 * call-seq:
3570 * chmod(mode) -> 0
3571 *
3572 * Changes the mode of +self+; returns '0'.
3573 * See {Filesystem Modes}[rdoc-ref:file/filesystem_modes.md]
3574 * and especially {Setting a Mode}[rdoc-ref:file/filesystem_modes.md@Setting+a+Mode].
3575 *
3576 * filepath = '/tmp/t.tmp'
3577 * File.write(filepath, 'foo')
3578 * '%06o' % File.stat(filepath).mode # => "100664"
3579 * file = File.new(filepath)
3580 * file.chmod(0o755)
3581 * '%06o' % File.stat(filepath).mode # => "100755"
3582 * # Clean up.
3583 * file.close
3584 * File.delete(filepath)
3585 *
3586 */
3587
3588static VALUE
3589rb_file_chmod(VALUE obj, VALUE vmode)
3590{
3591 rb_io_t *fptr;
3592 mode_t mode;
3593#if !defined HAVE_FCHMOD || !HAVE_FCHMOD
3594 VALUE path;
3595#endif
3596
3597 mode = NUM2MODET(vmode);
3598
3599 GetOpenFile(obj, fptr);
3600#ifdef HAVE_FCHMOD
3601 if (rb_fchmod(fptr, mode) == -1) {
3602 if (HAVE_FCHMOD || errno != ENOSYS)
3603 rb_sys_fail_path(fptr->pathv);
3604 }
3605 else {
3606 if (!HAVE_FCHMOD) return INT2FIX(0);
3607 }
3608#endif
3609#if !defined HAVE_FCHMOD || !HAVE_FCHMOD
3610 if (NIL_P(fptr->pathv)) return Qnil;
3611 path = rb_str_encode_ospath(fptr->pathv);
3612 if (rb_chmod(RSTRING_PTR(path), mode) == -1)
3613 rb_sys_fail_path(fptr->pathv);
3614#endif
3615
3616 return INT2FIX(0);
3617}
3618
3619#if defined(HAVE_LCHMOD)
3620static int
3621lchmod_internal(const char *path, void *mode)
3622{
3623 return lchmod(path, *(mode_t *)mode);
3624}
3625
3626/*
3627 * :markup: markdown
3628 *
3629 * call-seq:
3630 * File.lchmod(mode, *paths) -> paths_count
3631 *
3632 * Not supported on Linux or Windows (raises NotImplementedError).
3633 *
3634 * When supported: like File::chmod,
3635 * but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md),
3636 * and therefore changes the mode of the entries given by `paths`;
3637 * returns the number of paths given:
3638 *
3639 * ```ruby
3640 * File.write('t.tmp', '')
3641 * File.symlink('t.tmp', 'link')
3642 * File.lstat('t.tmp').mode.to_s(8) # => "100664"
3643 * File.lstat('link').mode.to_s(8) # => "120755"
3644 * File.lchmod(0777, 'link')
3645 * File.lstat('t.tmp').mode.to_s(8) # => "100664"
3646 * File.lstat('link').mode.to_s(8) # => "120777"
3647 * File.delete('t.tmp')
3648 * File.delete('link')
3649 * ```
3650 */
3651
3652static VALUE
3653rb_file_s_lchmod(int argc, VALUE *argv, VALUE _)
3654{
3655 mode_t mode;
3656
3657 apply2args(1);
3658 mode = NUM2MODET(*argv++);
3659
3660 return apply2files(lchmod_internal, argc, argv, &mode);
3661}
3662#else
3663#define rb_file_s_lchmod rb_f_notimplement
3664#endif
3665
3666static inline rb_uid_t
3667to_uid(VALUE u)
3668{
3669 if (NIL_P(u)) {
3670 return (rb_uid_t)-1;
3671 }
3672 return NUM2UIDT(u);
3673}
3674
3675static inline rb_gid_t
3676to_gid(VALUE g)
3677{
3678 if (NIL_P(g)) {
3679 return (rb_gid_t)-1;
3680 }
3681 return NUM2GIDT(g);
3682}
3683
3685 rb_uid_t owner;
3686 rb_gid_t group;
3687};
3688
3689static int
3690chown_internal(const char *path, void *arg)
3691{
3692 struct chown_args *args = arg;
3693 return chown(path, args->owner, args->group);
3694}
3695
3696/*
3697 * call-seq:
3698 * File.chown(owner_int, group_int, *paths) -> integer
3699 *
3700 * Changes the owner and group of the entry at each of the given +paths+;
3701 * returns the count of the given +paths+:
3702 *
3703 * # Super user; all privileges.
3704 * Process.uid => 0
3705 * Process.gid => 0
3706 * # Create a directory and a file.
3707 * dirpath = 'doc/foo'
3708 * Dir.mkdir(dirpath)
3709 * filepath = 't.tmp'
3710 * File.write(filepath, 'foo')
3711 * # Get their user and group ids.
3712 * dirstat = File::Stat.new(dirpath)
3713 * dirstat.uid => 0
3714 * dirstat.gid => 0
3715 * filestat = File::Stat.new(filepath)
3716 * filestat.uid => 0
3717 * filestat.gid => 0
3718 * # Change ownership of both.
3719 * File.chown(1000, 1000, filepath, dirpath) => 2
3720 * dirstat = File::Stat.new(dirpath)
3721 * dirstat.uid => 1000
3722 * dirstat.gid => 1000
3723 * filestat = File::Stat.new(filepath)
3724 * filestat.uid => 1000
3725 * filestat.gid => 1000
3726 * # Clean up.
3727 * Dir.rmdir(dirpath)
3728 * File.delete(filepath)
3729 *
3730 * Notes:
3731 *
3732 * - On Windows, the owner and group are not changed.
3733 * - Only a process with superuser privileges can change the owner of an entry.
3734 * - The owner of an entry can change its group to any group
3735 * to which the owner belongs.
3736 * - A +nil+ or +-1+ owner or group id is ignored.
3737 * - The method follows symbolic links to the target entry.
3738 *
3739 */
3740
3741static VALUE
3742rb_file_s_chown(int argc, VALUE *argv, VALUE _)
3743{
3744 struct chown_args arg;
3745
3746 apply2args(2);
3747 arg.owner = to_uid(*argv++);
3748 arg.group = to_gid(*argv++);
3749
3750 return apply2files(chown_internal, argc, argv, &arg);
3751}
3752
3754 union {
3755 const char *path;
3756 int fd;
3757 } as;
3758 struct chown_args new;
3759};
3760
3761static void *
3762nogvl_chown(void *ptr)
3763{
3764 struct nogvl_chown_data *data = ptr;
3765 return (void *)(VALUE)chown(data->as.path, data->new.owner, data->new.group);
3766}
3767
3768static int
3769rb_chown(const char *path, rb_uid_t owner, rb_gid_t group)
3770{
3771 struct nogvl_chown_data data = {
3772 .as = {.path = path},
3773 .new = {.owner = owner, .group = group},
3774 };
3775 return IO_WITHOUT_GVL_INT(nogvl_chown, &data);
3776}
3777
3778#ifdef HAVE_FCHOWN
3779static void *
3780nogvl_fchown(void *ptr)
3781{
3782 struct nogvl_chown_data *data = ptr;
3783 return (void *)(VALUE)fchown(data->as.fd, data->new.owner, data->new.group);
3784}
3785
3786static int
3787rb_fchown(int fd, rb_uid_t owner, rb_gid_t group)
3788{
3789 (void)rb_chown; /* suppress unused-function warning when HAVE_FCHMOD */
3790 struct nogvl_chown_data data = {
3791 .as = {.fd = fd},
3792 .new = {.owner = owner, .group = group},
3793 };
3794 return IO_WITHOUT_GVL_INT(nogvl_fchown, &data);
3795}
3796#endif
3797
3798/*
3799 * call-seq:
3800 * file.chown(owner_int, group_int ) -> 0
3801 *
3802 * Changes the owner and group of <i>file</i> to the given numeric
3803 * owner and group id's. Only a process with superuser privileges may
3804 * change the owner of a file. The current owner of a file may change
3805 * the file's group to any group to which the owner belongs. A +nil+
3806 * or -1 owner or group id is ignored. Follows symbolic links. See
3807 * also File#lchown.
3808 *
3809 * File.new("testfile").chown(502, 1000)
3810 *
3811 */
3812
3813static VALUE
3814rb_file_chown(VALUE obj, VALUE owner, VALUE group)
3815{
3816 rb_io_t *fptr;
3817 rb_uid_t o;
3818 rb_gid_t g;
3819#ifndef HAVE_FCHOWN
3820 VALUE path;
3821#endif
3822
3823 o = to_uid(owner);
3824 g = to_gid(group);
3825 GetOpenFile(obj, fptr);
3826#ifndef HAVE_FCHOWN
3827 if (NIL_P(fptr->pathv)) return Qnil;
3828 path = rb_str_encode_ospath(fptr->pathv);
3829 if (rb_chown(RSTRING_PTR(path), o, g) == -1)
3830 rb_sys_fail_path(fptr->pathv);
3831#else
3832 if (rb_fchown(fptr->fd, o, g) == -1)
3833 rb_sys_fail_path(fptr->pathv);
3834#endif
3835
3836 return INT2FIX(0);
3837}
3838
3839#if defined(HAVE_LCHOWN)
3840static int
3841lchown_internal(const char *path, void *arg)
3842{
3843 struct chown_args *args = arg;
3844 return lchown(path, args->owner, args->group);
3845}
3846
3847/*
3848 * :markup: markdown
3849 *
3850 * call-seq:
3851 * File.lchown(uid, gid, *paths ) -> paths_count
3852 *
3853 * Not supported on some platforms (raises exception).
3854 *
3855 * Calling process must have superuser privileges.
3856 *
3857 * When supported: like File::chown,
3858 * but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md),
3859 * and therefore changes the ownership of the entries given by `paths`;
3860 * returns the number of paths given:
3861 *
3862 * ```ruby
3863 * # Super user; all privileges.
3864 * Process.uid # => 0
3865 * Process.gid # => 0
3866 * # Create regular file and symbolic link to it.
3867 * File.write('t.tmp', '')
3868 * File.symlink('t.tmp', 'link')
3869 * Capture original statuses.
3870 * fstat0 = File.stat('t.tmp') # Method ::stat; status of file.
3871 * lstat0 = File.lstat('link') # Method ::lstat; status of link.
3872 * # Original user ids and group ids.
3873 * fstat0.uid => 0
3874 * fstat0.gid => 0
3875 * lstat0.uid => 0
3876 * lstat0.gid => 0
3877 * # Change ids for link.
3878 * File.lchown(1000, 1000, 'link') # => 1
3879 * # Capture new statuses.
3880 * fstat1 = File.stat('t.tmp')
3881 * lstat1 = File.stat('link')
3882 * # User id and group id for file not changed..
3883 * fstat1.uid # => 0
3884 * fstat1.gid # => 0
3885 * # User is and group id for link changed.
3886 * lstat1.uid # => 1000
3887 * lstat1.gid # => 1000
3888 * Clean up.
3889 * File.delete('t.tmp')
3890 * File.delete('link')
3891 * ```
3892 *
3893 */
3894
3895static VALUE
3896rb_file_s_lchown(int argc, VALUE *argv, VALUE _)
3897{
3898 struct chown_args arg;
3899
3900 apply2args(2);
3901 arg.owner = to_uid(*argv++);
3902 arg.group = to_gid(*argv++);
3903
3904 return apply2files(lchown_internal, argc, argv, &arg);
3905}
3906#else
3907#define rb_file_s_lchown rb_f_notimplement
3908#endif
3909
3911 const struct timespec* tsp;
3912 VALUE atime, mtime;
3913 int follow; /* Whether to act on symlinks (1) or their referent (0) */
3914};
3915
3916#ifdef UTIME_EINVAL
3917NORETURN(static void utime_failed(struct apply_arg *));
3918
3919static void
3920utime_failed(struct apply_arg *aa)
3921{
3922 int e = aa->errnum;
3923 VALUE path = aa->fn[aa->i].path;
3924 struct utime_args *ua = aa->arg;
3925
3926 if (ua->tsp && e == EINVAL) {
3927 VALUE e[2], a = Qnil, m = Qnil;
3928 int d = 0;
3929 VALUE atime = ua->atime;
3930 VALUE mtime = ua->mtime;
3931
3932 if (!NIL_P(atime)) {
3933 a = rb_inspect(atime);
3934 }
3935 if (!NIL_P(mtime) && mtime != atime && !rb_equal(atime, mtime)) {
3936 m = rb_inspect(mtime);
3937 }
3938 if (NIL_P(a)) e[0] = m;
3939 else if (NIL_P(m) || rb_str_cmp(a, m) == 0) e[0] = a;
3940 else {
3941 e[0] = rb_str_plus(a, rb_str_new_cstr(" or "));
3942 rb_str_append(e[0], m);
3943 d = 1;
3944 }
3945 if (!NIL_P(e[0])) {
3946 if (path) {
3947 if (!d) e[0] = rb_str_dup(e[0]);
3948 rb_str_append(rb_str_cat2(e[0], " for "), path);
3949 }
3950 e[1] = INT2FIX(EINVAL);
3952 }
3953 }
3954 rb_syserr_fail_path(e, path);
3955}
3956#endif /* UTIME_EINVAL */
3957
3958#if defined(HAVE_UTIMES)
3959
3960# if !defined(HAVE_UTIMENSAT)
3961/* utimensat() is not found, runtime check is not needed */
3962# elif defined(__APPLE__) && \
3963 (!defined(MAC_OS_X_VERSION_13_0) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_13_0))
3964
3965# if __has_attribute(availability) && __has_warning("-Wunguarded-availability-new")
3966typedef int utimensat_func(int, const char *, const struct timespec [2], int);
3967
3969RBIMPL_WARNING_IGNORED(-Wunguarded-availability-new)
3970static inline utimensat_func *
3971rb_utimensat(void)
3972{
3973 return &utimensat;
3974}
3976
3977# define utimensat rb_utimensat()
3978# else /* __API_AVAILABLE macro does nothing on gcc */
3979__attribute__((weak)) int utimensat(int, const char *, const struct timespec [2], int);
3980# endif /* utimesat availability */
3981# endif /* __APPLE__ && < MAC_OS_X_VERSION_13_0 */
3982
3983static int
3984utime_internal(const char *path, void *arg)
3985{
3986 struct utime_args *v = arg;
3987 const struct timespec *tsp = v->tsp;
3988 struct timeval tvbuf[2], *tvp = NULL;
3989
3990#if defined(HAVE_UTIMENSAT)
3991# if defined(__APPLE__)
3992 const int try_utimensat = utimensat != NULL;
3993 const int try_utimensat_follow = utimensat != NULL;
3994# else /* !__APPLE__ */
3995# define TRY_UTIMENSAT 1
3996 static int try_utimensat = 1;
3997# ifdef AT_SYMLINK_NOFOLLOW
3998 static int try_utimensat_follow = 1;
3999# else
4000 const int try_utimensat_follow = 0;
4001# endif
4002# endif /* __APPLE__ */
4003 int flags = 0;
4004
4005 if (v->follow ? try_utimensat_follow : try_utimensat) {
4006# ifdef AT_SYMLINK_NOFOLLOW
4007 if (v->follow) {
4008 flags = AT_SYMLINK_NOFOLLOW;
4009 }
4010# endif
4011
4012 int result = utimensat(AT_FDCWD, path, tsp, flags);
4013# ifdef TRY_UTIMENSAT
4014 if (result < 0 && errno == ENOSYS) {
4015# ifdef AT_SYMLINK_NOFOLLOW
4016 try_utimensat_follow = 0;
4017# endif /* AT_SYMLINK_NOFOLLOW */
4018 if (!v->follow)
4019 try_utimensat = 0;
4020 }
4021 else
4022# endif /* TRY_UTIMESAT */
4023 return result;
4024 }
4025#endif /* defined(HAVE_UTIMENSAT) */
4026
4027 if (tsp) {
4028 tvbuf[0].tv_sec = tsp[0].tv_sec;
4029 tvbuf[0].tv_usec = (int)(tsp[0].tv_nsec / 1000);
4030 tvbuf[1].tv_sec = tsp[1].tv_sec;
4031 tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
4032 tvp = tvbuf;
4033 }
4034#ifdef HAVE_LUTIMES
4035 if (v->follow) return lutimes(path, tvp);
4036#endif
4037 return utimes(path, tvp);
4038}
4039
4040#else /* !defined(HAVE_UTIMES) */
4041
4042#if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H
4043struct utimbuf {
4044 long actime;
4045 long modtime;
4046};
4047#endif
4048
4049static int
4050utime_internal(const char *path, void *arg)
4051{
4052 struct utime_args *v = arg;
4053 const stat_timestamp *tsp = v->tsp;
4054 struct utimbuf utbuf, *utp = NULL;
4055 if (tsp) {
4056 utbuf.actime = tsp[0].tv_sec;
4057 utbuf.modtime = tsp[1].tv_sec;
4058 utp = &utbuf;
4059 }
4060 return utime(path, utp);
4061}
4062#endif /* !defined(HAVE_UTIMES) */
4063
4064static VALUE
4065utime_internal_i(int argc, VALUE *argv, int follow)
4066{
4067 struct utime_args args;
4068 struct timespec tss[2], *tsp = NULL;
4069
4070 apply2args(2);
4071 args.atime = *argv++;
4072 args.mtime = *argv++;
4073
4074 args.follow = follow;
4075
4076 if (!NIL_P(args.atime) || !NIL_P(args.mtime)) {
4077 tsp = tss;
4078 tsp[0] = rb_time_timespec(args.atime);
4079 if (args.atime == args.mtime)
4080 tsp[1] = tsp[0];
4081 else
4082 tsp[1] = rb_time_timespec(args.mtime);
4083 }
4084 args.tsp = tsp;
4085
4086 return apply2files(utime_internal, argc, argv, &args);
4087}
4088
4089/*
4090 * :markup: markdown
4091 *
4092 * call-seq:
4093 * File.utime(atime, mtime, *paths) -> integer
4094 *
4095 * For the entry at the paths in `paths`,
4096 * updates its access time to the given `atime`
4097 * and its modification time to the given `mtime`;
4098 * see [Filesystem Timestamps](rdoc-ref:file/timestamps.md).
4099 * Returns the number of entries updated.
4100 * Each path points to a file or directory.
4101 *
4102 * Each given time may be a Time object, an integer representing a time,
4103 * or `nil` (meaning Time.now):
4104 *
4105 * ```ruby
4106 * filepath = '/tmp/t.tmp'
4107 * File.write(filepath, 'foo')
4108 * File.atime(filepath) # => 2026-09-29 12:38:29.889703781 -0500
4109 * File.mtime(filepath) # => 2026-09-29 12:38:29.889703781 -0500
4110 * time = Time.now
4111 * File.utime(time, time, filepath)
4112 * File.atime(filepath) # => 2026-09-29 12:38:52.533160634 -0500
4113 * File.mtime(filepath) # => 2026-09-29 12:38:52.533160634 -0500
4114 * File.utime(0, 0, filepath)
4115 * File.atime(filepath) # => 1969-12-31 18:00:00 -0600
4116 * File.mtime(filepath) # => 1969-12-31 18:00:00 -0600
4117 * File.utime(nil, nil, filepath)
4118 * File.atime(filepath) # => 2026-09-29 12:39:50.859421265 -0500
4119 * File.mtime(filepath) # => 2026-09-29 12:39:50.859421265 -0500
4120 * File.delete(filepath) # Clean up.
4121 * ```
4122 *
4123 * Raises an exception if any entry cannot be updated;
4124 * some entries may have already been updated.
4125 *
4126 * Follows symbolic links; use File.lutime to update the times for symbolic links.
4127 */
4128
4129static VALUE
4130rb_file_s_utime(int argc, VALUE *argv, VALUE _)
4131{
4132 return utime_internal_i(argc, argv, FALSE);
4133}
4134
4135#if defined(HAVE_UTIMES) && (defined(HAVE_LUTIMES) || (defined(HAVE_UTIMENSAT) && defined(AT_SYMLINK_NOFOLLOW)))
4136
4137/*
4138 * :markup: markdown
4139 *
4140 * call-seq:
4141 * File.lutime(atime, mtime, *paths) -> path_count
4142 *
4143 * Like File::utime,
4144 * but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md),
4145 * and therefore changes the times of the entries given by `paths`,
4146 * regardless of whether they are symbolic links;
4147 * returns the number of `paths` given:
4148 *
4149 * ```ruby
4150 * # Create a file and a link to it.
4151 * file_path = 't.tmp'
4152 * File.write(file_path, '')
4153 * link_path = 'link'
4154 * File.symlink(file_path, link_path)
4155 * # Take snapshots of both.
4156 * file_stat = File.stat(file_path)
4157 * link_stat = File.lstat(link_path)
4158 * # Fetch access times and modification times of both.
4159 * file_stat.atime # => 2026-06-15 10:45:11.376753268 -0500
4160 * file_stat.mtime # => 2026-06-15 10:44:47.335854904 -0500
4161 * link_stat.atime # => 2026-06-15 10:44:59.788801128 -0500
4162 * link_stat.mtime # => 2026-06-15 10:44:49.367845961 -0500
4163 * # Update access time and modification time of the link.
4164 * time = Time.now # => 2026-06-15 10:48:57.847422496 -0500
4165 * File.lutime(time, time, link_path)
4166 * # Take fresh snapshots of both.
4167 * file_stat = File.stat(file_path)
4168 * link_stat = File.lstat(link_path)
4169 * # Fetch access time and modification time of file (not changed).
4170 * file_stat.atime # => 2026-06-15 10:45:11.376753268 -0500
4171 * file_stat.mtime # => 2026-06-15 10:44:47.335854904 -0500
4172 * # Fetch access time and modification time of link (changed).
4173 * link_stat.atime # => 2026-06-15 10:49:27.119146136 -0500
4174 * link_stat.mtime # => 2026-06-15 10:48:57.847422496 -0500
4175 * # Clean up.
4176 * File.delete(file_path)
4177 * File.delete(link_path)
4178 * ```
4179 *
4180 * Arguments `atime` and `mtime` may be Time objects (as above).
4181 *
4182 * Either or both may be integers;
4183 * when an integer `i` is passed, `Time.new(i)` is used.
4184 *
4185 * Either or both may be `nil`, in which case `Time.now` is used.
4186 *
4187 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
4188 */
4189
4190static VALUE
4191rb_file_s_lutime(int argc, VALUE *argv, VALUE _)
4192{
4193 return utime_internal_i(argc, argv, TRUE);
4194}
4195#else
4196#define rb_file_s_lutime rb_f_notimplement
4197#endif
4198
4199#ifdef RUBY_FUNCTION_NAME_STRING
4200# define syserr_fail2(e, s1, s2) syserr_fail2_in(RUBY_FUNCTION_NAME_STRING, e, s1, s2)
4201#else
4202# define syserr_fail2_in(func, e, s1, s2) syserr_fail2(e, s1, s2)
4203#endif
4204#define sys_fail2(s1, s2) syserr_fail2(errno, s1, s2)
4205NORETURN(static void syserr_fail2_in(const char *,int,VALUE,VALUE));
4206static void
4207syserr_fail2_in(const char *func, int e, VALUE s1, VALUE s2)
4208{
4209 VALUE str;
4210#ifdef MAX_PATH
4211 const int max_pathlen = MAX_PATH;
4212#else
4213 const int max_pathlen = MAXPATHLEN;
4214#endif
4215
4216 if (e == EEXIST) {
4217 rb_syserr_fail_path(e, rb_str_ellipsize(s2, max_pathlen));
4218 }
4219 str = rb_str_new_cstr("(");
4220 rb_str_append(str, rb_str_ellipsize(s1, max_pathlen));
4221 rb_str_cat2(str, ", ");
4222 rb_str_append(str, rb_str_ellipsize(s2, max_pathlen));
4223 rb_str_cat2(str, ")");
4224#ifdef RUBY_FUNCTION_NAME_STRING
4225 rb_syserr_fail_path_in(func, e, str);
4226#else
4227 rb_syserr_fail_path(e, str);
4228#endif
4229}
4230
4231#ifdef HAVE_LINK
4232/*
4233 * :markup: markdown
4234
4235 * call-seq:
4236 * File.link(path, new_path) -> 0
4237 *
4238 * Not available on some systems.
4239 *
4240 * Creates a new entry at `new_path` for the existing entry at `path`
4241 * using a [hard link](https://en.wikipedia.org/wiki/Hard_link):
4242 *
4243 * ```ruby
4244 * File.write('doc/t.tmp', 'foo')
4245 * File.link('doc/t.tmp', 'lib/u.tmp')
4246 * File.read('lib/u.tmp') # => "foo"
4247 * File.write('lib/u.tmp', 'bar')
4248 * File.read('doc/t.tmp') # => "bar"
4249 * File.delete('doc/t.tmp')
4250 * File.read('lib/u.tmp') # => "bar"
4251 * File.delete('lib/u.tmp')
4252 * ```
4253 *
4254 * Raises an exception if the entry at `new_path` exists.
4255 */
4256
4257static VALUE
4258rb_file_s_link(VALUE klass, VALUE from, VALUE to)
4259{
4260 FilePathValue(from);
4261 FilePathValue(to);
4262 from = rb_str_encode_ospath(from);
4263 to = rb_str_encode_ospath(to);
4264
4265 if (link(StringValueCStr(from), StringValueCStr(to)) < 0) {
4266 sys_fail2(from, to);
4267 }
4268 return INT2FIX(0);
4269}
4270#else
4271#define rb_file_s_link rb_f_notimplement
4272#endif
4273
4274#ifdef HAVE_SYMLINK
4275/*
4276 * :markup: markdown
4277 *
4278 * call-seq:
4279 * File.symlink(target_path, link_path) -> 0
4280 *
4281 * Not supported on some platforms.
4282 *
4283 * Creates a [symbolic link](rdoc-ref:file/symbolic_links.md)
4284 * at `link_path` to the entry at `target_path`:
4285 *
4286 * ```ruby
4287 * filepath = '/etc/passwd' # Regular file.
4288 * linkpath = '/tmp/foo'
4289 * File.symlink(filepath, linkpath)
4290 * File.readlink(linkpath) # => "/etc/passwd"
4291 * File.read(filepath) == File.read(linkpath) # => true
4292 * ```
4293 *
4294 * If the entry at `target_path` is itself a symbolic link,
4295 * that link is _not_ followed:
4296 *
4297 * ```ruby
4298 * link2path = '/tmp/bar'
4299 * File.symlink(linkpath, link2path)
4300 * File.readlink(link2path) # => "/tmp/foo"
4301 * File.read(filepath) == File.read(link2path) # => true
4302 * File.delete(linkpath, link2path) # Clean up.
4303 * ```
4304 *
4305 */
4306
4307static VALUE
4308rb_file_s_symlink(VALUE klass, VALUE from, VALUE to)
4309{
4310 FilePathValue(from);
4311 FilePathValue(to);
4312 from = rb_str_encode_ospath(from);
4313 to = rb_str_encode_ospath(to);
4314
4315 if (symlink(StringValueCStr(from), StringValueCStr(to)) < 0) {
4316 sys_fail2(from, to);
4317 }
4318 return INT2FIX(0);
4319}
4320#else
4321#define rb_file_s_symlink rb_f_notimplement
4322#endif
4323
4324#ifdef HAVE_READLINK
4325/*
4326 * :markup: markdown
4327 *
4328 * call-seq:
4329 * File.readlink(link_path) -> string
4330 *
4331 * Returns the string path to the entry referenced
4332 * by the [symbolic link](rdoc-ref:file/symbolic_links.md) at `link_path`:
4333 *
4334 * ```ruby
4335 * filepath = 'doc/maintainers.md'
4336 * linkpath = '/tmp/link'
4337 * File.symlink(filepath, linkpath)
4338 * File.readlink(linkpath) # => "doc/maintainers.md"
4339 * File.delete(linkpath) # Clean up.
4340 * ```
4341 *
4342 * Raises Errno::EINVAL if the entry referenced by `link_path`
4343 * is not a symbolic link.
4344 */
4345
4346static VALUE
4347rb_file_s_readlink(VALUE klass, VALUE path)
4348{
4349 return rb_readlink(path, rb_filesystem_encoding());
4350}
4351
4352struct readlink_arg {
4353 const char *path;
4354 char *buf;
4355 size_t size;
4356};
4357
4358static void *
4359nogvl_readlink(void *ptr)
4360{
4361 struct readlink_arg *ra = ptr;
4362
4363 return (void *)(VALUE)readlink(ra->path, ra->buf, ra->size);
4364}
4365
4366static ssize_t
4367readlink_without_gvl(VALUE path, VALUE buf, size_t size)
4368{
4369 struct readlink_arg ra;
4370
4371 ra.path = RSTRING_PTR(path);
4372 ra.buf = RSTRING_PTR(buf);
4373 ra.size = size;
4374
4375 return (ssize_t)IO_WITHOUT_GVL(nogvl_readlink, &ra);
4376}
4377
4378VALUE
4379rb_readlink(VALUE path, rb_encoding *enc)
4380{
4381 int size = 100;
4382 ssize_t rv;
4383 VALUE v;
4384
4385 FilePathValue(path);
4386 path = rb_str_encode_ospath(path);
4387 v = rb_enc_str_new(0, size, enc);
4388 while ((rv = readlink_without_gvl(path, v, size)) == size
4389#ifdef _AIX
4390 || (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */
4391#endif
4392 ) {
4393 rb_str_modify_expand(v, size);
4394 size *= 2;
4395 rb_str_set_len(v, size);
4396 }
4397 if (rv < 0) {
4398 int e = errno;
4399 rb_str_resize(v, 0);
4400 rb_syserr_fail_path(e, path);
4401 }
4402 rb_str_resize(v, rv);
4403
4404 return v;
4405}
4406#else
4407#define rb_file_s_readlink rb_f_notimplement
4408#endif
4409
4410static int
4411unlink_internal(const char *path, void *arg)
4412{
4413 return unlink(path);
4414}
4415
4416/*
4417 * :markup: markdown
4418 *
4419 * call-seq:
4420 * File.delete(*paths) -> integer
4421 * File.unlink(*paths) -> integer
4422 *
4423 * Removes the entry ([hard link](rdoc-ref:file/hard_links.md)) at each path in `paths`;
4424 * returns the count of removed entries:
4425 *
4426 * ```ruby
4427 * filepath0 = '/tmp/t0.tmp'
4428 * filepath1 = '/tmp/t1.tmp'
4429 * File.write(filepath0, 'foo')
4430 * File.write(filepath1, 'bar')
4431 * File.unlink(filepath0, filepath1) # => 2
4432 * ```
4433 *
4434 * If the removed hard link is the last one associated with the inode,
4435 * also removes the inode; otherwise, not.
4436 * See [Unlinking](rdoc-ref:file/hard_links.md@Unlinking).
4437 *
4438 * Does not follow [symbolic links](rdoc-ref:file/symbolic_links.md);
4439 * if the entry is a symbolic link, removes the entry itself (not the link target).
4440 *
4441 * ```ruby
4442 * filepath = '/tmp/t.tmp'
4443 * linkpath = '/tmp/link'
4444 * File.write(filepath, 'foo')
4445 * File.symlink(filepath, linkpath)
4446 * File.unlink(linkpath) # => 1
4447 * File.exist?(filepath) # => true
4448 * File.unlink(filepath) # => 1
4449 * ```
4450 *
4451 * Raises an exception on any error;
4452 * some entries may have been deleted before the error occurs.
4453 */
4454
4455static VALUE
4456rb_file_s_unlink(int argc, VALUE *argv, VALUE klass)
4457{
4458 return apply2files(unlink_internal, argc, argv, 0);
4459}
4460
4462 const char *src;
4463 const char *dst;
4464};
4465
4466static void *
4467no_gvl_rename(void *ptr)
4468{
4469 struct rename_args *ra = ptr;
4470
4471 return (void *)(VALUE)rename(ra->src, ra->dst);
4472}
4473
4474/*
4475 * :markup: markdown
4476 *
4477 * call-seq:
4478 * File.rename(path, new_path) -> 0
4479 *
4480 * Moves the entry at the given `path` to the given `new_path`.
4481 *
4482 * Does not follow [symbolic links](rdoc-ref:file/symbolic_links.md);
4483 * if the entry is a symlink, the link itself is renamed.
4484 *
4485 * The examples below use two temporary directories:
4486 *
4487 * ```ruby
4488 * src_dirpath = '/tmp/src/' # => "/tmp/src/"
4489 * dst_dirpath = '/tmp/dst/' # => "/tmp/dst/"
4490 * Dir.mkdir(src_dirpath)
4491 * Dir.mkdir(dst_dirpath)
4492 * ```
4493 *
4494 * The entry to be renamed may be a file:
4495 *
4496 * ```ruby
4497 * src_filepath = File.join(src_dirpath, 't.tmp') # => "/tmp/src/t.tmp"
4498 * File.write(src_filepath, 'foo')
4499 * dst_filepath = File.join(dst_dirpath, 'u.tmp') # => "/tmp/dst/u.tmp"
4500 * File.rename(src_filepath, dst_filepath)
4501 * File.exist?(src_filepath) # => false
4502 * File.exist?(dst_filepath) # => true
4503 * File.delete(dst_filepath) # Clean up.
4504 * ```
4505 *
4506 * The entry to be renamed may be a symbolic link:
4507 *
4508 * ```ruby
4509 * filepath = File.join(src_dirpath, 't.tmp') # => "/tmp/src/t.tmp"
4510 * File.write(src_filepath, 'foo')
4511 * linkpath = File.join(src_dirpath, 'u.tmp') # => "/tmp/src/u.tmp"
4512 * File.symlink(filepath, linkpath)
4513 * File.readlink(linkpath) # => "/tmp/src/t.tmp"
4514 * newpath = File.join(dst_dirpath, 'v.tmp') # => "/tmp/dst/v.tmp"
4515 * File.rename(linkpath, newpath) # Symlink not followed.
4516 * File.readlink(newpath) # => "/tmp/src/t.tmp"
4517 * File.delete(filepath, newpath) # Clean up.
4518 * ```
4519 *
4520 * The entry to be renamed may be a directory:
4521 *
4522 * ```ruby
4523 * old_dirpath = File.join(src_dirpath, 'olddir') # => "/tmp/src/olddir"
4524 * Dir.mkdir(old_dirpath)
4525 * new_dirpath = File.join(dst_dirpath, 'newdir') # => "/tmp/dst/newdir"
4526 * File.rename(old_dirpath, new_dirpath)
4527 * File.directory?(new_dirpath) # => true
4528 * Dir.rmdir(new_dirpath) # Clean up.
4529 * ```
4530 *
4531 * Clean up:
4532 *
4533 * ```ruby
4534 * FileUtils.rm_rf(src_dirpath) # => ["/tmp/src/"]
4535 * FileUtils.rm_rf(dst_dirpath) # => ["/tmp/dst/"]
4536 * ```
4537 *
4538 * Raises SystemCallError if the file cannot be renamed.
4539 */
4540
4541static VALUE
4542rb_file_s_rename(VALUE klass, VALUE from, VALUE to)
4543{
4544 struct rename_args ra;
4545 VALUE f, t;
4546
4547 FilePathValue(from);
4548 FilePathValue(to);
4549 f = rb_str_encode_ospath(from);
4550 t = rb_str_encode_ospath(to);
4551 ra.src = StringValueCStr(f);
4552 ra.dst = StringValueCStr(t);
4553#if defined __CYGWIN__
4554 errno = 0;
4555#endif
4556 if (IO_WITHOUT_GVL_INT(no_gvl_rename, &ra) < 0) {
4557 int e = errno;
4558#if defined DOSISH
4559 switch (e) {
4560 case EEXIST:
4561 if (chmod(ra.dst, 0666) == 0 &&
4562 unlink(ra.dst) == 0 &&
4563 rename(ra.src, ra.dst) == 0)
4564 return INT2FIX(0);
4565 }
4566#endif
4567 syserr_fail2(e, from, to);
4568 }
4569
4570 return INT2FIX(0);
4571}
4572
4573/*
4574 * call-seq:
4575 * File.umask() -> integer
4576 * File.umask(integer) -> integer
4577 *
4578 * Returns the current umask value for this process. If the optional argument
4579 * is given, set the umask to that value and return the previous value. Umask
4580 * values are <em>subtracted</em> from the default permissions, so a umask of
4581 * +0222+ would make a file read-only for everyone.
4582 *
4583 * File.umask(0006) #=> 18
4584 * File.umask #=> 6
4585 */
4586
4587static VALUE
4588rb_file_s_umask(int argc, VALUE *argv, VALUE _)
4589{
4590 mode_t omask = 0;
4591
4592 switch (argc) {
4593 case 0:
4594 omask = umask(0);
4595 umask(omask);
4596 break;
4597 case 1:
4598 omask = umask(NUM2MODET(argv[0]));
4599 break;
4600 default:
4601 rb_error_arity(argc, 0, 1);
4602 }
4603 return MODET2NUM(omask);
4604}
4605
4606#ifdef __CYGWIN__
4607#undef DOSISH
4608#endif
4609#if defined __CYGWIN__ || defined DOSISH
4610#define DOSISH_UNC
4611#define DOSISH_DRIVE_LETTER
4612#define FILE_ALT_SEPARATOR '\\'
4613#endif
4614#ifdef FILE_ALT_SEPARATOR
4615#define isdirsep(x) ((x) == '/' || (x) == FILE_ALT_SEPARATOR)
4616# ifdef DOSISH
4617static const char file_alt_separator[] = {FILE_ALT_SEPARATOR, '\0'};
4618# endif
4619#else
4620#define isdirsep(x) ((x) == '/')
4621#endif
4622
4623#ifndef USE_NTFS
4624# if defined _WIN32
4625# define USE_NTFS 1
4626# else
4627# define USE_NTFS 0
4628# endif
4629#endif
4630
4631#if USE_NTFS
4632#define istrailinggarbage(x) ((x) == '.' || (x) == ' ')
4633#define isADS(x) ((x) == ':')
4634#else
4635#define istrailinggarbage(x) 0
4636#endif
4637
4638#define enc_mbclen_needed(enc) (!rb_str_encindex_fastpath(rb_enc_to_index(enc)))
4639
4640#define Next(p, e, mb_enc, enc) ((p) + ((mb_enc) ? rb_enc_mbclen((p), (e), (enc)) : 1))
4641#define Inc(p, e, mb_enc, enc) ((p) = Next((p), (e), (mb_enc), (enc)))
4642
4643#if defined(DOSISH_UNC)
4644#define has_unc(buf) (isdirsep((buf)[0]) && isdirsep((buf)[1]))
4645#else
4646#define has_unc(buf) 0
4647#endif
4648
4649#ifdef DOSISH_DRIVE_LETTER
4650static inline int
4651has_drive_letter(const char *buf)
4652{
4653 if (ISALPHA(buf[0]) && buf[1] == ':') {
4654 return 1;
4655 }
4656 else {
4657 return 0;
4658 }
4659}
4660
4661#ifndef _WIN32
4662static VALUE
4663getcwdofdrv(int drv)
4664{
4665 char drive[4];
4666 char *oldcwd;
4667 VALUE drvcwd;
4668
4669 drive[0] = drv;
4670 drive[1] = ':';
4671 drive[2] = '\0';
4672
4673 /* the only way that I know to get the current directory
4674 of a particular drive is to change chdir() to that drive,
4675 so save the old cwd before chdir()
4676 */
4677 oldcwd = ruby_getcwd();
4678 if (chdir(drive) == 0) {
4679 drvcwd = rb_dir_getwd_ospath();
4680 chdir(oldcwd);
4681 xfree(oldcwd);
4682 }
4683 else {
4684 /* perhaps the drive is not exist. we return only drive letter */
4685 drvcwd = rb_enc_str_new_cstr(drive, rb_filesystem_encoding());
4686 }
4687 return drvcwd;
4688}
4689
4690static inline int
4691not_same_drive(VALUE path, int drive)
4692{
4693 const char *p = RSTRING_PTR(path);
4694 if (RSTRING_LEN(path) < 2) return 0;
4695 if (has_drive_letter(p)) {
4696 return TOLOWER(p[0]) != TOLOWER(drive);
4697 }
4698 else {
4699 return has_unc(p);
4700 }
4701}
4702#endif /* _WIN32 */
4703#endif /* DOSISH_DRIVE_LETTER */
4704
4705static inline char *
4706skiproot(const char *path, const char *end)
4707{
4708#ifdef DOSISH_DRIVE_LETTER
4709 if (path + 2 <= end && has_drive_letter(path)) path += 2;
4710#endif
4711 while (path < end && isdirsep(*path)) path++;
4712 return (char *)path;
4713}
4714
4715static inline char *
4716enc_path_next(const char *s, const char *e, bool mb_enc, rb_encoding *enc)
4717{
4718 while (s < e && !isdirsep(*s)) {
4719 Inc(s, e, mb_enc, enc);
4720 }
4721 return (char *)s;
4722}
4723
4724#define nextdirsep rb_enc_path_next
4725char *
4726rb_enc_path_next(const char *s, const char *e, rb_encoding *enc)
4727{
4728 return enc_path_next(s, e, enc_mbclen_needed(enc), enc);
4729}
4730
4731#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
4732#define skipprefix enc_path_skip_prefix
4733#else
4734#define skipprefix(path, end, mb_enc, enc) (path)
4735#endif
4736static inline char *
4737enc_path_skip_prefix(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
4738{
4739#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
4740#ifdef DOSISH_UNC
4741 if (path + 2 <= end && isdirsep(path[0]) && isdirsep(path[1])) {
4742 path += 2;
4743 while (path < end && isdirsep(*path)) path++;
4744 if ((path = enc_path_next(path, end, mb_enc, enc)) < end &&
4745 path + 2 <= end && !isdirsep(path[1])) {
4746 path = enc_path_next(path + 1, end, mb_enc, enc);
4747 }
4748 return (char *)path;
4749 }
4750#endif
4751#ifdef DOSISH_DRIVE_LETTER
4752 if (path + 2 <= end && has_drive_letter(path))
4753 return (char *)(path + 2);
4754#endif
4755#endif /* defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) */
4756 return (char *)path;
4757}
4758
4759char *
4760rb_enc_path_skip_prefix(const char *path, const char *end, rb_encoding *enc)
4761{
4762 return enc_path_skip_prefix(path, end, enc_mbclen_needed(enc), enc);
4763}
4764
4765static inline char *
4766skipprefixroot(const char *path, const char *end, rb_encoding *enc)
4767{
4768#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
4769 char *p = skipprefix(path, end, enc_mbclen_needed(enc), enc);
4770 while (p < end && isdirsep(*p)) p++;
4771 return p;
4772#else
4773 return skiproot(path, end);
4774#endif
4775}
4776
4777char *
4778rb_enc_path_skip_prefix_root(const char *path, const char *end, rb_encoding *enc)
4779{
4780 return skipprefixroot(path, end, enc);
4781}
4782
4783static char *
4784enc_path_last_separator(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
4785{
4786 char *last = NULL;
4787 while (path < end) {
4788 if (isdirsep(*path)) {
4789 const char *tmp = path++;
4790 while (path < end && isdirsep(*path)) path++;
4791 if (path >= end) break;
4792 last = (char *)tmp;
4793 }
4794 else {
4795 Inc(path, end, mb_enc, enc);
4796 }
4797 }
4798 return last;
4799}
4800char *
4801rb_enc_path_last_separator(const char *path, const char *end, rb_encoding *enc)
4802{
4803 return enc_path_last_separator(path, end, enc_mbclen_needed(enc), enc);
4804}
4805
4806static inline char *
4807strrdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
4808{
4809 if (RB_UNLIKELY(mb_enc)) {
4810 return enc_path_last_separator(path, end, mb_enc, enc);
4811 }
4812
4813 const char *cursor = end - 1;
4814
4815 while (cursor >= path && isdirsep(cursor[0])) {
4816 cursor--;
4817 }
4818
4819 while (cursor >= path) {
4820 if (isdirsep(cursor[0])) {
4821 while (cursor > path && isdirsep(cursor[-1])) {
4822 cursor--;
4823 }
4824 return (char *)cursor;
4825 }
4826 cursor--;
4827 }
4828 return NULL;
4829}
4830
4831static char *
4832chompdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
4833{
4834 while (path < end) {
4835 if (isdirsep(*path)) {
4836 const char *last = path++;
4837 while (path < end && isdirsep(*path)) path++;
4838 if (path >= end) return (char *)last;
4839 }
4840 else {
4841 Inc(path, end, mb_enc, enc);
4842 }
4843 }
4844 return (char *)path;
4845}
4846
4847char *
4848rb_enc_path_end(const char *path, const char *end, rb_encoding *enc)
4849{
4850 if (path < end && isdirsep(*path)) path++;
4851 return chompdirsep(path, end, enc_mbclen_needed(enc), enc);
4852}
4853
4854static rb_encoding *
4855fs_enc_check(VALUE path1, VALUE path2)
4856{
4857 rb_encoding *enc = rb_enc_check_str(path1, path2);
4858 int encidx = rb_enc_to_index(enc);
4859 if (encidx == ENCINDEX_US_ASCII) {
4860 encidx = rb_enc_get_index(path1);
4861 if (encidx == ENCINDEX_US_ASCII)
4862 encidx = rb_enc_get_index(path2);
4863 enc = rb_enc_from_index(encidx);
4864 }
4865 return enc;
4866}
4867
4868#if USE_NTFS
4869static char *
4870ntfs_tail(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
4871{
4872 while (path < end && *path == '.') path++;
4873 while (path < end && !isADS(*path)) {
4874 if (istrailinggarbage(*path)) {
4875 const char *last = path++;
4876 while (path < end && istrailinggarbage(*path)) path++;
4877 if (path >= end || isADS(*path)) return (char *)last;
4878 }
4879 else if (isdirsep(*path)) {
4880 const char *last = path++;
4881 while (path < end && isdirsep(*path)) path++;
4882 if (path >= end) return (char *)last;
4883 if (isADS(*path)) path++;
4884 }
4885 else {
4886 Inc(path, end, mb_enc, enc);
4887 }
4888 }
4889 return (char *)path;
4890}
4891#endif /* USE_NTFS */
4892
4893#define BUFCHECK(cond) do {\
4894 bdiff = p - buf;\
4895 if (cond) {\
4896 do {buflen *= 2;} while (cond);\
4897 rb_str_resize(result, buflen);\
4898 buf = RSTRING_PTR(result);\
4899 p = buf + bdiff;\
4900 pend = buf + buflen;\
4901 }\
4902} while (0)
4903
4904#define BUFINIT(result, buf, p, pend) do {\
4905 if (!result) { result = rb_usascii_str_new(0, 1); } \
4906 p = buf = RSTRING_PTR(result); \
4907 buflen = RSTRING_LEN(result); \
4908 pend = p + buflen; \
4909} while (0)
4910
4911#ifdef __APPLE__
4912# define SKIPPATHSEP(p) ((*(p)) ? 1 : 0)
4913#else
4914# define SKIPPATHSEP(p) 1
4915#endif
4916
4917#define BUFCOPY(srcptr, srclen) do { \
4918 const int skip = SKIPPATHSEP(p); \
4919 rb_str_set_len(result, p-buf+skip); \
4920 BUFCHECK(bdiff + ((srclen)+skip) >= buflen); \
4921 p += skip; \
4922 memcpy(p, (srcptr), (srclen)); \
4923 p += (srclen); \
4924} while (0)
4925
4926#define WITH_ROOTDIFF(stmt) do { \
4927 rb_len_t rootdiff = root - buf; \
4928 stmt; \
4929 root = buf + rootdiff; \
4930} while (0)
4931
4932static VALUE
4933copy_home_path(VALUE result, const char *dir)
4934{
4935 char *buf;
4936 rb_len_t dirlen;
4937 int encidx;
4938
4939 dirlen = strlen(dir);
4940 rb_str_resize(result, dirlen);
4941 memcpy(buf = RSTRING_PTR(result), dir, dirlen);
4942 encidx = rb_filesystem_encindex();
4943 rb_enc_associate_index(result, encidx);
4944#if defined FILE_ALT_SEPARATOR
4945 rb_encoding *enc = rb_enc_from_index(encidx);
4946 bool mb_enc = enc_mbclen_needed(enc);
4947 for (char *p = buf, *bend = p + dirlen; p < bend; Inc(p, bend, mb_enc, enc)) {
4948 if (*p == FILE_ALT_SEPARATOR) {
4949 *p = '/';
4950 }
4951 }
4952#endif
4953 return result;
4954}
4955
4956VALUE
4957rb_home_dir_of(VALUE user, VALUE result)
4958{
4959#ifdef HAVE_PWD_H
4960 VALUE dirname = rb_getpwdirnam_for_login(user);
4961 if (dirname == Qnil) {
4962 rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user);
4963 }
4964 const char *dir = RSTRING_PTR(dirname);
4965#else
4966 extern char *getlogin(void);
4967 const char *pwPtr = 0;
4968 const char *login;
4969 # define endpwent() ((void)0)
4970 const char *dir, *username = RSTRING_PTR(user);
4971 rb_encoding *enc = rb_enc_get(user);
4972#if defined _WIN32
4973 rb_encoding *fsenc = rb_utf8_encoding();
4974#else
4975 rb_encoding *fsenc = rb_filesystem_encoding();
4976#endif
4977 if (enc != fsenc) {
4978 dir = username = RSTRING_PTR(rb_str_conv_enc(user, enc, fsenc));
4979 }
4980
4981 if ((login = getlogin()) && strcasecmp(username, login) == 0)
4982 dir = pwPtr = getenv("HOME");
4983 if (!pwPtr) {
4984 rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user);
4985 }
4986#endif
4987 copy_home_path(result, dir);
4988 return result;
4989}
4990
4991#ifndef _WIN32 /* this encompasses rb_file_expand_path_internal */
4992VALUE
4993rb_default_home_dir(VALUE result)
4994{
4995 const char *dir = getenv("HOME");
4996
4997#if defined HAVE_PWD_H
4998 if (!dir) {
4999 /* We'll look up the user's default home dir in the password db by
5000 * login name, if possible, and failing that will fall back to looking
5001 * the information up by uid (as would be needed for processes that
5002 * are not a descendant of login(1) or a work-alike).
5003 *
5004 * While the lookup by uid is more likely to succeed (since we always
5005 * have a uid, but may or may not have a login name), we prefer first
5006 * looking up by name to accommodate the possibility of multiple login
5007 * names (each with its own record in the password database, so each
5008 * with a potentially different home directory) being mapped to the
5009 * same uid (as explicitly allowed for by POSIX; see getlogin(3posix)).
5010 */
5011 VALUE login_name = rb_getlogin();
5012
5013# if !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID)
5014 /* This is a corner case, but for backward compatibility reasons we
5015 * want to emit this error if neither the lookup by login name nor
5016 * lookup by getuid() has a chance of succeeding.
5017 */
5018 if (NIL_P(login_name)) {
5019 rb_raise(rb_eArgError, "couldn't find login name -- expanding '~'");
5020 }
5021# endif /* !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID) */
5022
5023 VALUE pw_dir = rb_getpwdirnam_for_login(login_name);
5024 if (NIL_P(pw_dir)) {
5025 pw_dir = rb_getpwdiruid();
5026 if (NIL_P(pw_dir)) {
5027 rb_raise(rb_eArgError, "couldn't find home for uid '%ld'", (long)getuid());
5028 }
5029 }
5030
5031 /* found it */
5032 copy_home_path(result, RSTRING_PTR(pw_dir));
5033 rb_str_resize(pw_dir, 0);
5034 return result;
5035 }
5036#endif /* defined HAVE_PWD_H */
5037 if (!dir) {
5038 rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'");
5039 }
5040 return copy_home_path(result, dir);
5041}
5042
5043static VALUE
5044ospath_new(const char *ptr, rb_len_t len, rb_encoding *fsenc)
5045{
5046#if NORMALIZE_UTF8PATH
5047 VALUE path = rb_str_normalize_ospath(ptr, len);
5048 rb_enc_associate(path, fsenc);
5049 return path;
5050#else
5051 return rb_enc_str_new(ptr, len, fsenc);
5052#endif
5053}
5054
5055static char *
5056append_fspath(VALUE result, VALUE fname, VALUE dirname, rb_encoding **enc, rb_encoding *fsenc)
5057{
5058 if (RB_UNLIKELY(!rb_enc_asciicompat(fsenc) || rb_enc_str_coderange(dirname) != ENC_CODERANGE_7BIT)) {
5059 dirname = rb_str_new_shared(dirname);
5060 rb_enc_associate(dirname, fsenc);
5061 }
5062
5063 char *buf, *cwdp;
5064 size_t dirlen = RSTRING_LEN(dirname);
5065 size_t buflen = rb_str_capacity(result);
5066
5067 if (NORMALIZE_UTF8PATH || *enc != fsenc) {
5068 if (!rb_enc_compatible(fname, dirname)) {
5069 /* rb_enc_check must raise because the two encodings are not
5070 * compatible. */
5071 rb_enc_check(fname, dirname);
5072 rb_bug("unreachable");
5073 }
5074 rb_encoding *direnc = fs_enc_check(fname, dirname);
5075 if (direnc != fsenc) {
5076 dirname = rb_str_conv_enc(dirname, fsenc, direnc);
5077 }
5078 *enc = direnc;
5079 }
5080
5081 RSTRING_GETMEM(dirname, cwdp, dirlen);
5082 do {buflen *= 2;} while (dirlen > buflen);
5083 rb_str_resize(result, buflen);
5084 buf = RSTRING_PTR(result);
5085 memcpy(buf, cwdp, dirlen);
5086 rb_enc_associate(result, *enc);
5087 return buf + dirlen;
5088}
5089
5090VALUE
5091rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
5092{
5093 const char *s, *b, *fend;
5094 char *buf, *p, *pend, *root;
5095 size_t buflen, bdiff;
5096 rb_encoding *enc, *fsenc = rb_filesystem_encoding();
5097
5098 s = StringValuePtr(fname);
5099 fend = s + RSTRING_LEN(fname);
5100 enc = rb_str_enc_get(fname);
5101 bool mb_enc = enc_mbclen_needed(enc);
5102 if (!mb_enc && RTEST(dname)) {
5103 mb_enc = enc_mbclen_needed(rb_str_enc_get(dname));
5104 }
5105
5106 if (s < fend && s[0] == '~' && abs_mode == 0) { /* execute only if NOT absolute_path() */
5107 BUFINIT(result, buf, p, pend); // TOOD: right size the buffer
5108
5109 rb_len_t userlen = 0;
5110 if (s + 1 == fend || isdirsep(s[1])) {
5111 buf = 0;
5112 b = 0;
5113 rb_str_set_len(result, 0);
5114 if (++s < fend) ++s;
5115 rb_default_home_dir(result);
5116 }
5117 else {
5118 s = nextdirsep(b = s, fend, enc);
5119 b++; /* b[0] is '~' */
5120 userlen = s - b;
5121 BUFCHECK(bdiff + userlen >= buflen);
5122 memcpy(p, b, userlen);
5123 ENC_CODERANGE_CLEAR(result);
5124 rb_str_set_len(result, userlen);
5125 rb_enc_associate(result, enc);
5126 rb_home_dir_of(result, result);
5127 buf = p + 1;
5128 p += userlen;
5129 }
5130 if (!rb_is_absolute_path(RSTRING_PTR(result))) {
5131 if (userlen) {
5132 rb_enc_raise(enc, rb_eArgError, "non-absolute home of %.*s%.0"PRIsVALUE,
5133 (int)userlen, b, fname);
5134 }
5135 else {
5136 rb_raise(rb_eArgError, "non-absolute home");
5137 }
5138 }
5139 BUFINIT(result, buf, p, pend);
5140 p = pend;
5141 }
5142#ifdef DOSISH_DRIVE_LETTER
5143 /* skip drive letter */
5144 else if (s + 1 < fend && has_drive_letter(s)) {
5145 BUFINIT(result, buf, p, pend); // TOOD: right size the buffer
5146
5147 if (s + 2 < fend && isdirsep(s[2])) {
5148 /* specified drive letter, and full path */
5149 /* skip drive letter */
5150 BUFCHECK(bdiff + 2 >= buflen);
5151 memcpy(p, s, 2);
5152 p += 2;
5153 s += 2;
5154 rb_enc_copy(result, fname);
5155 }
5156 else {
5157 /* specified drive, but not full path */
5158 int same = 0;
5159 if (!NIL_P(dname) && !not_same_drive(dname, s[0])) {
5160 rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
5161 BUFINIT(result, buf, p, pend);
5162 if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) {
5163 /* ok, same drive */
5164 same = 1;
5165 }
5166 }
5167 if (!same) {
5168 char *e = append_fspath(result, fname, getcwdofdrv(*s), &enc, fsenc);
5169 BUFINIT(result, buf, p, pend);
5170 p = e;
5171 }
5172 else {
5173 rb_enc_associate(result, enc = fs_enc_check(result, fname));
5174 p = pend;
5175 }
5176 p = chompdirsep(skiproot(buf, p), p, mb_enc, enc);
5177 s += 2;
5178 }
5179 }
5180#endif /* DOSISH_DRIVE_LETTER */
5181 else if (s == fend || !rb_is_absolute_path(s)) {
5182
5183 if (!NIL_P(dname)) {
5184 if (result) {
5185 rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
5186 }
5187 else {
5188 result = rb_usascii_str_new(0, RSTRING_LEN(dname) + RSTRING_LEN(fname) + 1);
5189 rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
5190
5191 if (RB_UNLIKELY(RSTRING_LEN(result) > RSTRING_LEN(dname))) {
5192 VALUE resized_result = rb_usascii_str_new(0, RSTRING_LEN(result) + RSTRING_LEN(fname) + 1);
5193 rb_str_set_len(resized_result, 0);
5194 rb_str_buf_append(resized_result, result);
5195 rb_str_set_len(result, 0);
5196 result = resized_result;
5197 }
5198 }
5199
5200 rb_enc_associate(result, fs_enc_check(result, fname));
5201 BUFINIT(result, buf, p, pend);
5202 p = pend;
5203 }
5204 else {
5205 VALUE cwd = rb_dir_getwd_ospath();
5206 if (!result) {
5207 result = rb_usascii_str_new(0, RSTRING_LEN(cwd) + RSTRING_LEN(fname) + 1);
5208 }
5209 char *e = append_fspath(result, fname, rb_dir_getwd_ospath(), &enc, fsenc);
5210 BUFINIT(result, buf, p, pend);
5211 p = e;
5212 }
5213#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
5214 if (s < fend && isdirsep(*s)) {
5215 /* specified full path, but not drive letter nor UNC */
5216 /* we need to get the drive letter or UNC share name */
5217 p = skipprefix(buf, p, mb_enc, enc);
5218 }
5219 else
5220#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */
5221 p = chompdirsep(skiproot(buf, p), p, mb_enc, enc);
5222 }
5223 else {
5224 BUFINIT(result, buf, p, pend);
5225
5226 size_t len;
5227 b = s;
5228 do s++; while (s < fend && isdirsep(*s));
5229 len = s - b;
5230 p = buf + len;
5231 BUFCHECK(bdiff >= buflen);
5232 memset(buf, '/', len);
5233 rb_str_set_len(result, len);
5234 rb_enc_associate(result, fs_enc_check(result, fname));
5235 }
5236 if (p > buf && p[-1] == '/')
5237 --p;
5238 else {
5239 rb_str_set_len(result, p-buf);
5240 BUFCHECK(bdiff + 1 >= buflen);
5241 *p = '/';
5242 }
5243
5244 rb_str_set_len(result, p-buf+1);
5245 BUFCHECK(bdiff + 1 >= buflen);
5246 p[1] = 0;
5247 root = skipprefix(buf, p+1, mb_enc, enc);
5248
5249 b = s;
5250 while (s < fend) {
5251 switch (*s) {
5252 case '.':
5253 if (b == s++) { /* beginning of path element */
5254 if (s == fend) {
5255 b = s;
5256 break;
5257 }
5258 switch (*s) {
5259 case '.':
5260 if (s+1 == fend || isdirsep(*(s+1))) {
5261 /* We must go back to the parent */
5262 char *n;
5263 *p = '\0';
5264 if (!(n = strrdirsep(root, p, mb_enc, enc))) {
5265 *p = '/';
5266 }
5267 else {
5268 p = n;
5269 }
5270 b = ++s;
5271 }
5272 break;
5273 case '/':
5274#if defined FILE_ALT_SEPARATOR
5275 case FILE_ALT_SEPARATOR:
5276#endif
5277 b = ++s;
5278 break;
5279 default:
5280 /* ordinary path element, beginning don't move */
5281 break;
5282 }
5283 }
5284 break;
5285 case '/':
5286#if defined FILE_ALT_SEPARATOR
5287 case FILE_ALT_SEPARATOR:
5288#endif
5289 if (s > b) {
5290 WITH_ROOTDIFF(BUFCOPY(b, s-b));
5291 *p = '/';
5292 }
5293 b = ++s;
5294 break;
5295 default:
5296#ifdef __APPLE__
5297 {
5298 int n = ignored_char_p(s, fend, enc);
5299 if (n) {
5300 if (s > b) {
5301 WITH_ROOTDIFF(BUFCOPY(b, s-b));
5302 *p = '\0';
5303 }
5304 b = s += n;
5305 break;
5306 }
5307 }
5308#endif /* __APPLE__ */
5309 Inc(s, fend, mb_enc, enc);
5310 break;
5311 }
5312 }
5313
5314 if (s > b) {
5315 BUFCOPY(b, s-b);
5316 rb_str_set_len(result, p-buf);
5317 }
5318 if (p == skiproot(buf, p + !!*p) - 1) p++;
5319
5320 rb_str_set_len(result, p - buf);
5321 rb_enc_check(fname, result);
5322 ENC_CODERANGE_CLEAR(result);
5323 return result;
5324}
5325#endif /* !_WIN32 (this ifdef started above rb_default_home_dir) */
5326
5327static VALUE
5328str_shrink(VALUE str)
5329{
5330 rb_str_resize(str, RSTRING_LEN(str));
5331 return str;
5332}
5333
5334#define expand_path(fname, dname, abs_mode, long_name, result) \
5335 str_shrink(rb_file_expand_path_internal(fname, dname, abs_mode, long_name, result))
5336
5337#define check_expand_path_args(fname, dname) \
5338 (((fname) = rb_get_path(fname)), \
5339 (void)(NIL_P(dname) ? (dname) : ((dname) = rb_get_path(dname))))
5340
5341static VALUE
5342file_expand_path_1(VALUE fname, rb_len_t extra_capa)
5343{
5344 VALUE buffer = rb_usascii_str_new(0, RSTRING_LEN(fname) + extra_capa);
5345 return rb_file_expand_path_internal(fname, Qnil, 0, 0, buffer);
5346}
5347
5348VALUE
5349rb_file_expand_path(VALUE fname, VALUE dname)
5350{
5351 check_expand_path_args(fname, dname);
5352 return expand_path(fname, dname, 0, 1, Qfalse);
5353}
5354
5355VALUE
5356rb_file_expand_path_fast(VALUE fname, VALUE dname)
5357{
5358 return expand_path(fname, dname, 0, 0, Qfalse);
5359}
5360
5361VALUE
5362rb_file_s_expand_path(int argc, const VALUE *argv)
5363{
5364 rb_check_arity(argc, 1, 2);
5365 return rb_file_expand_path(argv[0], argc > 1 ? argv[1] : Qnil);
5366}
5367
5368/*
5369 * :markup: markdown
5370 *
5371 * call-seq:
5372 * File.expand_path(path, dirpath = '.') -> absolute_path
5373 *
5374 * Returns the string absolute path for the given `path`.
5375 *
5376 * Evaluates a relative path with respect to the directory given by `dirpath`:
5377 *
5378 * ```ruby
5379 * Dir.chdir('/snap')
5380 * # Default dirpath.
5381 * File.expand_path('README') # => "/snap/README"
5382 * File.expand_path('bin') # => "/snap/bin"
5383 * File.expand_path('bin/../var') # => "/snap/var" # Cleaned.
5384 * # Other dirpath.
5385 * File.expand_path('../zip', '/usr/bin/ruby') # => "/usr/bin/zip"
5386 * Dir.chdir('/usr/bin')
5387 * File.expand_path('../../snap', __FILE__) # => "/usr/snap"
5388 * ```
5389 *
5390 * Evaluates an absolute path without respect to `dirpath`:
5391 *
5392 * ```ruby
5393 * File.expand_path('/snap') # => "/snap"
5394 * File.expand_path('/snap', 'nosuch') # => "/snap"
5395 * File.expand_path('/snap/../snap') # => "/snap" # Cleaned.
5396 * ```
5397 *
5398 * More examples:
5399 *
5400 * ```
5401 * Dir.chdir('/usr/bin')
5402 * File.expand_path('../../snap', __FILE__) # => "/usr/snap"
5403 * File.expand_path('../../snap') # => "/snap"
5404 * ```
5405 *
5406 */
5407
5408static VALUE
5409s_expand_path(int c, const VALUE * v, VALUE _)
5410{
5411 return rb_file_s_expand_path(c, v);
5412}
5413
5414VALUE
5415rb_file_absolute_path(VALUE fname, VALUE dname)
5416{
5417 check_expand_path_args(fname, dname);
5418 return expand_path(fname, dname, 1, 1, Qfalse);
5419}
5420
5421VALUE
5422rb_file_s_absolute_path(int argc, const VALUE *argv)
5423{
5424 rb_check_arity(argc, 1, 2);
5425 return rb_file_absolute_path(argv[0], argc > 1 ? argv[1] : Qnil);
5426}
5427
5428/*
5429 * :markup: markdown
5430 *
5431 * call-seq:
5432 * File.absolute_path(path, dirpath = '.') -> absolute_path
5433 *
5434 * Returns the string absolute path for the given `path`.
5435 *
5436 * Evaluates a relative path with respect to the directory given by `dirpath`:
5437 *
5438 * ```ruby
5439 * Dir.chdir('/snap')
5440 * # Default dirpath.
5441 * File.absolute_path('README') # => "/snap/README"
5442 * File.absolute_path('bin') # => "/snap/bin"
5443 * File.absolute_path('bin/../var') # => "/snap/var"
5444 * # Other dirpath.
5445 * File.absolute_path('../zip', '/usr/bin/ruby') # => "/usr/bin/zip"
5446 * ```
5447 *
5448 * For an absolute path, argument `dirpath` is ignored:
5449 *
5450 * ```ruby
5451 * File.absolute_path('/snap', '/usr/bin') # => "/snap"
5452 * File.absolute_path('/snap', 'nosuch') # => "/snap"
5453 * ```
5454 *
5455 * A leading tilde character (`'~'`), is not expanded:
5456 *
5457 * ```ruby
5458 * Dir.chdir('/usr/bin')
5459 * File.absolute_path("~") # => "/usr/bin/~"
5460 * File.absolute_path("~/Documents") # => "/usr/bin/~/Documents"
5461 * ```
5462 *
5463 */
5464
5465static VALUE
5466s_absolute_path(int c, const VALUE * v, VALUE _)
5467{
5468 return rb_file_s_absolute_path(c, v);
5469}
5470
5471/*
5472 * :markup: markdown
5473 *
5474 * call-seq:
5475 * File.absolute_path?(path) -> true or false
5476 *
5477 * Returns whether the given `path` is an absolute path:
5478 *
5479 * ```ruby
5480 * File.absolute_path?('/home') # => true
5481 * File.absolute_path?('lib') # => false
5482 * ```
5483 *
5484 * The result is OS-dependent for some paths:
5485 *
5486 * ```ruby
5487 * File.absolute_path?('C:/') # => true # On Windows.
5488 * File.absolute_path?('C:/') # => false # Elsewhere.
5489 * ```
5490 *
5491 */
5492
5493static VALUE
5494s_absolute_path_p(VALUE klass, VALUE fname)
5495{
5496 VALUE path = rb_get_path(fname);
5497
5498 if (!rb_is_absolute_path(RSTRING_PTR(path))) return Qfalse;
5499 return Qtrue;
5500}
5501
5502enum rb_realpath_mode {
5503 RB_REALPATH_CHECK,
5504 RB_REALPATH_DIR,
5505 RB_REALPATH_STRICT,
5506 RB_REALPATH_MODE_MAX
5507};
5508
5509static int
5510realpath_rec(rb_len_t *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE fallback,
5511 VALUE loopcheck, enum rb_realpath_mode mode, int last)
5512{
5513 const char *pend = unresolved + strlen(unresolved);
5514 rb_encoding *enc = rb_enc_get(*resolvedp);
5515 ID resolving;
5516 CONST_ID(resolving, "resolving");
5517 while (unresolved < pend) {
5518 const char *testname = unresolved;
5519 const char *unresolved_firstsep = rb_enc_path_next(unresolved, pend, enc);
5520 rb_len_t testnamelen = unresolved_firstsep - unresolved;
5521 const char *unresolved_nextname = unresolved_firstsep;
5522 while (unresolved_nextname < pend && isdirsep(*unresolved_nextname))
5523 unresolved_nextname++;
5524 unresolved = unresolved_nextname;
5525 if (testnamelen == 1 && testname[0] == '.') {
5526 }
5527 else if (testnamelen == 2 && testname[0] == '.' && testname[1] == '.') {
5528 if (*prefixlenp < RSTRING_LEN(*resolvedp)) {
5529 bool mb_enc = enc_mbclen_needed(enc);
5530 const char *resolved_str = RSTRING_PTR(*resolvedp);
5531 const char *resolved_names = resolved_str + *prefixlenp;
5532 const char *lastsep = strrdirsep(resolved_names, resolved_str + RSTRING_LEN(*resolvedp), mb_enc, enc);
5533 rb_len_t len = lastsep ? lastsep - resolved_names : 0;
5534 rb_str_resize(*resolvedp, *prefixlenp + len);
5535 }
5536 }
5537 else {
5538 VALUE checkval;
5539 VALUE testpath = rb_str_dup(*resolvedp);
5540 if (*prefixlenp < RSTRING_LEN(testpath))
5541 rb_str_cat2(testpath, "/");
5542#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
5543 if (*prefixlenp > 1 && *prefixlenp == RSTRING_LEN(testpath)) {
5544 const char *prefix = RSTRING_PTR(testpath);
5545 const char *last = rb_enc_left_char_head(prefix, prefix + *prefixlenp - 1, prefix + *prefixlenp, enc);
5546 if (!isdirsep(*last)) rb_str_cat2(testpath, "/");
5547 }
5548#endif
5549 rb_str_cat(testpath, testname, testnamelen);
5550 checkval = rb_hash_aref(loopcheck, testpath);
5551 if (!NIL_P(checkval)) {
5552 if (checkval == ID2SYM(resolving)) {
5553 if (mode == RB_REALPATH_CHECK) {
5554 errno = ELOOP;
5555 return -1;
5556 }
5557 rb_syserr_fail_path(ELOOP, testpath);
5558 }
5559 else {
5560 *resolvedp = rb_str_dup(checkval);
5561 }
5562 }
5563 else {
5564 struct stat sbuf;
5565 int ret;
5566 ret = lstat_without_gvl(RSTRING_PTR(testpath), &sbuf);
5567 if (ret == -1) {
5568 int e = errno;
5569 if (e == ENOENT && !NIL_P(fallback)) {
5570 if (stat_without_gvl(RSTRING_PTR(fallback), &sbuf) == 0) {
5571 rb_str_replace(*resolvedp, fallback);
5572 return 0;
5573 }
5574 }
5575 if (mode == RB_REALPATH_CHECK) return -1;
5576 if (e == ENOENT) {
5577 if (mode == RB_REALPATH_STRICT || !last || *unresolved_firstsep)
5578 rb_syserr_fail_path(e, testpath);
5579 *resolvedp = testpath;
5580 break;
5581 }
5582 else {
5583 rb_syserr_fail_path(e, testpath);
5584 }
5585 }
5586#ifdef HAVE_READLINK
5587 if (S_ISLNK(sbuf.st_mode)) {
5588 VALUE link;
5589 VALUE link_orig = Qnil;
5590 const char *link_prefix, *link_names;
5591 rb_len_t link_prefixlen;
5592 rb_hash_aset(loopcheck, testpath, ID2SYM(resolving));
5593 link = rb_readlink(testpath, enc);
5594 link_prefix = RSTRING_PTR(link);
5595 link_names = skipprefixroot(link_prefix, link_prefix + RSTRING_LEN(link), rb_enc_get(link));
5596 link_prefixlen = link_names - link_prefix;
5597 if (link_prefixlen > 0) {
5598 rb_encoding *tmpenc, *linkenc = rb_enc_get(link);
5599 link_orig = link;
5600 link = rb_str_subseq(link, 0, link_prefixlen);
5601 tmpenc = fs_enc_check(*resolvedp, link);
5602 if (tmpenc != linkenc) link = rb_str_conv_enc(link, linkenc, tmpenc);
5603 *resolvedp = link;
5604 *prefixlenp = link_prefixlen;
5605 }
5606 if (realpath_rec(prefixlenp, resolvedp, link_names, testpath,
5607 loopcheck, mode, !*unresolved_firstsep))
5608 return -1;
5609 RB_GC_GUARD(link_orig);
5610 rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp));
5611 }
5612 else
5613#endif /* HAVE_READLINK */
5614 {
5615 VALUE s = rb_str_dup_frozen(testpath);
5616 rb_hash_aset(loopcheck, s, s);
5617 *resolvedp = testpath;
5618 }
5619 }
5620 }
5621 }
5622 return 0;
5623}
5624
5625#ifdef DOSISH_DRIVE_LETTER
5626/* expand_path on Windows builds the result in the code page of the
5627 * argument encoding, which may not represent the current directory */
5628static VALUE
5629ospath_for_expand(VALUE path)
5630{
5631 switch (ENCODING_GET(path)) {
5632 case ENCINDEX_ASCII_8BIT:
5633 case ENCINDEX_US_ASCII:
5634 return rb_enc_associate_index(rb_str_dup(path), rb_filesystem_encindex());
5635 }
5636 return TO_OSPATH(path);
5637}
5638#endif
5639
5640static VALUE
5641rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode)
5642{
5643 rb_len_t prefixlen;
5644 VALUE resolved;
5645 VALUE unresolved_path;
5646 VALUE loopcheck;
5647 VALUE curdir = Qnil;
5648
5649 rb_encoding *enc;
5650 char *path_names = NULL, *basedir_names = NULL, *curdir_names = NULL;
5651 char *ptr, *prefixptr = NULL, *pend;
5652 rb_len_t len;
5653
5654#ifdef DOSISH_DRIVE_LETTER
5655 VALUE rootdir = path;
5656 RSTRING_GETMEM(path, ptr, len);
5657 if (!NIL_P(basedir) && skipprefixroot(ptr, ptr + len, rb_enc_get(path)) == ptr) {
5658 FilePathValue(basedir);
5659 rootdir = basedir;
5660 }
5661 RSTRING_GETMEM(rootdir, ptr, len);
5662 if (len >= 2 && has_drive_letter(ptr) && (len == 2 || !isdirsep(ptr[2]))) {
5663 /* Expand a drive-relative path or basedir against the current
5664 * directory of the drive, as File.absolute_path does */
5665 if (!NIL_P(basedir)) basedir = ospath_for_expand(rb_get_path(basedir));
5666 path = rb_file_absolute_path(ospath_for_expand(path), basedir);
5667 basedir = Qnil;
5668 }
5669#endif
5670
5671 unresolved_path = rb_str_dup_frozen(path);
5672
5673 if (!NIL_P(basedir)) {
5674 FilePathValue(basedir);
5675 basedir = TO_OSPATH(rb_str_dup_frozen(basedir));
5676 }
5677
5678 enc = rb_enc_get(unresolved_path);
5679 unresolved_path = TO_OSPATH(unresolved_path);
5680 RSTRING_GETMEM(unresolved_path, ptr, len);
5681 path_names = skipprefixroot(ptr, ptr + len, rb_enc_get(unresolved_path));
5682 if (ptr != path_names) {
5683 resolved = rb_str_subseq(unresolved_path, 0, path_names - ptr);
5684 goto root_found;
5685 }
5686
5687 if (!NIL_P(basedir)) {
5688 RSTRING_GETMEM(basedir, ptr, len);
5689 basedir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(basedir));
5690 if (ptr != basedir_names) {
5691 resolved = rb_str_subseq(basedir, 0, basedir_names - ptr);
5692 goto root_found;
5693 }
5694 }
5695
5696 curdir = rb_dir_getwd_ospath();
5697 RSTRING_GETMEM(curdir, ptr, len);
5698 curdir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(curdir));
5699 resolved = rb_str_subseq(curdir, 0, curdir_names - ptr);
5700
5701 root_found:
5702 RSTRING_GETMEM(resolved, prefixptr, prefixlen);
5703 pend = prefixptr + prefixlen;
5704 bool mb_enc = enc_mbclen_needed(enc);
5705 ptr = chompdirsep(prefixptr, pend, mb_enc, enc);
5706 if (ptr < pend) {
5707 prefixlen = ++ptr - prefixptr;
5708 rb_str_set_len(resolved, prefixlen);
5709 }
5710#ifdef FILE_ALT_SEPARATOR
5711 while (prefixptr < ptr) {
5712 if (*prefixptr == FILE_ALT_SEPARATOR) {
5713 *prefixptr = '/';
5714 }
5715 Inc(prefixptr, pend, mb_enc, enc);
5716 }
5717#endif
5718
5719 switch (rb_enc_to_index(enc)) {
5720 case ENCINDEX_ASCII_8BIT:
5721 case ENCINDEX_US_ASCII:
5722 rb_enc_associate_index(resolved, rb_filesystem_encindex());
5723 }
5724
5725 loopcheck = rb_hash_new();
5726 if (curdir_names) {
5727 if (realpath_rec(&prefixlen, &resolved, curdir_names, Qnil, loopcheck, mode, 0))
5728 return Qnil;
5729 }
5730 if (basedir_names) {
5731 if (realpath_rec(&prefixlen, &resolved, basedir_names, Qnil, loopcheck, mode, 0))
5732 return Qnil;
5733 }
5734 if (realpath_rec(&prefixlen, &resolved, path_names, Qnil, loopcheck, mode, 1))
5735 return Qnil;
5736
5737 if (origenc && origenc != rb_enc_get(resolved)) {
5738 if (rb_enc_str_asciionly_p(resolved)) {
5739 rb_enc_associate(resolved, origenc);
5740 }
5741 else {
5742 resolved = rb_str_conv_enc(resolved, NULL, origenc);
5743 }
5744 }
5745
5746 RB_GC_GUARD(unresolved_path);
5747 RB_GC_GUARD(curdir);
5748 return resolved;
5749}
5750
5751static VALUE rb_file_join(rb_len_t argc, VALUE *args);
5752
5753#ifndef HAVE_REALPATH
5754static VALUE
5755rb_check_realpath_emulate_try(VALUE arg)
5756{
5757 VALUE *args = (VALUE *)arg;
5758 return rb_check_realpath_emulate(args[0], args[1], (rb_encoding *)args[2], RB_REALPATH_CHECK);
5759}
5760
5761static VALUE
5762rb_check_realpath_emulate_rescue(VALUE arg, VALUE exc)
5763{
5764 return Qnil;
5765}
5766#endif /* HAVE_REALPATH */
5767
5768static VALUE
5769rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode)
5770{
5771#ifdef HAVE_REALPATH
5772 VALUE unresolved_path;
5773 char *resolved_ptr = NULL;
5774 VALUE resolved;
5775
5776 if (mode == RB_REALPATH_DIR) {
5777 return rb_check_realpath_emulate(basedir, path, origenc, mode);
5778 }
5779
5780 unresolved_path = rb_str_dup_frozen(path);
5781 if (*RSTRING_PTR(unresolved_path) != '/' && !NIL_P(basedir)) {
5782 VALUE paths[2] = {basedir, unresolved_path};
5783 unresolved_path = rb_file_join(2, paths);
5784 }
5785 if (origenc) unresolved_path = TO_OSPATH(unresolved_path);
5786
5787 if ((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) {
5788 /*
5789 wasi-libc 22 and later support realpath(3) but return ENOTSUP
5790 when the underlying host syscall returns it.
5791 glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb,
5792 returning ENOTDIR in that case.
5793 glibc realpath(3) can also return ENOENT for paths that exist,
5794 such as /dev/fd/5.
5795 Fallback to the emulated approach in either of those cases. */
5796 if (errno == ENOTSUP ||
5797 errno == ENOTDIR ||
5798 (errno == ENOENT && rb_file_exist_p(0, unresolved_path))) {
5799 return rb_check_realpath_emulate(basedir, path, origenc, mode);
5800
5801 }
5802 if (mode == RB_REALPATH_CHECK) {
5803 return Qnil;
5804 }
5805 rb_sys_fail_path(unresolved_path);
5806 }
5807 resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding());
5808 free(resolved_ptr);
5809
5810# if !defined(__linux__) && !defined(__APPLE__)
5811 /* As `resolved` is a String in the filesystem encoding, no
5812 * conversion is needed */
5813 struct stat st;
5814 if (stat_without_gvl(RSTRING_PTR(resolved), &st) < 0) {
5815 if (mode == RB_REALPATH_CHECK) {
5816 return Qnil;
5817 }
5818 rb_sys_fail_path(unresolved_path);
5819 }
5820# endif /* !defined(__linux__) && !defined(__APPLE__) */
5821
5822 if (origenc && origenc != rb_enc_get(resolved)) {
5823 if (!rb_enc_str_asciionly_p(resolved)) {
5824 resolved = rb_str_conv_enc(resolved, NULL, origenc);
5825 }
5826 rb_enc_associate(resolved, origenc);
5827 }
5828
5829 if (is_broken_string(resolved)) {
5830 rb_enc_associate(resolved, rb_filesystem_encoding());
5831 if (is_broken_string(resolved)) {
5832 rb_enc_associate(resolved, rb_ascii8bit_encoding());
5833 }
5834 }
5835
5836 RB_GC_GUARD(unresolved_path);
5837 return resolved;
5838#else /* !HAVE_REALPATH */
5839 if (mode == RB_REALPATH_CHECK) {
5840 VALUE arg[3];
5841 arg[0] = basedir;
5842 arg[1] = path;
5843 arg[2] = (VALUE)origenc;
5844
5845 return rb_rescue(rb_check_realpath_emulate_try, (VALUE)arg,
5846 rb_check_realpath_emulate_rescue, Qnil);
5847 }
5848 else {
5849 return rb_check_realpath_emulate(basedir, path, origenc, mode);
5850 }
5851#endif /* HAVE_REALPATH */
5852}
5853
5854VALUE
5855rb_realpath_internal(VALUE basedir, VALUE path, int strict)
5856{
5857 const enum rb_realpath_mode mode =
5858 strict ? RB_REALPATH_STRICT : RB_REALPATH_DIR;
5859 return rb_check_realpath_internal(basedir, path, rb_enc_get(path), mode);
5860}
5861
5862VALUE
5863rb_check_realpath(VALUE basedir, VALUE path, rb_encoding *enc)
5864{
5865 return rb_check_realpath_internal(basedir, path, enc, RB_REALPATH_CHECK);
5866}
5867
5868/*
5869 * call-seq:
5870 * File.realpath(pathname [, dir_string]) -> real_pathname
5871 *
5872 * Returns the real (absolute) pathname of _pathname_ in the actual
5873 * filesystem not containing symlinks or useless dots.
5874 *
5875 * If _dir_string_ is given, it is used as a base directory
5876 * for interpreting relative pathname instead of the current directory.
5877 *
5878 * All components of the pathname must exist when this method is
5879 * called.
5880 */
5881static VALUE
5882rb_file_s_realpath(int argc, VALUE *argv, VALUE klass)
5883{
5884 VALUE basedir = (rb_check_arity(argc, 1, 2) > 1) ? argv[1] : Qnil;
5885 VALUE path = argv[0];
5886 FilePathValue(path);
5887 return rb_realpath_internal(basedir, path, 1);
5888}
5889
5890/*
5891 * call-seq:
5892 * File.realdirpath(pathname [, dir_string]) -> real_pathname
5893 *
5894 * Returns the real (absolute) pathname of _pathname_ in the actual filesystem.
5895 * The real pathname doesn't contain symlinks or useless dots.
5896 *
5897 * If _dir_string_ is given, it is used as a base directory
5898 * for interpreting relative pathname instead of the current directory.
5899 *
5900 * The last component of the real pathname can be nonexistent.
5901 */
5902static VALUE
5903rb_file_s_realdirpath(int argc, VALUE *argv, VALUE klass)
5904{
5905 VALUE basedir = (rb_check_arity(argc, 1, 2) > 1) ? argv[1] : Qnil;
5906 VALUE path = argv[0];
5907 FilePathValue(path);
5908 return rb_realpath_internal(basedir, path, 0);
5909}
5910
5911static size_t
5912rmext(const char *p, rb_len_t l0, rb_len_t l1, const char *e, rb_len_t l2, rb_encoding *enc)
5913{
5914 int len1, len2;
5915 unsigned int c;
5916 const char *s, *last;
5917
5918 if (!e || !l2) return 0;
5919
5920 c = rb_enc_codepoint_len(e, e + l2, &len1, enc);
5921 if (rb_enc_ascget(e + len1, e + l2, &len2, enc) == '*' && len1 + len2 == l2) {
5922 if (c == '.') return l0;
5923 s = p;
5924 e = p + l1;
5925 last = e;
5926 while (s < e) {
5927 if (rb_enc_codepoint_len(s, e, &len1, enc) == c) last = s;
5928 s += len1;
5929 }
5930 return last - p;
5931 }
5932 if (l1 < l2) return l1;
5933
5934 s = p+l1-l2;
5935 if (!at_char_boundary(p, s, p+l1, enc)) return 0;
5936#if CASEFOLD_FILESYSTEM
5937#define fncomp strncasecmp
5938#else
5939#define fncomp strncmp
5940#endif
5941 if (fncomp(s, e, l2) == 0) {
5942 return l1-l2;
5943 }
5944 return 0;
5945}
5946
5947static inline const char *
5948enc_find_basename(const char *name, rb_len_t *baselen, rb_len_t *alllen, bool mb_enc, rb_encoding *enc)
5949{
5950 const char *p, *q, *e, *end;
5951 rb_len_t f = 0, n = -1;
5952
5953 rb_len_t len = (alllen ? (size_t)*alllen : strlen(name));
5954
5955 if (len <= 0) {
5956 return name;
5957 }
5958
5959 end = name + len;
5960 name = skipprefix(name, end, mb_enc, enc);
5961#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
5962 const char *root = name;
5963#endif
5964
5965 while (name < end && isdirsep(*name)) {
5966 name++;
5967 }
5968
5969 if (name == end) {
5970 p = name - 1;
5971 f = 1;
5972#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
5973 if (name != root) {
5974 /* has slashes */
5975 }
5976#ifdef DOSISH_DRIVE_LETTER
5977 else if (*p == ':') {
5978 p++;
5979 f = 0;
5980 }
5981#endif /* DOSISH_DRIVE_LETTER */
5982#ifdef DOSISH_UNC
5983 else {
5984 p = "/";
5985 }
5986#endif /* DOSISH_UNC */
5987#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */
5988 }
5989 else {
5990 p = strrdirsep(name, end, mb_enc, enc);
5991 if (!p) {
5992 p = name;
5993 }
5994 else {
5995 while (isdirsep(*p)) {
5996 p++; /* skip last / */
5997 }
5998 }
5999#if USE_NTFS
6000 n = ntfs_tail(p, end, mb_enc, enc) - p;
6001#else
6002 n = chompdirsep(p, end, mb_enc, enc) - p;
6003#endif
6004 for (q = p; q - p < n && *q == '.'; q++);
6005 for (e = 0; q - p < n; Inc(q, end, mb_enc, enc)) {
6006 if (*q == '.') e = q;
6007 }
6008 if (e) {
6009 f = e - p;
6010 }
6011 else {
6012 f = n;
6013 }
6014 }
6015
6016 if (baselen) {
6017 *baselen = f;
6018 }
6019 if (alllen) {
6020 *alllen = n;
6021 }
6022 return p;
6023}
6024
6025const char *
6026ruby_enc_find_basename(const char *name, rb_len_t *baselen, rb_len_t *alllen, rb_encoding *enc)
6027{
6028 return enc_find_basename(name, baselen, alllen, enc_mbclen_needed(enc), enc);
6029}
6030
6031/*
6032 * call-seq:
6033 * File.basename(path, suffix = '') -> string
6034 *
6035 * Returns a new string containing all or part of the last component of the given +path+.
6036 * Components are delimited by the value of constant File::SEPARATOR
6037 * and, if non-+nil+, the value of constant File::ALT_SEPARATOR.
6038 *
6039 * When +suffix+ is the empty string <tt>''</tt>,
6040 * returns all of the last entry:
6041 *
6042 * File.basename('foo/bar/baz/bat.txt') # => "bat.txt"
6043 * File.basename('foo/bar/baz') # => "baz"
6044 *
6045 * File::SEPARATOR # => "/"
6046 * File.basename('foo/bar.txt////') # => "bar.txt"
6047 * File::ALT_SEPARATOR # => "\\" # On Windows.
6048 * File.basename('foo/bar.txt//\\\\//') # => "bar.txt"
6049 *
6050 * When +suffix+ is <tt>'.*'</tt>,
6051 * the last {filename extension}[https://en.wikipedia.org/wiki/Filename_extension],
6052 * if any, is removed:
6053 *
6054 * File.basename('foo/bar.txt', '.*') # => "bar"
6055 * File.basename('foo/bar.txt.old', '.*') # => "bar.txt"
6056 * File.basename('foo/bar', '.*') # => "bar"
6057 *
6058 * When +suffix+ is any string other than <tt>''</tt> or <tt>'.*'</tt>,
6059 * the matching trailing substring, if any, is removed:
6060 *
6061 * File.basename('foo/bar.txt', '.txt') # => "bar"
6062 * File.basename('foo/bar.txt', 'txt') # => "bar."
6063 * File.basename('foo/bar.txt', '*') # => "bar.txt"
6064 * File.basename('foo/bar.txt', '.') # => "bar.txt"
6065 *
6066 */
6067
6068static VALUE
6069rb_file_s_basename(int argc, VALUE *argv, VALUE _)
6070{
6071 VALUE fname, fext = Qnil;
6072 const char *name, *p, *fp = 0;
6073 rb_len_t f = 0, n;
6074 rb_encoding *enc;
6075
6076 argc = rb_check_arity(argc, 1, 2);
6077 fname = argv[0];
6078 CheckPath(fname, name);
6079 if (argc == 2) {
6080 fext = argv[1];
6081 fp = StringValueCStr(fext);
6082 check_path_encoding(fext);
6083 }
6084 if (NIL_P(fext) || !(enc = rb_enc_compatible(fname, fext))) {
6085 enc = rb_str_enc_get(fname);
6086 }
6087
6088 n = RSTRING_LEN(fname);
6089 if (n <= 0 || !*name) {
6090 return rb_enc_str_new(0, 0, enc);
6091 }
6092
6093 bool mb_enc = enc_mbclen_needed(enc);
6094 p = enc_find_basename(name, &f, &n, mb_enc, enc);
6095 if (n >= 0) {
6096 if (!fp) {
6097 f = n;
6098 }
6099 else {
6100 if (!(f = rmext(p, f, n, fp, RSTRING_LEN(fext), enc))) {
6101 f = n;
6102 }
6103 RB_GC_GUARD(fext);
6104 }
6105 if (f == RSTRING_LEN(fname)) {
6106 return rb_str_new_shared(fname);
6107 }
6108 }
6109
6110 return rb_enc_str_new(p, f, enc);
6111}
6112
6113static VALUE rb_file_dirname_n(VALUE fname, int n);
6114
6115/*
6116 * call-seq:
6117 * File.dirname(path, count = 1) -> string
6118 *
6119 * Returns a string path containing all but the last +count+ components
6120 * of the given +path+:
6121 *
6122 * File.dirname('/usr/lib/linux') # => "/usr/lib"
6123 * File.dirname('/usr') # => "/"
6124 * File.dirname('/') # => "/"
6125 * File.dirname('lib/') # => "."
6126 * File.dirname('nosuch') # => "."
6127 * File.dirname('/usr/lib/linux', 2) # => "/usr"
6128 * File.dirname('/usr/lib/linux', 20) # => "/"
6129 * File.dirname('/usr/lib/linux', 0) # => "/usr/lib/linux"
6130 *
6131 * Components are delimited by File::SEPARATOR and, if non-+nil+, File::ALT_SEPARATOR.
6132 *
6133 */
6134
6135static VALUE
6136rb_file_s_dirname(int argc, VALUE *argv, VALUE klass)
6137{
6138 int n = 1;
6139 if ((argc = rb_check_arity(argc, 1, 2)) > 1) {
6140 n = NUM2INT(argv[1]);
6141 }
6142 return rb_file_dirname_n(argv[0], n);
6143}
6144
6145VALUE
6146rb_file_dirname(VALUE fname)
6147{
6148 return rb_file_dirname_n(fname, 1);
6149}
6150
6151static VALUE
6152rb_file_dirname_n(VALUE fname, int n)
6153{
6154 const char *name, *root, *p, *end;
6155 VALUE dirname;
6156
6157 if (n < 0) rb_raise(rb_eArgError, "negative level: %d", n);
6158 CheckPath(fname, name);
6159 end = name + RSTRING_LEN(fname);
6160
6161 bool mb_enc = !rb_str_enc_fastpath(fname);
6162 rb_encoding *enc = rb_str_enc_get(fname);
6163
6164 root = skiproot(name, end);
6165#ifdef DOSISH_UNC
6166 if (root > name + 1 && isdirsep(*name))
6167 root = skipprefix(name = root - 2, end, mb_enc, enc);
6168#else
6169 if (root > name + 1)
6170 name = root - 1;
6171#endif
6172 if (n > (end - root + 1) / 2) {
6173 p = root;
6174 }
6175 else {
6176 p = end;
6177 while (n) {
6178 if (!(p = strrdirsep(root, p, mb_enc, enc))) {
6179 p = root;
6180 break;
6181 }
6182 n--;
6183 }
6184 }
6185
6186 if (p == name) {
6187 return rb_enc_str_new(".", 1, enc);
6188 }
6189#ifdef DOSISH_DRIVE_LETTER
6190 if (name + 3 < end && has_drive_letter(name) && isdirsep(*(name + 2))) {
6191 const char *top = skiproot(name + 2, end);
6192 dirname = rb_enc_str_new(name, 3, enc);
6193 rb_str_cat(dirname, top, p - top);
6194 }
6195 else
6196#endif
6197 dirname = rb_enc_str_new(name, p - name, enc);
6198#ifdef DOSISH_DRIVE_LETTER
6199 if (root == name + 2 && p == root && name[1] == ':')
6200 rb_str_cat(dirname, ".", 1);
6201#endif
6202 return dirname;
6203}
6204
6205static inline const char *
6206enc_find_extname(const char *name, rb_len_t *len, bool mb_enc, rb_encoding *enc)
6207{
6208 const char *p, *e, *end = name + (len ? *len : (rb_len_t)strlen(name));
6209
6210 p = strrdirsep(name, end, mb_enc, enc); /* get the last path component */
6211 if (!p)
6212 p = name;
6213 else
6214 do name = ++p; while (isdirsep(*p));
6215
6216 e = 0;
6217 while (*p && *p == '.') p++;
6218 while (*p) {
6219 if (*p == '.' || istrailinggarbage(*p)) {
6220#if USE_NTFS
6221 const char *last = p++, *dot = last;
6222 while (istrailinggarbage(*p)) {
6223 if (*p == '.') dot = p;
6224 p++;
6225 }
6226 if (!*p || isADS(*p)) {
6227 p = last;
6228 break;
6229 }
6230 if (*last == '.' || dot > last) e = dot;
6231 continue;
6232#else
6233 e = p; /* get the last dot of the last component */
6234#endif /* USE_NTFS */
6235 }
6236#if USE_NTFS
6237 else if (isADS(*p)) {
6238 break;
6239 }
6240#endif
6241 else if (isdirsep(*p))
6242 break;
6243 Inc(p, end, mb_enc, enc);
6244 }
6245
6246 if (len) {
6247 /* no dot, or the only dot is first or end? */
6248 if (!e || e == name)
6249 *len = 0;
6250 else if (e+1 == p)
6251 *len = 1;
6252 else
6253 *len = p - e;
6254 }
6255 return e;
6256}
6257
6258/*
6259 * accept a String, and return the pointer of the extension.
6260 * if len is passed, set the length of extension to it.
6261 * returned pointer is in ``name'' or NULL.
6262 * returns *len
6263 * no dot NULL 0
6264 * dotfile top 0
6265 * end with dot dot 1
6266 * .ext dot len of .ext
6267 * .ext:stream dot len of .ext without :stream (NTFS only)
6268 *
6269 */
6270const char *
6271ruby_enc_find_extname(const char *name, rb_len_t *len, rb_encoding *enc)
6272{
6273 return enc_find_extname(name, len, enc_mbclen_needed(enc), enc);
6274}
6275
6276/*
6277 * :markup: markdown
6278 *
6279 * call-seq:
6280 * File.extname(path) -> extension
6281 *
6282 * Returns the filename extension --
6283 * usually the portion of the string `path`
6284 * beginning from the last period:
6285 *
6286 * ```ruby
6287 * File.extname('t.rb') # => ".rb"
6288 * File.extname('foo.bar.t.rb') # => ".rb"
6289 * File.extname('foo/bar/t.rb') # => ".rb"
6290 * File.extname('nosuch.txt') # => ".txt" # Path need not exist.
6291 * ```
6292 *
6293 * Returns the entire string when there is no period:
6294 *
6295 * ```ruby
6296 * Pathname('foo').extname # => ""
6297 * ```
6298 *
6299 * Returns an empty string when the only period is the first character:
6300 *
6301 * ```ruby
6302 * File.extname('.irbrc') # => ""
6303 * ```
6304 *
6305 * Returns an empty string or `'.'` when `path` ends with a period:
6306 *
6307 * ```
6308 * File.extname('foo.') # => "" # On Windows.
6309 * File.extname('foo.') # => "." # Elsewhere.
6310 * File.extname('foo....') # => "" # On Windows.
6311 * File.extname('foo....') # => "." # Elsewhere.
6312 * ```
6313 *
6314 */
6315
6316static VALUE
6317rb_file_s_extname(VALUE klass, VALUE fname)
6318{
6319 const char *name;
6320 CheckPath(fname, name);
6321 rb_len_t len = RSTRING_LEN(fname);
6322
6323 if (len < 1) {
6324 return rb_enc_str_new(0, 0, rb_str_enc_get(fname));
6325 }
6326
6327 bool mb_enc = !rb_str_enc_fastpath(fname);
6328 rb_encoding *enc = rb_str_enc_get(fname);
6329
6330 const char *ext = enc_find_extname(name, &len, mb_enc, enc);
6331 return rb_enc_str_new(ext, len, enc);
6332}
6333
6334/*
6335 * :markup: markdown
6336 *
6337 * call-seq:
6338 * File.path(path) -> string
6339 *
6340 * Returns a string representation of the given `path`:
6341 *
6342 * ```ruby
6343 * File.path(File::NULL) # => "/dev/null"
6344 * File.path('/tmp') # => "/tmp"
6345 * File.path('../ruby') # => "../ruby"
6346 * File.path($stdin) # => "<STDIN>"
6347 * File.path('nosuch') # => "nosuch"
6348 * ```
6349 *
6350 */
6351
6352static VALUE
6353rb_file_s_path(VALUE klass, VALUE fname)
6354{
6355 return rb_get_path(fname);
6356}
6357
6358/*
6359 * :markup: markdown
6360 *
6361 * call-seq:
6362 * File.split(string) -> array_of_strings
6363 *
6364 * Returns a 2-element array of strings containing the ::dirname and ::basename
6365 * of the the given `string`:
6366 *
6367 * ```ruby
6368 * File.split('doc/maintainers.md') # => ["doc", "maintainers.md"]
6369 * File.split('doc/') # => [".", "doc"]
6370 * File.split('README.md') # => [".", "README.md"]
6371 * File.split('/tmp/nosuch') # => ["/tmp", "nosuch"]
6372 * File.split('@@##$$/%%^^&&') # => ["@@#\#$$", "%%^^&&"]
6373 * ```
6374 *
6375 */
6376
6377static VALUE
6378rb_file_s_split(VALUE klass, VALUE path)
6379{
6380 FilePathStringValue(path); /* get rid of converting twice */
6381 return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path,Qundef));
6382}
6383
6384static VALUE rb_file_join_ary(VALUE ary);
6385
6386static VALUE
6387file_inspect_join(VALUE ary, VALUE arg, int recur)
6388{
6389 if (recur || ary == arg) rb_raise(rb_eArgError, "recursive array");
6390 return rb_file_join_ary(arg);
6391}
6392
6393static VALUE
6394rb_file_join_ary(VALUE ary)
6395{
6396 rb_len_t len, i;
6397 VALUE result, tmp;
6398 const char *name, *tail;
6399 int checked = TRUE;
6400 rb_encoding *enc;
6401
6402 if (RARRAY_LEN(ary) == 0) return rb_str_new(0, 0);
6403
6404 len = 1;
6405 for (i=0; i<RARRAY_LEN(ary); i++) {
6406 tmp = RARRAY_AREF(ary, i);
6407 if (RB_TYPE_P(tmp, T_STRING)) {
6408 check_path_encoding(tmp);
6409 len += RSTRING_LEN(tmp);
6410 }
6411 else {
6412 len += 10;
6413 }
6414 }
6415 len += RARRAY_LEN(ary) - 1;
6416 result = rb_str_buf_new(len);
6417 RBASIC_CLEAR_CLASS(result);
6418 for (i=0; i<RARRAY_LEN(ary); i++) {
6419 tmp = RARRAY_AREF(ary, i);
6420 switch (OBJ_BUILTIN_TYPE(tmp)) {
6421 case T_STRING:
6422 if (!checked) check_path_encoding(tmp);
6423 StringValueCStr(tmp);
6424 break;
6425 case T_ARRAY:
6426 if (ary == tmp) {
6427 rb_raise(rb_eArgError, "recursive array");
6428 }
6429 else {
6430 tmp = rb_exec_recursive(file_inspect_join, ary, tmp);
6431 }
6432 break;
6433 default:
6435 checked = FALSE;
6436 }
6437 RSTRING_GETMEM(result, name, len);
6438 if (i == 0) {
6439 rb_enc_copy(result, tmp);
6440 }
6441 else {
6442 tail = chompdirsep(name, name + len, true, rb_enc_get(result));
6443 if (RSTRING_LEN(tmp) > 0 && isdirsep(RSTRING_PTR(tmp)[0])) {
6444 rb_str_set_len(result, tail - name);
6445 }
6446 else if (tail == name + len) {
6447 rb_str_cat(result, "/", 1);
6448 }
6449 }
6450 enc = fs_enc_check(result, tmp);
6451 rb_str_buf_append(result, tmp);
6452 rb_enc_associate(result, enc);
6453 }
6454 RBASIC_SET_CLASS_RAW(result, rb_cString);
6455
6456 return result;
6457}
6458
6459static inline VALUE
6460rb_file_join_fastpath(rb_len_t argc, VALUE *args)
6461{
6462 rb_len_t size = argc;
6463
6464 rb_len_t i;
6465 for (i = 0; i < argc; i++) {
6466 VALUE tmp = args[i];
6467 if (RB_LIKELY(RB_TYPE_P(tmp, T_STRING) && rb_str_enc_fastpath(tmp))) {
6468 size += RSTRING_LEN(tmp);
6469 }
6470 else {
6471 return 0;
6472 }
6473 }
6474
6475 VALUE result = rb_str_buf_new(size);
6476
6477 int encidx = ENCODING_GET_INLINED(args[0]);
6478 ENCODING_SET_INLINED(result, encidx);
6479 rb_str_buf_append(result, args[0]);
6480
6481 const char *name = RSTRING_PTR(result);
6482 for (i = 1; i < argc; i++) {
6483 VALUE tmp = args[i];
6484 rb_len_t len = RSTRING_LEN(result);
6485
6486 const char *tmp_s;
6487 rb_len_t tmp_len;
6488 RSTRING_GETMEM(tmp, tmp_s, tmp_len);
6489
6490 if (tmp_len > 0 && isdirsep(tmp_s[0])) {
6491 // right side has a leading separator, remove left side separators.
6492 rb_len_t chomp = len;
6493 while (chomp > 0 && isdirsep(name[chomp - 1])) {
6494 --chomp;
6495 }
6496 rb_str_set_len(result, chomp);
6497 }
6498 else if (len < 1 || !isdirsep(name[len - 1])) {
6499 // neither side have a separator, append one;
6500 rb_str_cat(result, "/", 1);
6501 }
6502
6503 if (RB_UNLIKELY(ENCODING_GET_INLINED(tmp) != encidx)) {
6504 rb_encoding *new_enc = fs_enc_check(result, tmp);
6505 rb_enc_associate(result, new_enc);
6506 encidx = rb_enc_to_index(new_enc);
6507 }
6508
6509 rb_str_buf_cat(result, tmp_s, tmp_len);
6510 }
6511
6512 rb_str_null_check(result);
6513 return result;
6514}
6515
6516static inline VALUE
6517rb_file_join(rb_len_t argc, VALUE *args)
6518{
6519 if (RB_UNLIKELY(argc == 0)) {
6520 return rb_str_new(0, 0);
6521 }
6522
6523 VALUE result = rb_file_join_fastpath(argc, args);
6524 if (RB_LIKELY(result)) {
6525 return result;
6526 }
6527
6528 return rb_file_join_ary(rb_ary_new_from_values(argc, args));
6529}
6530/*
6531 * call-seq:
6532 * File.join(*components) -> string
6533 *
6534 * Returns a new string formed by joining the given string +components+
6535 * with character <tt>'/'</tt>:
6536 *
6537 * File.join # => ""
6538 * File.join('foo') # => "foo"
6539 * File.join(*%w[bar baz bat]) # => "bar/baz/bat"
6540 *
6541 */
6542
6543static VALUE
6544rb_file_s_join(int argc, VALUE *argv, VALUE klass)
6545{
6546 return rb_file_join(argc, argv);
6547}
6548
6549#if defined(HAVE_TRUNCATE)
6550struct truncate_arg {
6551 const char *path;
6552 rb_off_t pos;
6553};
6554
6555static void *
6556nogvl_truncate(void *ptr)
6557{
6558 struct truncate_arg *ta = ptr;
6559 return (void *)(VALUE)truncate(ta->path, ta->pos);
6560}
6561
6562/*
6563 * :markup: markdown
6564 *
6565 * call-seq:
6566 * File.truncate(filepath, size) -> 0
6567 *
6568 * Adjusts the size of file `filepath` to the given `size`:
6569 *
6570 * ```ruby
6571 * filepath = '/tmp/t.tmp'
6572 * File.write(filepath, '0123456789')
6573 * File.read(filepath) # => "0123456789"
6574 * File.truncate(filepath, 5)
6575 * File.read(filepath) # => "01234"
6576 * ```
6577 *
6578 * Pads with null characters if necessary:
6579 *
6580 * ```ruby
6581 * File.truncate(filepath, 10)
6582 * File.read(filepath) # => "01234\u0000\u0000\u0000\u0000\u0000"
6583 * File.delete(filepath) # Clean up.
6584 * ```
6585 *
6586 */
6587
6588static VALUE
6589rb_file_s_truncate(VALUE klass, VALUE path, VALUE len)
6590{
6591 struct truncate_arg ta;
6592 int r;
6593
6594 ta.pos = NUM2OFFT(len);
6595 FilePathValue(path);
6596 path = rb_str_encode_ospath(path);
6597 ta.path = StringValueCStr(path);
6598
6599 r = IO_WITHOUT_GVL_INT(nogvl_truncate, &ta);
6600 if (r < 0)
6601 rb_sys_fail_path(path);
6602 return INT2FIX(0);
6603}
6604#else
6605#define rb_file_s_truncate rb_f_notimplement
6606#endif
6607
6608#if defined(HAVE_FTRUNCATE)
6609struct ftruncate_arg {
6610 int fd;
6611 rb_off_t pos;
6612};
6613
6614static VALUE
6615nogvl_ftruncate(void *ptr)
6616{
6617 struct ftruncate_arg *fa = ptr;
6618
6619 return (VALUE)ftruncate(fa->fd, fa->pos);
6620}
6621
6622/*
6623 * :markup: markdown
6624 *
6625 * call-seq:
6626 * truncate(size) -> 0
6627 *
6628 * Adjusts the size of `self` to the given `size`,
6629 * regardless of the current [position](rdoc-ref:IO@Position);
6630 * does not adjust the position:
6631 *
6632 * ```ruby
6633 * filepath = '/tmp/t.tmp'
6634 * file = File.new(filepath, 'w+')
6635 * file.write('0123456789')
6636 * file.truncate(5)
6637 * file.pos # => 10
6638 * file.rewind
6639 * file.read # => "01234"
6640 * ```
6641 *
6642 * Pads with null characters if necessary:
6643 *
6644 * ```ruby
6645 * file.truncate(10)
6646 * file.pos # => 5
6647 * file.rewind
6648 * file.read # => "01234\u0000\u0000\u0000\u0000\u0000"
6649 * # Clean up.
6650 * file.close
6651 * File.delete(filepath)
6652 * ```
6653 *
6654 */
6655
6656static VALUE
6657rb_file_truncate(VALUE obj, VALUE len)
6658{
6659 rb_io_t *fptr;
6660 struct ftruncate_arg fa;
6661
6662 fa.pos = NUM2OFFT(len);
6663 GetOpenFile(obj, fptr);
6664 if (!(fptr->mode & FMODE_WRITABLE)) {
6665 rb_raise(rb_eIOError, "not opened for writing");
6666 }
6667 rb_io_flush_raw(obj, 0);
6668 fa.fd = fptr->fd;
6669 if ((int)rb_io_blocking_region(fptr, nogvl_ftruncate, &fa) < 0) {
6670 rb_sys_fail_path(fptr->pathv);
6671 }
6672 return INT2FIX(0);
6673}
6674#else
6675#define rb_file_truncate rb_f_notimplement
6676#endif
6677
6678# ifndef LOCK_SH
6679# define LOCK_SH 1
6680# endif
6681# ifndef LOCK_EX
6682# define LOCK_EX 2
6683# endif
6684# ifndef LOCK_NB
6685# define LOCK_NB 4
6686# endif
6687# ifndef LOCK_UN
6688# define LOCK_UN 8
6689# endif
6690
6691#ifdef __CYGWIN__
6692#include <winerror.h>
6693#endif
6694
6695static VALUE
6696rb_thread_flock(void *data)
6697{
6698#ifdef __CYGWIN__
6699 int old_errno = errno;
6700#endif
6701 int *op = data, ret = flock(op[0], op[1]);
6702
6703#ifdef __CYGWIN__
6704 if (GetLastError() == ERROR_NOT_LOCKED) {
6705 ret = 0;
6706 errno = old_errno;
6707 }
6708#endif
6709 return (VALUE)ret;
6710}
6711
6712/* :markup: markdown
6713 *
6714 * call-seq:
6715 * flock(locking_constant) -> 0 or false
6716 *
6717 * Locks or unlocks file `self` according to the given `locking_constant`,
6718 * a bitwise OR of the values in the table below.
6719 *
6720 * Not available on all platforms.
6721 *
6722 * Returns `false` if `File::LOCK_NB` is specified and the operation would have blocked;
6723 * otherwise returns `0`.
6724 *
6725 * | Constant | Lock | Effect
6726 * |-----------------|--------------|-----------------------------------------------------------------------------------------------------------------|
6727 * | `File::LOCK_EX` | Exclusive | Only one process may hold an exclusive lock for `self` at a time. |
6728 * | `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>. |
6729 * | `File::LOCK_SH` | Shared | Multiple processes may each hold a shared lock for `self` at the same time. |
6730 * | `File::LOCK_UN` | Unlock | Remove an existing lock held by this process. |
6731 *
6732 * Example:
6733 *
6734 * ```ruby
6735 * # Update a counter using an exclusive lock.
6736 * # Don't use File::WRONLY because it truncates the file.
6737 * File.open('counter', File::RDWR | File::CREAT, 0644) do |f|
6738 * f.flock(File::LOCK_EX)
6739 * value = f.read.to_i + 1
6740 * f.rewind
6741 * f.write("#{value}\n")
6742 * f.flush
6743 * f.truncate(f.pos)
6744 * end
6745 *
6746 * # Read the counter using a shared lock.
6747 * File.open('counter', 'r') do |f|
6748 * f.flock(File::LOCK_SH)
6749 * f.read
6750 * end
6751 * ```
6752 *
6753 */
6754
6755static VALUE
6756rb_file_flock(VALUE obj, VALUE operation)
6757{
6758 rb_io_t *fptr;
6759 int op[2], op1;
6760 struct timeval time;
6761
6762 op[1] = op1 = NUM2INT(operation);
6763 GetOpenFile(obj, fptr);
6764 op[0] = fptr->fd;
6765
6766 if (fptr->mode & FMODE_WRITABLE) {
6767 rb_io_flush_raw(obj, 0);
6768 }
6769 while ((int)rb_io_blocking_region(fptr, rb_thread_flock, op) < 0) {
6770 int e = errno;
6771 switch (e) {
6772 case EAGAIN:
6773 case EACCES:
6774#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
6775 case EWOULDBLOCK:
6776#endif
6777 if (op1 & LOCK_NB) return Qfalse;
6778
6779 time.tv_sec = 0;
6780 time.tv_usec = 100 * 1000; /* 0.1 sec */
6781 rb_thread_wait_for(time);
6782 rb_io_check_closed(fptr);
6783 continue;
6784
6785 case EINTR:
6786#if defined(ERESTART)
6787 case ERESTART:
6788#endif
6789 break;
6790
6791 default:
6792 rb_syserr_fail_path(e, fptr->pathv);
6793 }
6794 }
6795 return INT2FIX(0);
6796}
6797
6798static void
6799test_check(int n, int argc, VALUE *argv)
6800{
6801 int i;
6802
6803 n+=1;
6804 rb_check_arity(argc, n, n);
6805 for (i=1; i<n; i++) {
6806 if (!RB_TYPE_P(argv[i], T_FILE)) {
6807 FilePathValue(argv[i]);
6808 }
6809 }
6810}
6811
6812#define CHECK(n) test_check((n), argc, argv)
6813
6814/*
6815 * :markup: markdown
6816 *
6817 * call-seq:
6818 * test(char, path0, path1 = nil) -> object
6819 *
6820 * Performs a test on one or both of the <i>filesystem entities</i> at the given paths
6821 * `path0` and `path1`:
6822 *
6823 * - Each path `path0` or `path1` points to a file, directory, device, pipe, etc.
6824 * - Character `char` selects a specific test.
6825 *
6826 * The tests:
6827 *
6828 * - Each of these tests operates only on the entity at `path0`,
6829 * and returns `true` or `false`;
6830 * for a non-existent entity, returns `false` (does not raise exception):
6831 *
6832 * | Character | Test |
6833 * |:------------:|:--------------------------------------------------------------------------|
6834 * | <tt>'b'</tt> | Whether the entity is a block device. |
6835 * | <tt>'c'</tt> | Whether the entity is a character device. |
6836 * | <tt>'d'</tt> | Whether the entity is a directory. |
6837 * | <tt>'e'</tt> | Whether the entity is an existing entity. |
6838 * | <tt>'f'</tt> | Whether the entity is an existing regular file. |
6839 * | <tt>'g'</tt> | Whether the entity's setgid bit is set. |
6840 * | <tt>'G'</tt> | Whether the entity's group ownership is equal to the caller's. |
6841 * | <tt>'k'</tt> | Whether the entity's sticky bit is set. |
6842 * | <tt>'l'</tt> | Whether the entity is a symbolic link. |
6843 * | <tt>'o'</tt> | Whether the entity is owned by the caller's effective uid. |
6844 * | <tt>'O'</tt> | Like <tt>'o'</tt>, but uses the real uid (not the effective uid). |
6845 * | <tt>'p'</tt> | Whether the entity is a FIFO device (named pipe). |
6846 * | <tt>'r'</tt> | Whether the entity is readable by the caller's effective uid/gid. |
6847 * | <tt>'R'</tt> | Like <tt>'r'</tt>, but uses the real uid/gid (not the effective uid/gid). |
6848 * | <tt>'S'</tt> | Whether the entity is a socket. |
6849 * | <tt>'u'</tt> | Whether the entity's setuid bit is set. |
6850 * | <tt>'w'</tt> | Whether the entity is writable by the caller's effective uid/gid. |
6851 * | <tt>'W'</tt> | Like <tt>'w'</tt>, but uses the real uid/gid (not the effective uid/gid). |
6852 * | <tt>'x'</tt> | Whether the entity is executable by the caller's effective uid/gid. |
6853 * | <tt>'X'</tt> | Like <tt>'x'</tt>, but uses the real uid/gid (not the effective uid/git). |
6854 * | <tt>'z'</tt> | Whether the entity exists and is of length zero. |
6855 *
6856 * - This test operates only on the entity at `path0`,
6857 * and returns an integer size or `nil`:
6858 *
6859 * | Character | Test |
6860 * |:------------:|:---------------------------------------------------------------------------------------------|
6861 * | <tt>'s'</tt> | Returns positive integer size if the entity exists and has non-zero length, `nil` otherwise. |
6862 *
6863 * - Each of these tests operates only on the entity at `path0`,
6864 * and returns a Time object;
6865 * raises an exception if the entity does not exist:
6866 *
6867 * | Character | Test |
6868 * |:------------:|:---------------------------------------|
6869 * | <tt>'A'</tt> | Last access time for the entity. |
6870 * | <tt>'C'</tt> | Last change time for the entity. |
6871 * | <tt>'M'</tt> | Last modification time for the entity. |
6872 *
6873 * - Each of these tests operates on the modification time (`mtime`)
6874 * of each of the entities at `path0` and `path1`,
6875 * and returns a `true` or `false`;
6876 * returns `false` if either entity does not exist:
6877 *
6878 * | Character | Test |
6879 * |:------------:|:----------------------------------------------------------------|
6880 * | <tt>'<'</tt> | Whether the `mtime` at `path0` is less than that at `path1`. |
6881 * | <tt>'='</tt> | Whether the `mtime` at `path0` is equal to that at `path1`. |
6882 * | <tt>'>'</tt> | Whether the `mtime` at `path0` is greater than that at `path1`. |
6883 *
6884 * - This test operates on the content of each of the entities at `path0` and `path1`,
6885 * and returns a `true` or `false`;
6886 * returns `false` if either entity does not exist:
6887 *
6888 * | Character | Test |
6889 * |:------------:|:----------------------------------------------|
6890 * | <tt>'-'</tt> | Whether the entities exist and are identical. |
6891 *
6892 */
6893
6894static VALUE
6895rb_f_test(int argc, VALUE *argv, VALUE _)
6896{
6897 int cmd;
6898
6899 if (argc == 0) rb_check_arity(argc, 2, 3);
6900 cmd = NUM2CHR(argv[0]);
6901 if (cmd == 0) {
6902 goto unknown;
6903 }
6904 if (strchr("bcdefgGkloOprRsSuwWxXz", cmd)) {
6905 CHECK(1);
6906 switch (cmd) {
6907 case 'b':
6908 return rb_file_blockdev_p(0, argv[1]);
6909
6910 case 'c':
6911 return rb_file_chardev_p(0, argv[1]);
6912
6913 case 'd':
6914 return rb_file_directory_p(0, argv[1]);
6915
6916 case 'e':
6917 return rb_file_exist_p(0, argv[1]);
6918
6919 case 'f':
6920 return rb_file_file_p(0, argv[1]);
6921
6922 case 'g':
6923 return rb_file_sgid_p(0, argv[1]);
6924
6925 case 'G':
6926 return rb_file_grpowned_p(0, argv[1]);
6927
6928 case 'k':
6929 return rb_file_sticky_p(0, argv[1]);
6930
6931 case 'l':
6932 return rb_file_symlink_p(0, argv[1]);
6933
6934 case 'o':
6935 return rb_file_owned_p(0, argv[1]);
6936
6937 case 'O':
6938 return rb_file_rowned_p(0, argv[1]);
6939
6940 case 'p':
6941 return rb_file_pipe_p(0, argv[1]);
6942
6943 case 'r':
6944 return rb_file_readable_p(0, argv[1]);
6945
6946 case 'R':
6947 return rb_file_readable_real_p(0, argv[1]);
6948
6949 case 's':
6950 return rb_file_size_p(0, argv[1]);
6951
6952 case 'S':
6953 return rb_file_socket_p(0, argv[1]);
6954
6955 case 'u':
6956 return rb_file_suid_p(0, argv[1]);
6957
6958 case 'w':
6959 return rb_file_writable_p(0, argv[1]);
6960
6961 case 'W':
6962 return rb_file_writable_real_p(0, argv[1]);
6963
6964 case 'x':
6965 return rb_file_executable_p(0, argv[1]);
6966
6967 case 'X':
6968 return rb_file_executable_real_p(0, argv[1]);
6969
6970 case 'z':
6971 return rb_file_zero_p(0, argv[1]);
6972 }
6973 }
6974
6975 if (strchr("MAC", cmd)) {
6976 struct stat st;
6977 VALUE fname = argv[1];
6978
6979 CHECK(1);
6980 if (rb_stat(fname, &st) == -1) {
6981 int e = errno;
6982 FilePathValue(fname);
6983 rb_syserr_fail_path(e, fname);
6984 }
6985
6986 switch (cmd) {
6987 case 'A':
6988 return stat_atime(&st);
6989 case 'M':
6990 return stat_mtime(&st);
6991 case 'C':
6992 return stat_ctime(&st);
6993 }
6994 }
6995
6996 if (cmd == '-') {
6997 CHECK(2);
6998 return rb_file_identical_p(0, argv[1], argv[2]);
6999 }
7000
7001 if (strchr("=<>", cmd)) {
7002 struct stat st1, st2;
7003 stat_timestamp t1, t2;
7004
7005 CHECK(2);
7006 if (rb_stat(argv[1], &st1) < 0) return Qfalse;
7007 if (rb_stat(argv[2], &st2) < 0) return Qfalse;
7008
7009 t1 = stat_mtimespec(&st1);
7010 t2 = stat_mtimespec(&st2);
7011
7012 switch (cmd) {
7013 case '=':
7014 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec) return Qtrue;
7015 return Qfalse;
7016
7017 case '>':
7018 if (t1.tv_sec > t2.tv_sec) return Qtrue;
7019 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec) return Qtrue;
7020 return Qfalse;
7021
7022 case '<':
7023 if (t1.tv_sec < t2.tv_sec) return Qtrue;
7024 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec < t2.tv_nsec) return Qtrue;
7025 return Qfalse;
7026 }
7027 }
7028 unknown:
7029 /* unknown command */
7030 if (ISPRINT(cmd)) {
7031 rb_raise(rb_eArgError, "unknown command '%s%c'", cmd == '\'' || cmd == '\\' ? "\\" : "", cmd);
7032 }
7033 else {
7034 rb_raise(rb_eArgError, "unknown command \"\\x%02X\"", cmd);
7035 }
7037}
7038
7039
7040/*
7041 * Document-class: File::Stat
7042 *
7043 * :markup: markdown
7044 *
7045 * A \File::Stat object contains information about an entry in the filesystem.
7046 *
7047 * Each of these methods returns a new \File::Stat object.
7048 * the first three follow symbolic links; the others don't:
7049 *
7050 * - File::Stat.new
7051 * - File::stat
7052 * - IO#stat
7053 * - File#lstat
7054 * - File::lstat
7055 *
7056 * ## Snapshot
7057 *
7058 * A new \File::Stat object takes an immediate "snapshot" of the filesystem entry
7059 * at the given 'path';
7060 * the snapshot is never updated, regardless of changes in the entry (even its deletion):
7061 *
7062 * ```ruby
7063 * filepath = '/tmp/t.tmp'
7064 * File.stat(filepath) # Raises Errno::ENOENT: No such file or directory.
7065 * File.write(filepath, 'foo')
7066 * stat = File.stat(filepath)
7067 * stat.birthtime # => 2026-10-03 12:19:23.723062465 -0500
7068 * File.delete(filepath)
7069 * stat.birthtime # => 2026-10-03 12:19:23.723062465 -0500
7070 * ```
7071 *
7072 * ## Filesystem Dependencies
7073 *
7074 * Methods in a \File::Stat object may return filesystem-dependent values,
7075 * and not all values are meaningful on all filesystems;
7076 * for example, File::Stat#blocks returns `nil` on a Windows filesystem,
7077 * but returns an integer on others.
7078 *
7079 * See also Kernel#test.
7080 */
7081
7082static VALUE
7083rb_stat_s_alloc(VALUE klass)
7084{
7085 VALUE obj;
7086 stat_alloc(klass, &obj);
7087 return obj;
7088}
7089
7090/*
7091 * :markup: markdown
7092 *
7093 * call-seq:
7094 * File::Stat.new(path) -> stat
7095 *
7096 * Returns a new \File::Stat object containing a [snapshot](rdoc-ref:File::Stat@Snapshot)
7097 * of the filesystem entry at the given `path`:
7098 *
7099 * ```ruby
7100 * File::Stat.new('/etc/passwd')
7101 * File::Stat.new('/tmp')
7102 * File::Stat.new('nosuch') # Raises Errno::ENOENT: No such file or directory.
7103 * ```
7104 */
7105
7106static VALUE
7107rb_stat_init(VALUE obj, VALUE fname)
7108{
7109 rb_io_stat_data st;
7110
7111 FilePathValue(fname);
7112 fname = rb_str_encode_ospath(fname);
7113 if (STATX(StringValueCStr(fname), &st, STATX_ALL) == -1) {
7114 rb_sys_fail_path(fname);
7115 }
7116
7117 struct rb_stat *rb_st;
7118 TypedData_Get_Struct(obj, struct rb_stat, &stat_data_type, rb_st);
7119
7120 rb_st->stat = st;
7121 rb_st->initialized = true;
7122
7123 return Qnil;
7124}
7125
7126/* :nodoc: */
7127static VALUE
7128rb_stat_init_copy(VALUE copy, VALUE orig)
7129{
7130 if (!OBJ_INIT_COPY(copy, orig)) return copy;
7131
7132 struct rb_stat *orig_rb_st;
7133 TypedData_Get_Struct(orig, struct rb_stat, &stat_data_type, orig_rb_st);
7134
7135 struct rb_stat *copy_rb_st;
7136 TypedData_Get_Struct(copy, struct rb_stat, &stat_data_type, copy_rb_st);
7137
7138 *copy_rb_st = *orig_rb_st;
7139 return copy;
7140}
7141
7142/*
7143 * :markup: markdown
7144 *
7145 * call-seq:
7146 * ftype -> string
7147 *
7148 * Returns the string type of the path in the [snapshot](rdoc-ref:File::Stat@Snapshot)
7149 * in `self`, one of:
7150 *
7151 * - `'file'`.
7152 * - `'directory'`.
7153 * - `'characterSpecial'`.
7154 * - `'blockSpecial'`.
7155 * - `'fifo'`.
7156 * - `'link'`.
7157 * - `'socket'`.
7158 *
7159 * Examples:
7160 *
7161 * ```ruby
7162 * File.stat('/etc/passwd').ftype # => "file"
7163 * File.stat('/etc').ftype # => "directory"
7164 * File.stat('/dev/null').ftype # => "characterSpecial"
7165 * File.stat('/dev/loop0').ftype # => "blockSpecial"
7166 *
7167 * File.mkfifo('/tmp/pipe', 0o666)
7168 * File.stat('/tmp/pipe').ftype # => "fifo"
7169 *
7170 * File.symlink('/etc/passwd', '/tmp/link')
7171 * File.lstat('/tmp/link').ftype # => "link"
7172 *
7173 * require 'socket'
7174 * UNIXServer.new('/tmp/socket')
7175 * File.stat('/tmp/socket').ftype # => "socket"
7176 *
7177 * File.delete('/tmp/link', '/tmp/pipe', '/tmp/socket') # Clean up.
7178 * ```
7179 *
7180 * Returns `'unknown'` if the type cannot be determined.
7181 */
7182
7183static VALUE
7184rb_stat_ftype(VALUE obj)
7185{
7186 return rb_file_ftype(get_stat(obj)->ST_(mode));
7187}
7188
7189/*
7190 * :markup: markdown
7191 *
7192 * call-seq:
7193 * directory? -> true or false
7194 *
7195 * Returns whether the entry in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`
7196 * is a directory:
7197 *
7198 * ```ruby
7199 * File.stat('/etc').directory? # => true
7200 * File.stat('/etc/passwd').directory? # => false
7201 * ```
7202 *
7203 */
7204
7205static VALUE
7206rb_stat_d(VALUE obj)
7207{
7208 if (S_ISDIR(get_stat(obj)->ST_(mode))) return Qtrue;
7209 return Qfalse;
7210}
7211
7212/*
7213 * :markup: markdown
7214 *
7215 * call-seq:
7216 * pipe? -> true or false
7217 *
7218 * Returns whether the entry at the path in `self` is a pipe:
7219 *
7220 * ```ruby
7221 * pipepath = '/tmp/pipe'
7222 * File.mkfifo(pipepath) # Make pipe.
7223 * stat = File.stat(pipepath) # Take snapshot.
7224 * stat.pipe? # => true # In snapshot.
7225 * File.delete(pipepath) # Clean up.
7226 * stat.pipe? # => true # Snapshot not updated.
7227 * File.stat('/etc').pipe? # => false
7228 * File.stat('/etc/passwd').pipe? # => false
7229 * ```
7230 *
7231 */
7232
7233static VALUE
7234rb_stat_p(VALUE obj)
7235{
7236#ifdef S_IFIFO
7237 if (S_ISFIFO(get_stat(obj)->ST_(mode))) return Qtrue;
7238
7239#endif
7240 return Qfalse;
7241}
7242
7243/*
7244 * :markup: markdown
7245 *
7246 * call-seq:
7247 * symlink? -> true or false
7248 *
7249 * Returns whether the entry in `self` (see [Snapshot](rdoc-ref:File::Stat@Snapshot))
7250 * is a [symbolic link](rdoc-ref:file/symbolic_links.md):
7251 *
7252 * ```ruby
7253 * filepath = '/etc/passwd'
7254 * linkpath = '/tmp/foo'
7255 * File.symlink(filepath, linkpath)
7256 * File.stat(filepath).symlink? # => false
7257 * File.lstat(filepath).symlink? # => false
7258 * stat = File.stat(linkpath) # Snapshot with stat follows link.
7259 * stat.symlink? # => false
7260 * lstat = File.lstat(linkpath) # Snapshot with lstat does not follow link.
7261 * lstat.symlink? # => true
7262 * File.delete(linkpath) # Clean up.
7263 * # Snapshots are unchanged, even when link deleted.
7264 * stat.symlink? # => false
7265 * lstat.symlink? # => true
7266 * ```
7267 *
7268 */
7269
7270static VALUE
7271rb_stat_l(VALUE obj)
7272{
7273#ifdef S_ISLNK
7274 if (S_ISLNK(get_stat(obj)->ST_(mode))) return Qtrue;
7275#endif
7276 return Qfalse;
7277}
7278
7279/*
7280 * :markup: markdown
7281 *
7282 * call-seq:
7283 * socket? -> true or false
7284 *
7285 * Returns whether entry in the [snapshot](rdoc-ref:File::Stat@Snapshot)
7286 * in `self` is a socket:
7287 *
7288 * ```ruby
7289 * require 'socket'
7290 * socket_path = '/tmp/socket'
7291 * server = UNIXServer.new(socket_path)
7292 * stat = File.stat(socket_path) # Take snapshot.
7293 * stat.socket? # => true
7294 * server.close # Clean up.
7295 * File.delete(socket_path) # Clean up.
7296 * stat.socket? # => true # Snapshot not updated.
7297 * File.stat('/tmp').socket? # => false # Not a socket.
7298 * ```
7299 *
7300 */
7301
7302static VALUE
7303rb_stat_S(VALUE obj)
7304{
7305#ifdef S_ISSOCK
7306 if (S_ISSOCK(get_stat(obj)->ST_(mode))) return Qtrue;
7307
7308#endif
7309 return Qfalse;
7310}
7311
7312/*
7313 * :markup: markdown
7314 *
7315 * call-seq:
7316 * blockdev? -> true or false
7317 *
7318 * Returns whether the entry in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`
7319 * is a block device (i.e., a direct-access device):
7320 *
7321 * ```ruby
7322 * File.stat('/dev/nvme0n1').blockdev? # => true
7323 * File.stat('/dev/loop0').blockdev? # => true
7324 * File.stat('/dev/tty').blockdev? # => false
7325 * File.stat('/dev/null').blockdev? # => false
7326 * ```
7327 *
7328 * The returned value is filesystem-dependent; returns `false` on Windows or MacOS.
7329 *
7330 */
7331
7332static VALUE
7333rb_stat_b(VALUE obj)
7334{
7335#ifdef S_ISBLK
7336 if (S_ISBLK(get_stat(obj)->ST_(mode))) return Qtrue;
7337
7338#endif
7339 return Qfalse;
7340}
7341
7342/*
7343 * :markup: markdown
7344 *
7345 * call-seq:
7346 * chardev? -> true or false
7347 *
7348 * Returns whether the entry in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`
7349 * is a character device (i.e., a sequential-access device):
7350 *
7351 * ```ruby
7352 * File.stat('/dev/tty').chardev? # => true
7353 * File.stat('/dev/null').chardev? # => true
7354 * File.stat('/dev/nvme0n1').chardev? # => false
7355 * File.stat('/dev/loop0').chardev? # => false
7356 * ```
7357 *
7358 * The returned value is filesystem-dependent; on Windows, always `false`.
7359 */
7360
7361static VALUE
7362rb_stat_c(VALUE obj)
7363{
7364 if (S_ISCHR(get_stat(obj)->ST_(mode))) return Qtrue;
7365
7366 return Qfalse;
7367}
7368
7369/*
7370 * :markup: markdown
7371 *
7372 * call-seq:
7373 * owned? -> true or false
7374 *
7375 * Returns whether the owner in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`
7376 * is the owner of the current process:
7377 *
7378 * ```ruby
7379 * dirpath = '/tmp/dir'
7380 * Dir.mkdir(dirpath)
7381 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
7382 * File.write(filepath, 'foo')
7383 * dirstat = File.stat(dirpath) # Snapshot of directory.
7384 * filestat = File.stat(filepath) # Snapshot of file.
7385 * dirstat.owned? # => true
7386 * filestat.owned? # => true
7387 * FileUtils.rm_rf(dirpath) # Clean up.
7388 * dirstat.owned? # => true # Snapshot not updated.
7389 * filestat.owned? # => true # Snapshot not updated.
7390 * ```
7391 *
7392 */
7393
7394static VALUE
7395rb_stat_owned(VALUE obj)
7396{
7397 if (get_stat(obj)->ST_(uid) == geteuid()) return Qtrue;
7398 return Qfalse;
7399}
7400
7401static VALUE
7402rb_stat_rowned(VALUE obj)
7403{
7404 if (get_stat(obj)->ST_(uid) == getuid()) return Qtrue;
7405 return Qfalse;
7406}
7407
7408/*
7409 * :markup: markdown
7410 *
7411 * call-seq:
7412 * grpowned?(path) -> true or false
7413 *
7414 * Returns `false` on Windows.
7415 *
7416 * On other systems, returns whether the effective group id of the calling process
7417 * is the owner of the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`:
7418 *
7419 * ```ruby
7420 * filepath = '/tmp/t.tmp'
7421 * File.write(filepath, 'foo')
7422 * File.stat(filepath).grpowned? # => true
7423 * File.stat('/etc').grpowned? # => false
7424 * File.stat('/etc/passwd').grpowned? # => false
7425 * File.delete(filepath) # Clean up.
7426 * ```
7427 *
7428 */
7429
7430static VALUE
7431rb_stat_grpowned(VALUE obj)
7432{
7433#ifndef _WIN32
7434 if (rb_group_member(get_stat(obj)->ST_(gid))) return Qtrue;
7435#endif
7436 return Qfalse;
7437}
7438
7439/*
7440 * :markup: markdown
7441 *
7442 * call-seq:
7443 * readable? -> true or false
7444 *
7445 * Returns whether the entry represented by `self`
7446 * exists and is readable by the owner and group of the current process;
7447 * see [Permissions](rdoc-ref:file/filesystem_modes.md@Permissions):
7448 *
7449 * ```ruby
7450 * path = '/tmp/secret.txt'
7451 * File.write(path, 'foo')
7452 * File.stat(path).readable? # => true
7453 * File.chmod(0o000, path)
7454 * File.stat(path).readable? # => false
7455 * File.delete(path) # Clean up.
7456 * ```
7457 *
7458 */
7459
7460static VALUE
7461rb_stat_r(VALUE obj)
7462{
7463 rb_io_stat_data *st = get_stat(obj);
7464
7465#ifdef USE_GETEUID
7466 if (geteuid() == 0) return Qtrue;
7467#endif
7468#ifdef S_IRUSR
7469 if (rb_stat_owned(obj))
7470 return RBOOL(st->ST_(mode) & S_IRUSR);
7471#endif
7472#ifdef S_IRGRP
7473 if (rb_stat_grpowned(obj))
7474 return RBOOL(st->ST_(mode) & S_IRGRP);
7475#endif
7476#ifdef S_IROTH
7477 if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
7478#endif
7479 return Qtrue;
7480}
7481
7482/*
7483 * :markup: markdown
7484 *
7485 * call-seq:
7486 * stat.readable_real? -> true or false
7487 *
7488 * Like #readable?, but checks against the real owner and group ids
7489 * instead of the effective ids.
7490 */
7491
7492static VALUE
7493rb_stat_R(VALUE obj)
7494{
7495 rb_io_stat_data *st = get_stat(obj);
7496
7497#ifdef USE_GETEUID
7498 if (getuid() == 0) return Qtrue;
7499#endif
7500#ifdef S_IRUSR
7501 if (rb_stat_rowned(obj))
7502 return RBOOL(st->ST_(mode) & S_IRUSR);
7503#endif
7504#ifdef S_IRGRP
7505 if (rb_group_member(get_stat(obj)->ST_(gid)))
7506 return RBOOL(st->ST_(mode) & S_IRGRP);
7507#endif
7508#ifdef S_IROTH
7509 if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
7510#endif
7511 return Qtrue;
7512}
7513
7514/*
7515 * :markup: markdown
7516 *
7517 * call-seq:
7518 * world_readable? -> integer or nil
7519 *
7520 * Returns the integer [permissions](rdoc-ref:file/filesystem_modes.md@Permissions)
7521 * for the entry in the [snapshot](rdoc-ref:File::Stat@Snapshot) in `self`
7522 * if those permissions allow reading by others;
7523 * otherwise, returns `nil`:
7524 *
7525 * ```ruby
7526 * filepath = '/tmp/t.tmp'
7527 * File.write(filepath, 'foo') # Create file.
7528 * File.chmod(0o000, filepath) # Make file unreadable.
7529 * stat0 = File.stat(filepath) # Take snapshot.
7530 * stat0.world_readable? # => nil # Snapshot shows not world-readable.
7531 * File.chmod(0o644, filepath) # Make file world-readable.
7532 * stat0.world_readable? # => nil # Snapshot not updated.
7533 * stat1 = File.stat(filepath) # Take new snapshot.
7534 * stat1.world_readable?.to_s(8) # => "644" # Snapshot shows world-readable.
7535 * File.delete(filepath) # Clean up.
7536 * stat1.world_readable?.to_s(8) # => "644" # Snapshot not updated.
7537 * ```
7538 */
7539
7540static VALUE
7541rb_stat_wr(VALUE obj)
7542{
7543#ifdef S_IROTH
7544 rb_io_stat_data *st = get_stat(obj);
7545 if ((st->ST_(mode) & (S_IROTH)) == S_IROTH) {
7546 return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
7547 }
7548#endif
7549 return Qnil;
7550}
7551
7552/*
7553 * :markup: markdown
7554
7555 * call-seq:
7556 * writable? -> true or false
7557 *
7558 * Returns whether the entry at the path in `self` exists and is writable
7559 * by the effective owner and group in the current process:
7560 *
7561 * ```ruby
7562 * filepath = '/tmp/secret.txt'
7563 * File.write(filepath, 'foo')
7564 * File.stat(filepath).writable? # => true # Writable.
7565 * File.chmod(0o000, filepath) # Make non-writable.
7566 * File.stat(filepath).writable? # => false # Not writable.
7567 * File.delete(filepath) # Clean up.
7568 * File.stat('/etc').writable? # => false # Directory.
7569 * ```
7570 *
7571 * Note that filesystem security features may cause this method to return `true`
7572 * even when the file is not writable by the effective owner and group.
7573 */
7574
7575static VALUE
7576rb_stat_w(VALUE obj)
7577{
7578 rb_io_stat_data *st = get_stat(obj);
7579
7580#ifdef USE_GETEUID
7581 if (geteuid() == 0) return Qtrue;
7582#endif
7583#ifdef S_IWUSR
7584 if (rb_stat_owned(obj))
7585 return RBOOL(st->ST_(mode) & S_IWUSR);
7586#endif
7587#ifdef S_IWGRP
7588 if (rb_stat_grpowned(obj))
7589 return RBOOL(st->ST_(mode) & S_IWGRP);
7590#endif
7591#ifdef S_IWOTH
7592 if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
7593#endif
7594 return Qtrue;
7595}
7596
7597/*
7598 * :markup: markdown
7599 *
7600 * call-seq:
7601 * writable_real? -> true or false
7602 *
7603 * Like File::Stat.writable?, but checks against the real owner and group
7604 * instead of the effective owner and group.
7605 *
7606 * Note that filesystem security features may cause this method to return `true`
7607 * even when the entry in `self` is not writable by the real owner and group.
7608 */
7609
7610static VALUE
7611rb_stat_W(VALUE obj)
7612{
7613 rb_io_stat_data *st = get_stat(obj);
7614
7615#ifdef USE_GETEUID
7616 if (getuid() == 0) return Qtrue;
7617#endif
7618#ifdef S_IWUSR
7619 if (rb_stat_rowned(obj))
7620 return RBOOL(st->ST_(mode) & S_IWUSR);
7621#endif
7622#ifdef S_IWGRP
7623 if (rb_group_member(get_stat(obj)->ST_(gid)))
7624 return RBOOL(st->ST_(mode) & S_IWGRP);
7625#endif
7626#ifdef S_IWOTH
7627 if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
7628#endif
7629 return Qtrue;
7630}
7631
7632/*
7633 * :markup: markdown
7634
7635 * call-seq:
7636 * world_writable? -> integer or nil
7637 *
7638 * If the entry in `self` exists and is writable by others,
7639 * returns the integer [permissions](rdoc-ref:file/filesystem_modes.md@Permissions)
7640 * for the entry;
7641 * otherwise, returns `nil`:
7642 *
7643 * ```ruby
7644 * filepath = '/tmp/t.tmp'
7645 * File.write(filepath, 'foo')
7646 * File.stat(filepath).world_writable? # => nil # Not world-writable.
7647 * File.chmod(0o777, filepath) # Make world-writable.
7648 * File.stat(filepath).world_writable?.to_s(8) # => "777" # World-writable.
7649 * File.delete(filepath) # Clean up.
7650 * File.stat('/tmp').world_writable?.to_s(8) # => "777" # Directory.
7651 * ```
7652 *
7653 */
7654
7655static VALUE
7656rb_stat_ww(VALUE obj)
7657{
7658#ifdef S_IWOTH
7659 rb_io_stat_data *st = get_stat(obj);
7660 if ((st->ST_(mode) & (S_IWOTH)) == S_IWOTH) {
7661 return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
7662 }
7663#endif
7664 return Qnil;
7665}
7666
7667/*
7668 * :markup: markdown
7669 *
7670 * call-seq:
7671 * executable? -> true or false
7672 *
7673 * Returns whether the filesystem entry represented by `self`
7674 * is [executable](rdoc-ref:file/filesystem_modes.md@Executable+Files)
7675 * by the effective owner/group of the current process:
7676 *
7677 * ```ruby
7678 * File.stat('/bin/ruby').executable? # => true
7679 * File.stat('/etc').executable? # => true
7680 * File.stat('/etc/passwd').executable? # => false
7681 * ```
7682 *
7683 * Raises Errno::ENOENT if the entry does not exist.
7684 *
7685 * Note that some filesystem settings may cause this method to return `true`
7686 * even though the entry is not executable by the effective owner/group.
7687 */
7688
7689static VALUE
7690rb_stat_x(VALUE obj)
7691{
7692 rb_io_stat_data *st = get_stat(obj);
7693
7694#ifdef USE_GETEUID
7695 if (geteuid() == 0) {
7696 return RBOOL(st->ST_(mode) & S_IXUGO);
7697 }
7698#endif
7699#ifdef S_IXUSR
7700 if (rb_stat_owned(obj))
7701 return RBOOL(st->ST_(mode) & S_IXUSR);
7702#endif
7703#ifdef S_IXGRP
7704 if (rb_stat_grpowned(obj))
7705 return RBOOL(st->ST_(mode) & S_IXGRP);
7706#endif
7707#ifdef S_IXOTH
7708 if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
7709#endif
7710 return Qtrue;
7711}
7712
7713/*
7714 * :markup: markdown
7715 * call-seq:
7716 * executable_real? -> true or false
7717 *
7718 * Like Pathname#executable?, but checks against the real owner/group
7719 * instead of the effective owner/group.
7720 */
7721
7722static VALUE
7723rb_stat_X(VALUE obj)
7724{
7725 rb_io_stat_data *st = get_stat(obj);
7726
7727#ifdef USE_GETEUID
7728 if (getuid() == 0) {
7729 return RBOOL(st->ST_(mode) & S_IXUGO);
7730 }
7731#endif
7732#ifdef S_IXUSR
7733 if (rb_stat_rowned(obj))
7734 return RBOOL(st->ST_(mode) & S_IXUSR);
7735#endif
7736#ifdef S_IXGRP
7737 if (rb_group_member(get_stat(obj)->ST_(gid)))
7738 return RBOOL(st->ST_(mode) & S_IXGRP);
7739#endif
7740#ifdef S_IXOTH
7741 if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
7742#endif
7743 return Qtrue;
7744}
7745
7746/*
7747 * :markup: markdown
7748 *
7749 * call-seq:
7750 * file? -> true or false
7751 *
7752 * Returns whether `self` represents a filesystem entry that exists and is a regular file;
7753 * see File::Stat.ftype:
7754 *
7755 * ```ruby
7756 * File.stat('/etc/passwd').file? # => true
7757 * File.stat('/etc').file? # => false
7758 * ```
7759 *
7760 */
7761
7762static VALUE
7763rb_stat_f(VALUE obj)
7764{
7765 if (S_ISREG(get_stat(obj)->ST_(mode))) return Qtrue;
7766 return Qfalse;
7767}
7768
7769/*
7770 * :markup: markdown
7771 *
7772 * call-seq:
7773 * zero? -> true or false
7774 *
7775 * Returns whether the entry at the path in `self` has size zero.
7776 *
7777 * The entry may be a file:
7778 *
7779 * ```ruby
7780 * filepath = '/tmp/t.tmp'
7781 * File.write(filepath, 'foo')
7782 * File.stat(filepath).zero? # => false
7783 * File.truncate(filepath, 0)
7784 * File.stat(filepath).zero? # => true
7785 * File.delete(filepath) # Clean up.
7786 * ```
7787 *
7788 * The entry may be a directory:
7789 *
7790 * ```ruby
7791 * dirpath = '/tmp/foo'
7792 * Dir.mkdir(dirpath)
7793 * stat = File.stat(dirpath)
7794 * # Size is filesystem-dependent; may or may not be zero.
7795 * stat.size # => 4096
7796 * stat.zero? # => false
7797 * filepath = File.join(dirpath, 't.tmp') # => "/tmp/foo/t.tmp"
7798 * File.write(filepath, 'foo')
7799 * stat = File.stat(dirpath)
7800 * stat.size # => 4096
7801 * stat.zero? # => false
7802 * FileUtils.rm_rf(dirpath) # Clean up.
7803 * ```
7804 *
7805 */
7806
7807static VALUE
7808rb_stat_z(VALUE obj)
7809{
7810 if (get_stat(obj)->ST_(size) == 0) return Qtrue;
7811 return Qfalse;
7812}
7813
7814/*
7815 * :markup: markdown
7816 *
7817 * call-seq:
7818 * size? -> integer or nil
7819 *
7820 * Returns the size in bytes of the entry in `self`
7821 * if the entry exists and has non-zero size, `nil` otherwise:
7822 *
7823 * ```ruby
7824 * filepath = '/tmp/t.tmp'
7825 * File.write(filepath, 'foo')
7826 * File.size(filepath) # => 3
7827 * stat = File.stat(filepath) # Take snapshot.
7828 * stat.size? # => 3 # Non-zero size.
7829 * File.write(filepath, '')
7830 * File.size(filepath) # => 0
7831 * stat.size? # => 3 # Snapshot unchanged.
7832 * stat = File.stat(filepath) # Take new snapshot.
7833 * stat.size? # => nil # Zero size
7834 * File.delete(filepath) # Clean up.
7835 * ```
7836 *
7837 */
7838
7839static VALUE
7840rb_stat_s(VALUE obj)
7841{
7842 rb_off_t size = get_stat(obj)->ST_(size);
7843
7844 if (size == 0) return Qnil;
7845 return OFFT2NUM(size);
7846}
7847
7848/*
7849 * :markup: markdown
7850 *
7851 * call-seq:
7852 * setuid? -> true or false
7853 *
7854 * Returns whether the setuid bit is set
7855 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
7856 * for the entry represented in `self`:
7857 *
7858 * ```ruby
7859 * path = '/tmp/t.tmp'
7860 * File.write(path, 'foo')
7861 * stat = File.stat(path) # Take snapshot; bit not set.
7862 * stat.setuid? # => false
7863 * stat.mode.to_s(8) # => "100664"
7864 * File.chmod(0o4644, path) # Set the bit; snapshot not updated.
7865 * stat.setuid? # => false
7866 * stat.mode.to_s(8) # => "100664"
7867 * stat = File.stat(path) # Fresh snapshot.
7868 * stat.setuid? # => true
7869 * stat.mode.to_s(8) # => "104644"
7870 * File.delete(path) # Clean up.
7871 * ```
7872 *
7873 * On Windows, the bit is never set; the method always returns `false`.
7874 */
7875
7876static VALUE
7877rb_stat_suid(VALUE obj)
7878{
7879#ifdef S_ISUID
7880 if (get_stat(obj)->ST_(mode) & S_ISUID) return Qtrue;
7881#endif
7882 return Qfalse;
7883}
7884
7885/*
7886 * :markup: markdown
7887 *
7888 * call-seq:
7889 * setgid? -> true or false
7890 *
7891 * Returns whether the setgid bit is set
7892 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
7893 * for the entry represented in `self`:
7894 *
7895 * ```ruby
7896 * path = '/tmp/t.tmp'
7897 * File.write(path, 'foo')
7898 * stat = File.stat(path) # Take a snapshot.
7899 * stat.setgid? # => false
7900 * stat.mode.to_s(8) # => "100664"
7901 * File.chmod(0o2644, path) # Set the bit; stat snapshot unchanged.
7902 * stat.setgid? # => false
7903 * stat.mode.to_s(8) # => "100664"
7904 * stat = File.stat(path) # Fresh stat; snapshot changed.
7905 * stat.setgid? # => true
7906 * stat.mode.to_s(8) # => "102644"
7907 * File.delete(path) # Clean up.
7908 * ```
7909 *
7910 * On Windows, the bit is never set; the method always returns `false`.
7911 */
7912
7913static VALUE
7914rb_stat_sgid(VALUE obj)
7915{
7916#ifdef S_ISGID
7917 if (get_stat(obj)->ST_(mode) & S_ISGID) return Qtrue;
7918#endif
7919 return Qfalse;
7920}
7921
7922/*
7923 * :markup: markdown
7924
7925 * call-seq:
7926 * sticky? -> true or false
7927 *
7928 * Returns whether the sticky bit is set
7929 * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
7930 * for `self`:
7931 *
7932 * ```ruby
7933 * filepath = '/tmp/t.tmp'
7934 * File.write(filepath, 'foo')
7935 * stat = File.stat(filepath)
7936 * stat.sticky? # => false
7937 * stat.mode.to_s(8) # => "100664"
7938 * File.chmod(01644, filepath) # => 1 # Stat unchanged.
7939 * stat.sticky? # => false
7940 * stat.mode.to_s(8) # => "100664"
7941 * stat = File.stat(filepath) # Fresh stat.
7942 * stat.sticky? # => true
7943 * stat.mode.to_s(8) # => "101644"
7944 * File.delete(filepath) # Clean up.
7945 * ```
7946 *
7947 * Returns `false` on Windows.
7948 */
7949
7950static VALUE
7951rb_stat_sticky(VALUE obj)
7952{
7953#ifdef S_ISVTX
7954 if (get_stat(obj)->ST_(mode) & S_ISVTX) return Qtrue;
7955#endif
7956 return Qfalse;
7957}
7958
7959#if !defined HAVE_MKFIFO && defined HAVE_MKNOD && defined S_IFIFO
7960#define mkfifo(path, mode) mknod(path, (mode)&~S_IFMT|S_IFIFO, 0)
7961#define HAVE_MKFIFO
7962#endif
7963
7964#ifdef HAVE_MKFIFO
7965struct mkfifo_arg {
7966 const char *path;
7967 mode_t mode;
7968};
7969
7970static void *
7971nogvl_mkfifo(void *ptr)
7972{
7973 struct mkfifo_arg *ma = ptr;
7974
7975 return (void *)(VALUE)mkfifo(ma->path, ma->mode);
7976}
7977
7978/*
7979 * :markup: markdown
7980 *
7981 * call-seq:
7982 * File.mkfifo(path, mode = 0666) -> 0
7983 *
7984 * Creates a FIFO special file at the given `path`,
7985 * with the permissions given by `mode`;
7986 * see [Filesystem Modes](rdoc-ref:file/filesystem_modes.md):
7987 *
7988 * ```ruby
7989 * path = '/tmp/pipe'
7990 * File.mkfifo(path)
7991 * File.pipe?(path) # => true
7992 * File.ftype(path) # => "fifo"
7993 * File.unlink(path)
7994 * ```
7995 *
7996 * Not implemented on Windows.
7997 */
7998
7999static VALUE
8000rb_file_s_mkfifo(int argc, VALUE *argv, VALUE _)
8001{
8002 VALUE path;
8003 struct mkfifo_arg ma;
8004
8005 ma.mode = 0666;
8006 rb_check_arity(argc, 1, 2);
8007 if (argc > 1) {
8008 ma.mode = NUM2MODET(argv[1]);
8009 }
8010 path = argv[0];
8011 FilePathValue(path);
8012 path = rb_str_encode_ospath(path);
8013 ma.path = RSTRING_PTR(path);
8014 if (IO_WITHOUT_GVL(nogvl_mkfifo, &ma)) {
8015 rb_sys_fail_path(path);
8016 }
8017 return INT2FIX(0);
8018}
8019#else
8020#define rb_file_s_mkfifo rb_f_notimplement
8021#endif
8022
8023static VALUE rb_mFConst;
8024
8025void
8026rb_file_const(const char *name, VALUE value)
8027{
8028 rb_define_const(rb_mFConst, name, value);
8029}
8030
8031int
8032rb_is_absolute_path(const char *path)
8033{
8034#ifdef DOSISH_DRIVE_LETTER
8035 if (has_drive_letter(path) && isdirsep(path[2])) return 1;
8036#endif
8037#ifdef DOSISH_UNC
8038 if (isdirsep(path[0]) && isdirsep(path[1])) return 1;
8039#endif
8040#ifndef DOSISH
8041 if (path[0] == '/') return 1;
8042#endif
8043 return 0;
8044}
8045
8046int
8047ruby_is_fd_loadable(int fd)
8048{
8049#ifdef _WIN32
8050 return 1;
8051#else
8052 struct stat st;
8053
8054 if (fstat(fd, &st) < 0)
8055 return 0;
8056
8057 if (S_ISREG(st.st_mode))
8058 return 1;
8059
8060 if (S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode))
8061 return -1;
8062
8063 if (S_ISDIR(st.st_mode))
8064 errno = EISDIR;
8065 else
8066 errno = ENXIO;
8067
8068 return 0;
8069#endif
8070}
8071
8072#ifndef _WIN32
8073int
8074rb_file_load_ok(const char *path)
8075{
8076 int ret = 1;
8077 /*
8078 open(2) may block if path is FIFO and it's empty. Let's use O_NONBLOCK.
8079 FIXME: Why O_NDELAY is checked?
8080 */
8081 int mode = (O_RDONLY |
8082#if defined O_NONBLOCK
8083 O_NONBLOCK |
8084#elif defined O_NDELAY
8085 O_NDELAY |
8086#endif
8087 0);
8088 int fd = rb_cloexec_open(path, mode, 0);
8089 if (fd < 0) {
8090 if (!rb_gc_for_fd(errno)) return 0;
8091 fd = rb_cloexec_open(path, mode, 0);
8092 if (fd < 0) return 0;
8093 }
8094 rb_update_max_fd(fd);
8095 ret = ruby_is_fd_loadable(fd);
8096 (void)close(fd);
8097 return ret;
8098}
8099#endif
8100
8101static int
8102is_explicit_relative(const char *path)
8103{
8104 if (*path++ != '.') return 0;
8105 if (*path == '.') path++;
8106 return isdirsep(*path);
8107}
8108
8109static VALUE
8110copy_path_class(VALUE path, VALUE orig)
8111{
8112 int encidx = rb_enc_get_index(orig);
8113 if (encidx == ENCINDEX_ASCII_8BIT || encidx == ENCINDEX_US_ASCII)
8114 encidx = rb_filesystem_encindex();
8115 rb_enc_associate_index(path, encidx);
8116 str_shrink(path);
8117 RBASIC_SET_CLASS(path, rb_obj_class(orig));
8118 OBJ_FREEZE(path);
8119 return path;
8120}
8121
8122static bool
8123nav_component_p(const char *s, const char *send)
8124{
8125 if ((send - s) >= 2 && s[0] == '.') {
8126 return s[1] == '.' || isdirsep(s[1]);
8127 }
8128 return false;
8129}
8130
8131static bool
8132fname_need_expansion_p(VALUE fname)
8133{
8134 const char *s = RSTRING_PTR(fname);
8135 const rb_len_t len = RSTRING_LEN(fname);
8136 const char *send = s + len;
8137
8138 if (nav_component_p(s, send)) {
8139 return true;
8140 }
8141
8142 rb_encoding *enc = rb_str_enc_get(fname);
8143 bool mbenc = enc_mbclen_needed(enc);
8144
8145 s = enc_path_next(s, send, mbenc, enc);
8146 while (s < send) {
8147 if (nav_component_p(s, send)) {
8148 return true;
8149 }
8150 s++;
8151 s = enc_path_next(s, send, mbenc, enc);
8152 }
8153 return false;
8154}
8155
8156static bool
8157expand_feature(VALUE fname, VALUE dname, VALUE buffer, bool need_expansion)
8158{
8159 rb_len_t dname_len = RSTRING_LEN(dname);
8160 const char *dname_ptr = RSTRING_PTR(dname);
8161
8162 RUBY_ASSERT(dname_len > 0);
8163
8164 if (need_expansion || dname_ptr[0] == '~') {
8165 rb_file_expand_path_internal(fname, dname, 0, 0, buffer);
8166 }
8167 else {
8168 rb_str_set_len(buffer, 0);
8169 rb_str_append(buffer, dname);
8170 if (!isdirsep(dname_ptr[dname_len - 1])) {
8171 rb_str_cat(buffer, "/", 1);
8172 }
8173 rb_str_append(buffer, fname);
8174 }
8175 return true;
8176}
8177
8178int
8179rb_find_file_ext(VALUE *filep, const char *const *ext)
8180{
8181 const char *f = StringValueCStr(*filep);
8182 VALUE fname = *filep;
8183 rb_len_t i, j, fnlen;
8184 int expanded = 0;
8185
8186 if (!ext[0]) return 0;
8187
8188 if (f[0] == '~') {
8189 fname = file_expand_path_1(fname, DLEXT_MAXLEN);
8190 f = RSTRING_PTR(fname);
8191 *filep = fname;
8192 expanded = 1;
8193 }
8194
8195 if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) {
8196 if (!expanded) fname = file_expand_path_1(fname, DLEXT_MAXLEN);
8197 fnlen = RSTRING_LEN(fname);
8198 for (i=0; ext[i]; i++) {
8199 rb_str_cat2(fname, ext[i]);
8200 if (rb_file_load_ok(RSTRING_PTR(fname))) {
8201 *filep = copy_path_class(fname, *filep);
8202 return (int)(i+1);
8203 }
8204 rb_str_set_len(fname, fnlen);
8205 }
8206 return 0;
8207 }
8208
8209 rb_len_t expanded_load_path_maxlen;
8210 VALUE load_path = rb_get_expanded_load_path(&expanded_load_path_maxlen);
8211 if (!load_path) return 0;
8212
8213 fname = rb_str_dup(*filep);
8214 RBASIC_CLEAR_CLASS(fname);
8215 fnlen = RSTRING_LEN(fname);
8216 bool need_expansion = fname_need_expansion_p(fname);
8217
8218 VALUE tmp = rb_str_tmp_new(expanded_load_path_maxlen + fnlen + 2);
8219 rb_enc_associate_index(tmp, rb_usascii_encindex());
8220
8221 for (j=0; ext[j]; j++) {
8222 rb_str_cat2(fname, ext[j]);
8223 for (i = 0; i < RARRAY_LEN(load_path); i++) {
8224 VALUE dname = rb_get_path(RARRAY_AREF(load_path, i));
8225 if (!RSTRING_LEN(dname)) continue;
8226 expand_feature(fname, dname, tmp, need_expansion);
8227
8228 if (rb_file_load_ok(RSTRING_PTR(tmp))) {
8229 *filep = copy_path_class(tmp, *filep);
8230 return (int)(j+1);
8231 }
8232 }
8233 rb_str_set_len(fname, fnlen);
8234 }
8235 rb_str_resize(tmp, 0);
8236 RB_GC_GUARD(load_path);
8237 RB_GC_GUARD(tmp);
8238 return 0;
8239}
8240
8241VALUE
8242rb_find_file(VALUE path)
8243{
8244 const char *f = StringValueCStr(path);
8245 int expanded = 0;
8246
8247 if (f[0] == '~') {
8248 path = copy_path_class(file_expand_path_1(path, 0), path);
8249 f = RSTRING_PTR(path);
8250 expanded = 1;
8251 }
8252
8253 if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) {
8254 if (!rb_file_load_ok(f)) return 0;
8255 if (!expanded)
8256 path = copy_path_class(file_expand_path_1(path, 0), path);
8257 return path;
8258 }
8259
8260 rb_len_t expanded_load_path_maxlen;
8261 VALUE load_path = rb_get_expanded_load_path(&expanded_load_path_maxlen);
8262
8263 if (load_path) {
8264 bool need_expansion = fname_need_expansion_p(path);
8265 VALUE tmp = rb_str_tmp_new(expanded_load_path_maxlen + RSTRING_LEN(path) + 2);
8266 rb_enc_associate_index(tmp, rb_usascii_encindex());
8267 for (rb_len_t i = 0; i < RARRAY_LEN(load_path); i++) {
8268 VALUE dname = rb_get_path(RARRAY_AREF(load_path, i));
8269 if (!RSTRING_LEN(dname)) continue;
8270 expand_feature(path, dname, tmp, need_expansion);
8271
8272 if (rb_file_load_ok(RSTRING_PTR(tmp))) {
8273 return copy_path_class(tmp, path);
8274 }
8275 }
8276 rb_str_resize(tmp, 0);
8277 }
8278
8279 RB_GC_GUARD(load_path);
8280
8281 return Qfalse; /* no path, no load */
8282}
8283
8284#define define_filetest_function(name, func, argc) do { \
8285 rb_define_module_function(rb_mFileTest, name, func, argc); \
8286 rb_define_singleton_method(rb_cFile, name, func, argc); \
8287} while(false)
8288
8289const char ruby_null_device[] =
8290#if defined DOSISH
8291 "NUL"
8292#else
8293 "/dev/null"
8294#endif
8295 ;
8296
8297/*
8298 * A \File object is a representation of a file in the underlying platform.
8299 *
8300 * Class \File extends module FileTest, supporting such singleton methods
8301 * as <tt>File.exist?</tt>.
8302 *
8303 * == About the Examples
8304 *
8305 * Many examples here use these variables:
8306 *
8307 * :include: doc/examples/files.rdoc
8308 *
8309 * == Access Modes
8310 *
8311 * Methods File.new and File.open each create a \File object for a given file path.
8312 *
8313 * === \String Access Modes
8314 *
8315 * Methods File.new and File.open each may take string argument +mode+, which:
8316 *
8317 * - Begins with a 1- or 2-character
8318 * {read/write mode}[rdoc-ref:File@ReadWrite+Mode].
8319 * - May also contain a 1-character {data mode}[rdoc-ref:File@Data+Mode].
8320 * - May also contain a 1-character
8321 * {file-create mode}[rdoc-ref:File@File-Create+Mode].
8322 *
8323 * ==== Read/Write Mode
8324 *
8325 * The read/write +mode+ determines:
8326 *
8327 * - Whether the file is to be initially truncated.
8328 *
8329 * - Whether reading is allowed, and if so:
8330 *
8331 * - The initial read position in the file.
8332 * - Where in the file reading can occur.
8333 *
8334 * - Whether writing is allowed, and if so:
8335 *
8336 * - The initial write position in the file.
8337 * - Where in the file writing can occur.
8338 *
8339 * These tables summarize:
8340 *
8341 * Read/Write Modes for Existing File
8342 *
8343 * |------|-----------|----------|----------|----------|-----------|
8344 * | R/W | Initial | | Initial | | Initial |
8345 * | Mode | Truncate? | Read | Read Pos | Write | Write Pos |
8346 * |------|-----------|----------|----------|----------|-----------|
8347 * | 'r' | No | Anywhere | 0 | Error | - |
8348 * | 'w' | Yes | Error | - | Anywhere | 0 |
8349 * | 'a' | No | Error | - | End only | End |
8350 * | 'r+' | No | Anywhere | 0 | Anywhere | 0 |
8351 * | 'w+' | Yes | Anywhere | 0 | Anywhere | 0 |
8352 * | 'a+' | No | Anywhere | End | End only | End |
8353 * |------|-----------|----------|----------|----------|-----------|
8354 *
8355 * Read/Write Modes for \File To Be Created
8356 *
8357 * |------|----------|----------|----------|-----------|
8358 * | R/W | | Initial | | Initial |
8359 * | Mode | Read | Read Pos | Write | Write Pos |
8360 * |------|----------|----------|----------|-----------|
8361 * | 'w' | Error | - | Anywhere | 0 |
8362 * | 'a' | Error | - | End only | 0 |
8363 * | 'w+' | Anywhere | 0 | Anywhere | 0 |
8364 * | 'a+' | Anywhere | 0 | End only | End |
8365 * |------|----------|----------|----------|-----------|
8366 *
8367 * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
8368 * for a non-existent file (exception raised).
8369 *
8370 * In the tables:
8371 *
8372 * - +Anywhere+ means that methods IO#rewind, IO#pos=, and IO#seek
8373 * may be used to change the file's position,
8374 * so that allowed reading or writing may occur anywhere in the file.
8375 * - <tt>End only</tt> means that writing can occur only at end-of-file,
8376 * and that methods IO#rewind, IO#pos=, and IO#seek do not affect writing.
8377 * - +Error+ means that an exception is raised if disallowed reading or writing
8378 * is attempted.
8379 *
8380 * ===== Read/Write Modes for Existing \File
8381 *
8382 * - <tt>'r'</tt>:
8383 *
8384 * - \File is not initially truncated:
8385 *
8386 * f = File.new('t.txt') # => #<File:t.txt>
8387 * f.size == 0 # => false
8388 *
8389 * - File's initial read position is 0:
8390 *
8391 * f.pos # => 0
8392 *
8393 * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
8394 *
8395 * f.readline # => "First line\n"
8396 * f.readline # => "Second line\n"
8397 *
8398 * f.rewind
8399 * f.readline # => "First line\n"
8400 *
8401 * f.pos = 1
8402 * f.readline # => "irst line\n"
8403 *
8404 * f.seek(1, :CUR)
8405 * f.readline # => "econd line\n"
8406 *
8407 * - Writing is not allowed:
8408 *
8409 * f.write('foo') # Raises IOError.
8410 *
8411 * - <tt>'w'</tt>:
8412 *
8413 * - \File is initially truncated:
8414 *
8415 * path = 't.tmp'
8416 * File.write(path, text)
8417 * f = File.new(path, 'w')
8418 * f.size == 0 # => true
8419 *
8420 * - File's initial write position is 0:
8421 *
8422 * f.pos # => 0
8423 *
8424 * - \File may be written anywhere (even past end-of-file);
8425 * see IO#rewind, IO#pos=, IO#seek:
8426 *
8427 * f.write('foo')
8428 * f.flush
8429 * File.read(path) # => "foo"
8430 * f.pos # => 3
8431 *
8432 * f.write('bar')
8433 * f.flush
8434 * File.read(path) # => "foobar"
8435 * f.pos # => 6
8436 *
8437 * f.rewind
8438 * f.write('baz')
8439 * f.flush
8440 * File.read(path) # => "bazbar"
8441 * f.pos # => 3
8442 *
8443 * f.pos = 3
8444 * f.write('foo')
8445 * f.flush
8446 * File.read(path) # => "bazfoo"
8447 * f.pos # => 6
8448 *
8449 * f.seek(-3, :END)
8450 * f.write('bam')
8451 * f.flush
8452 * File.read(path) # => "bazbam"
8453 * f.pos # => 6
8454 *
8455 * f.pos = 8
8456 * f.write('bah') # Zero padding as needed.
8457 * f.flush
8458 * File.read(path) # => "bazbam\u0000\u0000bah"
8459 * f.pos # => 11
8460 *
8461 * - Reading is not allowed:
8462 *
8463 * f.read # Raises IOError.
8464 *
8465 * - <tt>'a'</tt>:
8466 *
8467 * - \File is not initially truncated:
8468 *
8469 * path = 't.tmp'
8470 * File.write(path, 'foo')
8471 * f = File.new(path, 'a')
8472 * f.size == 0 # => false
8473 *
8474 * - File's initial position is 0 (but is ignored):
8475 *
8476 * f.pos # => 0
8477 *
8478 * - \File may be written only at end-of-file;
8479 * IO#rewind, IO#pos=, IO#seek do not affect writing:
8480 *
8481 * f.write('bar')
8482 * f.flush
8483 * File.read(path) # => "foobar"
8484 * f.write('baz')
8485 * f.flush
8486 * File.read(path) # => "foobarbaz"
8487 *
8488 * f.rewind
8489 * f.write('bat')
8490 * f.flush
8491 * File.read(path) # => "foobarbazbat"
8492 *
8493 * - Reading is not allowed:
8494 *
8495 * f.read # Raises IOError.
8496 *
8497 * - <tt>'r+'</tt>:
8498 *
8499 * - \File is not initially truncated:
8500 *
8501 * path = 't.tmp'
8502 * File.write(path, text)
8503 * f = File.new(path, 'r+')
8504 * f.size == 0 # => false
8505 *
8506 * - File's initial read position is 0:
8507 *
8508 * f.pos # => 0
8509 *
8510 * - \File may be read or written anywhere (even past end-of-file);
8511 * see IO#rewind, IO#pos=, IO#seek:
8512 *
8513 * f.readline # => "First line\n"
8514 * f.readline # => "Second line\n"
8515 *
8516 * f.rewind
8517 * f.readline # => "First line\n"
8518 *
8519 * f.pos = 1
8520 * f.readline # => "irst line\n"
8521 *
8522 * f.seek(1, :CUR)
8523 * f.readline # => "econd line\n"
8524 *
8525 * f.rewind
8526 * f.write('WWW')
8527 * f.flush
8528 * File.read(path)
8529 * # => "WWWst line\nSecond line\nFourth line\nFifth line\n"
8530 *
8531 * f.pos = 10
8532 * f.write('XXX')
8533 * f.flush
8534 * File.read(path)
8535 * # => "WWWst lineXXXecond line\nFourth line\nFifth line\n"
8536 *
8537 * f.seek(-6, :END)
8538 * # => 0
8539 * f.write('YYY')
8540 * # => 3
8541 * f.flush
8542 * # => #<File:t.tmp>
8543 * File.read(path)
8544 * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n"
8545 *
8546 * f.seek(2, :END)
8547 * f.write('ZZZ') # Zero padding as needed.
8548 * f.flush
8549 * File.read(path)
8550 * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n\u0000\u0000ZZZ"
8551 *
8552 *
8553 * - <tt>'a+'</tt>:
8554 *
8555 * - \File is not initially truncated:
8556 *
8557 * path = 't.tmp'
8558 * File.write(path, 'foo')
8559 * f = File.new(path, 'a+')
8560 * f.size == 0 # => false
8561 *
8562 * - File's initial read position is 0:
8563 *
8564 * f.pos # => 0
8565 *
8566 * - \File may be written only at end-of-file;
8567 * IO#rewind, IO#pos=, IO#seek do not affect writing:
8568 *
8569 * f.write('bar')
8570 * f.flush
8571 * File.read(path) # => "foobar"
8572 * f.write('baz')
8573 * f.flush
8574 * File.read(path) # => "foobarbaz"
8575 *
8576 * f.rewind
8577 * f.write('bat')
8578 * f.flush
8579 * File.read(path) # => "foobarbazbat"
8580 *
8581 * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
8582 *
8583 * f.rewind
8584 * f.read # => "foobarbazbat"
8585 *
8586 * f.pos = 3
8587 * f.read # => "barbazbat"
8588 *
8589 * f.seek(-3, :END)
8590 * f.read # => "bat"
8591 *
8592 * ===== Read/Write Modes for \File To Be Created
8593 *
8594 * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
8595 * for a non-existent file (exception raised).
8596 *
8597 * - <tt>'w'</tt>:
8598 *
8599 * - File's initial write position is 0:
8600 *
8601 * path = 't.tmp'
8602 * FileUtils.rm_f(path)
8603 * f = File.new(path, 'w')
8604 * f.pos # => 0
8605 *
8606 * - \File may be written anywhere (even past end-of-file);
8607 * see IO#rewind, IO#pos=, IO#seek:
8608 *
8609 * f.write('foo')
8610 * f.flush
8611 * File.read(path) # => "foo"
8612 * f.pos # => 3
8613 *
8614 * f.write('bar')
8615 * f.flush
8616 * File.read(path) # => "foobar"
8617 * f.pos # => 6
8618 *
8619 * f.rewind
8620 * f.write('baz')
8621 * f.flush
8622 * File.read(path) # => "bazbar"
8623 * f.pos # => 3
8624 *
8625 * f.pos = 3
8626 * f.write('foo')
8627 * f.flush
8628 * File.read(path) # => "bazfoo"
8629 * f.pos # => 6
8630 *
8631 * f.seek(-3, :END)
8632 * f.write('bam')
8633 * f.flush
8634 * File.read(path) # => "bazbam"
8635 * f.pos # => 6
8636 *
8637 * f.pos = 8
8638 * f.write('bah') # Zero padding as needed.
8639 * f.flush
8640 * File.read(path) # => "bazbam\u0000\u0000bah"
8641 * f.pos # => 11
8642 *
8643 * - Reading is not allowed:
8644 *
8645 * f.read # Raises IOError.
8646 *
8647 * - <tt>'a'</tt>:
8648 *
8649 * - File's initial write position is 0:
8650 *
8651 * path = 't.tmp'
8652 * FileUtils.rm_f(path)
8653 * f = File.new(path, 'a')
8654 * f.pos # => 0
8655 *
8656 * - Writing occurs only at end-of-file:
8657 *
8658 * f.write('foo')
8659 * f.pos # => 3
8660 * f.write('bar')
8661 * f.pos # => 6
8662 * f.flush
8663 * File.read(path) # => "foobar"
8664 *
8665 * f.rewind
8666 * f.write('baz')
8667 * f.flush
8668 * File.read(path) # => "foobarbaz"
8669 *
8670 * - Reading is not allowed:
8671 *
8672 * f.read # Raises IOError.
8673 *
8674 * - <tt>'w+'</tt>:
8675 *
8676 * - File's initial position is 0:
8677 *
8678 * path = 't.tmp'
8679 * FileUtils.rm_f(path)
8680 * f = File.new(path, 'w+')
8681 * f.pos # => 0
8682 *
8683 * - \File may be written anywhere (even past end-of-file);
8684 * see IO#rewind, IO#pos=, IO#seek:
8685 *
8686 * f.write('foo')
8687 * f.flush
8688 * File.read(path) # => "foo"
8689 * f.pos # => 3
8690 *
8691 * f.write('bar')
8692 * f.flush
8693 * File.read(path) # => "foobar"
8694 * f.pos # => 6
8695 *
8696 * f.rewind
8697 * f.write('baz')
8698 * f.flush
8699 * File.read(path) # => "bazbar"
8700 * f.pos # => 3
8701 *
8702 * f.pos = 3
8703 * f.write('foo')
8704 * f.flush
8705 * File.read(path) # => "bazfoo"
8706 * f.pos # => 6
8707 *
8708 * f.seek(-3, :END)
8709 * f.write('bam')
8710 * f.flush
8711 * File.read(path) # => "bazbam"
8712 * f.pos # => 6
8713 *
8714 * f.pos = 8
8715 * f.write('bah') # Zero padding as needed.
8716 * f.flush
8717 * File.read(path) # => "bazbam\u0000\u0000bah"
8718 * f.pos # => 11
8719 *
8720 * - \File may be read anywhere (even past end-of-file);
8721 * see IO#rewind, IO#pos=, IO#seek:
8722 *
8723 * f.rewind
8724 * # => 0
8725 * f.read
8726 * # => "bazbam\u0000\u0000bah"
8727 *
8728 * f.pos = 3
8729 * # => 3
8730 * f.read
8731 * # => "bam\u0000\u0000bah"
8732 *
8733 * f.seek(-3, :END)
8734 * # => 0
8735 * f.read
8736 * # => "bah"
8737 *
8738 * - <tt>'a+'</tt>:
8739 *
8740 * - File's initial write position is 0:
8741 *
8742 * path = 't.tmp'
8743 * FileUtils.rm_f(path)
8744 * f = File.new(path, 'a+')
8745 * f.pos # => 0
8746 *
8747 * - Writing occurs only at end-of-file:
8748 *
8749 * f.write('foo')
8750 * f.pos # => 3
8751 * f.write('bar')
8752 * f.pos # => 6
8753 * f.flush
8754 * File.read(path) # => "foobar"
8755 *
8756 * f.rewind
8757 * f.write('baz')
8758 * f.flush
8759 * File.read(path) # => "foobarbaz"
8760 *
8761 * - \File may be read anywhere (even past end-of-file);
8762 * see IO#rewind, IO#pos=, IO#seek:
8763 *
8764 * f.rewind
8765 * f.read # => "foobarbaz"
8766 *
8767 * f.pos = 3
8768 * f.read # => "barbaz"
8769 *
8770 * f.seek(-3, :END)
8771 * f.read # => "baz"
8772 *
8773 * f.pos = 800
8774 * f.read # => ""
8775 *
8776 * ==== \Data Mode
8777 *
8778 * To specify whether data is to be treated as text or as binary data,
8779 * either of the following may be suffixed to any of the string read/write modes
8780 * above:
8781 *
8782 * - <tt>'t'</tt>: Text data; sets the default external encoding
8783 * to <tt>Encoding::UTF_8</tt>;
8784 * on Windows, enables conversion between EOL and CRLF
8785 * and enables interpreting <tt>0x1A</tt> as an end-of-file marker.
8786 * - <tt>'b'</tt>: Binary data; sets the default external encoding
8787 * to <tt>Encoding::ASCII_8BIT</tt>;
8788 * on Windows, suppresses conversion between EOL and CRLF
8789 * and disables interpreting <tt>0x1A</tt> as an end-of-file marker.
8790 *
8791 * If neither is given, the stream defaults to text data.
8792 *
8793 * Examples:
8794 *
8795 * File.new('t.txt', 'rt')
8796 * File.new('t.dat', 'rb')
8797 *
8798 * When the data mode is specified, the read/write mode may not be omitted,
8799 * and the data mode must precede the file-create mode, if given:
8800 *
8801 * File.new('t.dat', 'b') # Raises an exception.
8802 * File.new('t.dat', 'rxb') # Raises an exception.
8803 *
8804 * ==== \File-Create Mode
8805 *
8806 * The following may be suffixed to any writable string mode above:
8807 *
8808 * - <tt>'x'</tt>: Creates the file if it does not exist;
8809 * raises an exception if the file exists.
8810 *
8811 * Example:
8812 *
8813 * File.new('t.tmp', 'wx')
8814 *
8815 * When the file-create mode is specified, the read/write mode may not be omitted,
8816 * and the file-create mode must follow the data mode:
8817 *
8818 * File.new('t.dat', 'x') # Raises an exception.
8819 * File.new('t.dat', 'rxb') # Raises an exception.
8820 *
8821 * === \Integer Access Modes
8822 *
8823 * When mode is an integer it must be one or more of the following constants,
8824 * which may be combined by the bitwise OR operator <tt>|</tt>:
8825 *
8826 * - +File::RDONLY+: Open for reading only.
8827 * - +File::WRONLY+: Open for writing only.
8828 * - +File::RDWR+: Open for reading and writing.
8829 * - +File::APPEND+: Open for appending only.
8830 *
8831 * Examples:
8832 *
8833 * File.new('t.txt', File::RDONLY)
8834 * File.new('t.tmp', File::RDWR | File::CREAT | File::EXCL)
8835 *
8836 * Note: Method IO#set_encoding does not allow the mode to be specified as an integer.
8837 *
8838 * === File-Create Mode Specified as an \Integer
8839 *
8840 * These constants may also be ORed into the integer mode:
8841 *
8842 * - +File::CREAT+: Create file if it does not exist.
8843 * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists.
8844 *
8845 * === \Data Mode Specified as an \Integer
8846 *
8847 * \Data mode cannot be specified as an integer.
8848 * When the stream access mode is given as an integer,
8849 * the data mode is always text, never binary.
8850 *
8851 * Note that although there is a constant +File::BINARY+,
8852 * setting its value in an integer stream mode has no effect;
8853 * this is because, as documented in File::Constants,
8854 * the +File::BINARY+ value disables line code conversion,
8855 * but does not change the external encoding.
8856 *
8857 * === Encodings
8858 *
8859 * Any of the string modes above may specify encodings -
8860 * either external encoding only or both external and internal encodings -
8861 * by appending one or both encoding names, separated by colons:
8862 *
8863 * f = File.new('t.dat', 'rb')
8864 * f.external_encoding # => #<Encoding:ASCII-8BIT>
8865 * f.internal_encoding # => nil
8866 * f = File.new('t.dat', 'rb:UTF-16')
8867 * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
8868 * f.internal_encoding # => nil
8869 * f = File.new('t.dat', 'rb:UTF-16:UTF-16')
8870 * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
8871 * f.internal_encoding # => #<Encoding:UTF-16>
8872 * f.close
8873 *
8874 * The numerous encoding names are available in array Encoding.name_list:
8875 *
8876 * Encoding.name_list.take(3) # => ["ASCII-8BIT", "UTF-8", "US-ASCII"]
8877 *
8878 * When the external encoding is set, strings read are tagged by that encoding
8879 * when reading, and strings written are converted to that encoding when
8880 * writing.
8881 *
8882 * When both external and internal encodings are set,
8883 * strings read are converted from external to internal encoding,
8884 * and strings written are converted from internal to external encoding.
8885 * For further details about transcoding input and output,
8886 * see {Encodings}[rdoc-ref:encodings.rdoc@Encodings].
8887 *
8888 * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt>
8889 * or <tt>'BOM|UTF16-BE'</tt>,
8890 * Ruby checks for a Unicode BOM in the input document
8891 * to help determine the encoding.
8892 * For UTF-16 encodings the file open mode must be binary.
8893 * If the BOM is found,
8894 * it is stripped and the external encoding from the BOM is used.
8895 *
8896 * Note that the BOM-style encoding option is case insensitive,
8897 * so <tt>'bom|utf-8'</tt> is also valid.
8898 *
8899 * == \File Permissions
8900 *
8901 * A \File object has _permissions_, an octal integer representing
8902 * the permissions of an actual file in the underlying platform.
8903 *
8904 * Note that file permissions are quite different from the _mode_
8905 * of a file stream (\File object).
8906 *
8907 * In a \File object, the permissions are available thus,
8908 * where method +mode+, despite its name, returns permissions:
8909 *
8910 * f = File.new('t.txt')
8911 * f.lstat.mode.to_s(8) # => "100644"
8912 *
8913 * On a Unix-based operating system,
8914 * the three low-order octal digits represent the permissions
8915 * for owner (6), group (4), and world (4).
8916 * The triplet of bits in each octal digit represent, respectively,
8917 * read, write, and execute permissions.
8918 *
8919 * Permissions <tt>0644</tt> thus represent read-write access for owner
8920 * and read-only access for group and world.
8921 * See man pages {open(2)}[https://www.unix.com/man-page/bsd/2/open]
8922 * and {chmod(2)}[https://www.unix.com/man-page/bsd/2/chmod].
8923 *
8924 * For a directory, the meaning of the execute bit changes:
8925 * when set, the directory can be searched.
8926 *
8927 * Higher-order bits in permissions may indicate the type of file
8928 * (plain, directory, pipe, socket, etc.) and various other special features.
8929 *
8930 * On non-Posix operating systems, permissions may include only read-only or
8931 * read-write, in which case, the remaining permission will resemble typical
8932 * values. On Windows, for instance, the default permissions are +0644+; The
8933 * only change that can be made is to make the file read-only, which is
8934 * reported as +0444+.
8935 *
8936 * For a method that actually creates a file in the underlying platform
8937 * (as opposed to merely creating a \File object),
8938 * permissions may be specified:
8939 *
8940 * File.new('t.tmp', File::CREAT, 0644)
8941 * File.new('t.tmp', File::CREAT, 0444)
8942 *
8943 * Permissions may also be changed:
8944 *
8945 * f = File.new('t.tmp', File::CREAT, 0444)
8946 * f.chmod(0644)
8947 * f.chmod(0444)
8948 *
8949 * == \File \Constants
8950 *
8951 * Various constants for use in \File and IO methods
8952 * may be found in module File::Constants;
8953 * an array of their names is returned by <tt>File::Constants.constants</tt>.
8954 *
8955 * == What's Here
8956 *
8957 * First, what's elsewhere. Class \File:
8958 *
8959 * - Inherits from {class IO}[rdoc-ref:IO@Whats+Here],
8960 * in particular, methods for creating, reading, and writing files
8961 * - Includes module FileTest,
8962 * which provides dozens of additional methods.
8963 *
8964 * Here, class \File provides methods that are useful for:
8965 *
8966 * - {Creating}[rdoc-ref:File@Creating]
8967 * - {Querying}[rdoc-ref:File@Querying]
8968 * - {Settings}[rdoc-ref:File@Settings]
8969 * - {Other}[rdoc-ref:File@Other]
8970 *
8971 * === Creating
8972 *
8973 * - ::new: Opens the file at the given path; returns the file.
8974 * - ::open: Same as ::new, but when given a block will yield the file to the block,
8975 * and close the file upon exiting the block.
8976 * - ::link: Creates a new name for an existing file using a hard link.
8977 * - ::mkfifo: Returns the FIFO file created at the given path.
8978 * - ::symlink: Creates a symbolic link for the given file path.
8979 *
8980 * === Querying
8981 *
8982 * _Paths_
8983 *
8984 * - ::absolute_path: Returns the absolute file path for the given path.
8985 * - ::absolute_path?: Returns whether the given path is the absolute file path.
8986 * - ::basename: Returns the last component of the given file path.
8987 * - ::dirname: Returns all but the last component of the given file path.
8988 * - ::expand_path: Returns the absolute file path for the given path,
8989 * expanding <tt>~</tt> for a home directory.
8990 * - ::extname: Returns the file extension for the given file path.
8991 * - ::fnmatch? (aliased as ::fnmatch): Returns whether the given file path
8992 * matches the given pattern.
8993 * - ::join: Joins path components into a single path string.
8994 * - ::path: Returns the string representation of the given path.
8995 * - ::readlink: Returns the path to the file at the given symbolic link.
8996 * - ::realdirpath: Returns the real path for the given file path,
8997 * where the last component need not exist.
8998 * - ::realpath: Returns the real path for the given file path,
8999 * where all components must exist.
9000 * - ::split: Returns an array of two strings: the directory name and basename
9001 * of the file at the given path.
9002 * - #path (aliased as #to_path): Returns the string representation of the given path.
9003 *
9004 * _Times_
9005 *
9006 * - ::atime: Returns a Time for the most recent access to the given file.
9007 * - ::birthtime: Returns a Time for the creation of the given file.
9008 * - ::ctime: Returns a Time for the metadata change of the given file.
9009 * - ::mtime: Returns a Time for the most recent data modification to
9010 * the content of the given file.
9011 * - #atime: Returns a Time for the most recent access to +self+.
9012 * - #birthtime: Returns a Time the creation for +self+.
9013 * - #ctime: Returns a Time for the metadata change of +self+.
9014 * - #mtime: Returns a Time for the most recent data modification
9015 * to the content of +self+.
9016 *
9017 * _Types_
9018 *
9019 * - ::blockdev?: Returns whether the file at the given path is a block device.
9020 * - ::chardev?: Returns whether the file at the given path is a character device.
9021 * - ::directory?: Returns whether the file at the given path is a directory.
9022 * - ::executable?: Returns whether the file at the given path is executable
9023 * by the effective user and group of the current process.
9024 * - ::executable_real?: Returns whether the file at the given path is executable
9025 * by the real user and group of the current process.
9026 * - ::exist?: Returns whether the file at the given path exists.
9027 * - ::file?: Returns whether the file at the given path is a regular file.
9028 * - ::ftype: Returns a string giving the type of the file at the given path.
9029 * - ::grpowned?: Returns whether the effective group of the current process
9030 * owns the file at the given path.
9031 * - ::identical?: Returns whether the files at two given paths are identical.
9032 * - ::lstat: Returns the File::Stat object for the last symbolic link
9033 * in the given path.
9034 * - ::owned?: Returns whether the effective user of the current process
9035 * owns the file at the given path.
9036 * - ::pipe?: Returns whether the file at the given path is a pipe.
9037 * - ::readable?: Returns whether the file at the given path is readable
9038 * by the effective user and group of the current process.
9039 * - ::readable_real?: Returns whether the file at the given path is readable
9040 * by the real user and group of the current process.
9041 * - ::setgid?: Returns whether the setgid bit is set for the file at the given path.
9042 * - ::setuid?: Returns whether the setuid bit is set for the file at the given path.
9043 * - ::socket?: Returns whether the file at the given path is a socket.
9044 * - ::stat: Returns the File::Stat object for the file at the given path.
9045 * - ::sticky?: Returns whether the file at the given path has its sticky bit set.
9046 * - ::symlink?: Returns whether the file at the given path is a symbolic link.
9047 * - ::umask: Returns the umask value for the current process.
9048 * - ::world_readable?: Returns whether the file at the given path is readable
9049 * by others.
9050 * - ::world_writable?: Returns whether the file at the given path is writable
9051 * by others.
9052 * - ::writable?: Returns whether the file at the given path is writable
9053 * by the effective user and group of the current process.
9054 * - ::writable_real?: Returns whether the file at the given path is writable
9055 * by the real user and group of the current process.
9056 * - #lstat: Returns the File::Stat object for the last symbolic link
9057 * in the path for +self+.
9058 *
9059 * _Contents_
9060 *
9061 * - ::empty? (aliased as ::zero?): Returns whether the file at the given path
9062 * exists and is empty.
9063 * - ::size: Returns the size (bytes) of the file at the given path.
9064 * - ::size?: Returns +nil+ if there is no file at the given path,
9065 * or if that file is empty; otherwise returns the file size (bytes).
9066 * - #size: Returns the size (bytes) of +self+.
9067 *
9068 * === Settings
9069 *
9070 * - ::chmod: Changes permissions of the file at the given path.
9071 * - ::chown: Change ownership of the file at the given path.
9072 * - ::lchmod: Changes permissions of the last symbolic link in the given path.
9073 * - ::lchown: Change ownership of the last symbolic in the given path.
9074 * - ::lutime: For each given file path, sets the access time and modification time
9075 * of the last symbolic link in the path.
9076 * - ::rename: Moves the file at one given path to another given path.
9077 * - ::utime: Sets the access time and modification time of each file
9078 * at the given paths.
9079 * - #flock: Locks or unlocks +self+.
9080 *
9081 * === Other
9082 *
9083 * - ::truncate: Truncates the file at the given file path to the given size.
9084 * - ::unlink (aliased as ::delete): Deletes the file for each given file path.
9085 * - #truncate: Truncates +self+ to the given size.
9086 *
9087 */
9088
9089void
9090Init_File(void)
9091{
9092#if defined(__APPLE__) && defined(HAVE_WORKING_FORK)
9093 rb_CFString_class_initialize_before_fork();
9094#endif
9095
9096 VALUE separator;
9097
9098 rb_mFileTest = rb_define_module("FileTest");
9099 rb_cFile = rb_define_class("File", rb_cIO);
9100
9101 define_filetest_function("directory?", rb_file_directory_p, 1);
9102 define_filetest_function("exist?", rb_file_exist_p, 1);
9103 define_filetest_function("readable?", rb_file_readable_p, 1);
9104 define_filetest_function("readable_real?", rb_file_readable_real_p, 1);
9105 define_filetest_function("world_readable?", rb_file_world_readable_p, 1);
9106 define_filetest_function("writable?", rb_file_writable_p, 1);
9107 define_filetest_function("writable_real?", rb_file_writable_real_p, 1);
9108 define_filetest_function("world_writable?", rb_file_world_writable_p, 1);
9109 define_filetest_function("executable?", rb_file_executable_p, 1);
9110 define_filetest_function("executable_real?", rb_file_executable_real_p, 1);
9111 define_filetest_function("file?", rb_file_file_p, 1);
9112 define_filetest_function("zero?", rb_file_zero_p, 1);
9113 define_filetest_function("empty?", rb_file_zero_p, 1);
9114 define_filetest_function("size?", rb_file_size_p, 1);
9115 define_filetest_function("size", rb_file_s_size, 1);
9116 define_filetest_function("owned?", rb_file_owned_p, 1);
9117 define_filetest_function("grpowned?", rb_file_grpowned_p, 1);
9118
9119 define_filetest_function("pipe?", rb_file_pipe_p, 1);
9120 define_filetest_function("symlink?", rb_file_symlink_p, 1);
9121 define_filetest_function("socket?", rb_file_socket_p, 1);
9122
9123 define_filetest_function("blockdev?", rb_file_blockdev_p, 1);
9124 define_filetest_function("chardev?", rb_file_chardev_p, 1);
9125
9126 define_filetest_function("setuid?", rb_file_suid_p, 1);
9127 define_filetest_function("setgid?", rb_file_sgid_p, 1);
9128 define_filetest_function("sticky?", rb_file_sticky_p, 1);
9129
9130 define_filetest_function("identical?", rb_file_identical_p, 2);
9131
9132 rb_define_singleton_method(rb_cFile, "stat", rb_file_s_stat, 1);
9133 rb_define_singleton_method(rb_cFile, "lstat", rb_file_s_lstat, 1);
9134 rb_define_singleton_method(rb_cFile, "ftype", rb_file_s_ftype, 1);
9135
9136 rb_define_singleton_method(rb_cFile, "atime", rb_file_s_atime, 1);
9137 rb_define_singleton_method(rb_cFile, "mtime", rb_file_s_mtime, 1);
9138 rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1);
9139 rb_define_singleton_method(rb_cFile, "birthtime", rb_file_s_birthtime, 1);
9140
9141 rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1);
9142 rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1);
9143 rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1);
9144 rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
9145 rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1);
9146 rb_define_singleton_method(rb_cFile, "lutime", rb_file_s_lutime, -1);
9147
9148 rb_define_singleton_method(rb_cFile, "link", rb_file_s_link, 2);
9149 rb_define_singleton_method(rb_cFile, "symlink", rb_file_s_symlink, 2);
9150 rb_define_singleton_method(rb_cFile, "readlink", rb_file_s_readlink, 1);
9151
9152 rb_define_singleton_method(rb_cFile, "unlink", rb_file_s_unlink, -1);
9153 rb_define_singleton_method(rb_cFile, "delete", rb_file_s_unlink, -1);
9154 rb_define_singleton_method(rb_cFile, "rename", rb_file_s_rename, 2);
9155 rb_define_singleton_method(rb_cFile, "umask", rb_file_s_umask, -1);
9156 rb_define_singleton_method(rb_cFile, "truncate", rb_file_s_truncate, 2);
9157 rb_define_singleton_method(rb_cFile, "mkfifo", rb_file_s_mkfifo, -1);
9158 rb_define_singleton_method(rb_cFile, "expand_path", s_expand_path, -1);
9159 rb_define_singleton_method(rb_cFile, "absolute_path", s_absolute_path, -1);
9160 rb_define_singleton_method(rb_cFile, "absolute_path?", s_absolute_path_p, 1);
9161 rb_define_singleton_method(rb_cFile, "realpath", rb_file_s_realpath, -1);
9162 rb_define_singleton_method(rb_cFile, "realdirpath", rb_file_s_realdirpath, -1);
9163 rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1);
9164 rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, -1);
9165 rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1);
9166 rb_define_singleton_method(rb_cFile, "path", rb_file_s_path, 1);
9167
9168 separator = rb_fstring_lit("/");
9169 /* separates directory parts in path */
9170 rb_define_const(rb_cFile, "Separator", separator);
9171 /* separates directory parts in path */
9172 rb_define_const(rb_cFile, "SEPARATOR", separator);
9173 rb_define_singleton_method(rb_cFile, "split", rb_file_s_split, 1);
9174 rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -1);
9175
9176#ifdef DOSISH
9177 /* platform specific alternative separator */
9178 rb_define_const(rb_cFile, "ALT_SEPARATOR", rb_obj_freeze(rb_usascii_str_new2(file_alt_separator)));
9179#else
9180 rb_define_const(rb_cFile, "ALT_SEPARATOR", Qnil);
9181#endif
9182 /* path list separator */
9183 rb_define_const(rb_cFile, "PATH_SEPARATOR", rb_fstring_cstr(PATH_SEP));
9184
9185 rb_define_method(rb_cIO, "stat", rb_io_stat, 0); /* this is IO's method */
9186 rb_define_method(rb_cFile, "lstat", rb_file_lstat, 0);
9187
9188 rb_define_method(rb_cFile, "atime", rb_file_atime, 0);
9189 rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0);
9190 rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0);
9191 rb_define_method(rb_cFile, "birthtime", rb_file_birthtime, 0);
9192 rb_define_method(rb_cFile, "size", file_size, 0);
9193
9194 rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1);
9195 rb_define_method(rb_cFile, "chown", rb_file_chown, 2);
9196 rb_define_method(rb_cFile, "truncate", rb_file_truncate, 1);
9197
9198 rb_define_method(rb_cFile, "flock", rb_file_flock, 1);
9199
9200 /*
9201 * Document-module: File::Constants
9202 *
9203 * Module +File::Constants+ defines file-related constants.
9204 *
9205 * There are two families of constants here:
9206 *
9207 * - Those having to do with {file access}[rdoc-ref:File::Constants@File+Access].
9208 * - Those having to do with {filename globbing}[rdoc-ref:File::Constants@Filename+Globbing+Constants+-28File-3A-3AFNM_-2A-29].
9209 *
9210 * \File constants defined for the local process may be retrieved
9211 * with method File::Constants.constants:
9212 *
9213 * File::Constants.constants.take(5)
9214 * # => [:RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT]
9215 *
9216 * == \File Access
9217 *
9218 * \File-access constants may be used with optional argument +mode+ in calls
9219 * to the following methods:
9220 *
9221 * - File.new.
9222 * - File.open.
9223 * - IO.for_fd.
9224 * - IO.new.
9225 * - IO.open.
9226 * - IO.popen.
9227 * - IO.reopen.
9228 * - IO.sysopen.
9229 * - StringIO.new.
9230 * - StringIO.open.
9231 * - StringIO#reopen.
9232 *
9233 * === Read/Write Access
9234 *
9235 * Read-write access for a stream
9236 * may be specified by a file-access constant.
9237 *
9238 * The constant may be specified as part of a bitwise OR of other such constants.
9239 *
9240 * Any combination of the constants in this section may be specified.
9241 *
9242 * ==== File::RDONLY
9243 *
9244 * Flag File::RDONLY specifies the stream should be opened for reading only:
9245 *
9246 * filepath = '/tmp/t.tmp'
9247 * f = File.new(filepath, File::RDONLY)
9248 * f.write('Foo') # Raises IOError (not opened for writing).
9249 *
9250 * ==== File::WRONLY
9251 *
9252 * Flag File::WRONLY specifies that the stream should be opened for writing only:
9253 *
9254 * f = File.new(filepath, File::WRONLY)
9255 * f.read # Raises IOError (not opened for reading).
9256 *
9257 * ==== File::RDWR
9258 *
9259 * Flag File::RDWR specifies that the stream should be opened
9260 * for both reading and writing:
9261 *
9262 * f = File.new(filepath, File::RDWR)
9263 * f.write('Foo') # => 3
9264 * f.rewind # => 0
9265 * f.read # => "Foo"
9266 *
9267 * === \File Positioning
9268 *
9269 * ==== File::APPEND
9270 *
9271 * Flag File::APPEND specifies that the stream should be opened
9272 * in append mode.
9273 *
9274 * Before each write operation, the position is set to end-of-stream.
9275 * The modification of the position and the following write operation
9276 * are performed as a single atomic step.
9277 *
9278 * ==== File::TRUNC
9279 *
9280 * Flag File::TRUNC specifies that the stream should be truncated
9281 * at its beginning.
9282 * If the file exists and is successfully opened for writing,
9283 * it is to be truncated to position zero;
9284 * its ctime and mtime are updated.
9285 *
9286 * There is no effect on a FIFO special file or a terminal device.
9287 * The effect on other file types is implementation-defined.
9288 * The result of using File::TRUNC with File::RDONLY is undefined.
9289 *
9290 * === Creating and Preserving
9291 *
9292 * ==== File::CREAT
9293 *
9294 * Flag File::CREAT specifies that the stream should be created
9295 * if it does not already exist.
9296 *
9297 * If the file exists:
9298 *
9299 * - Raise an exception if File::EXCL is also specified.
9300 * - Otherwise, do nothing.
9301 *
9302 * If the file does not exist, then it is created.
9303 * Upon successful completion, the atime, ctime, and mtime of the file are updated,
9304 * and the ctime and mtime of the parent directory are updated.
9305 *
9306 * ==== File::EXCL
9307 *
9308 * Flag File::EXCL specifies that the stream should not already exist;
9309 * If flags File::CREAT and File::EXCL are both specified
9310 * and the stream already exists, an exception is raised.
9311 *
9312 * The check for the existence and creation of the file is performed as an
9313 * atomic operation.
9314 *
9315 * If both File::EXCL and File::CREAT are specified and the path names a symbolic link,
9316 * an exception is raised regardless of the contents of the symbolic link.
9317 *
9318 * If File::EXCL is specified and File::CREAT is not specified,
9319 * the result is undefined.
9320 *
9321 * === POSIX \File \Constants
9322 *
9323 * Some file-access constants are defined only on POSIX-compliant systems;
9324 * those are:
9325 *
9326 * - File::SYNC.
9327 * - File::DSYNC.
9328 * - File::RSYNC.
9329 * - File::DIRECT.
9330 * - File::NOATIME.
9331 * - File::NOCTTY.
9332 * - File::NOFOLLOW.
9333 * - File::TMPFILE.
9334 *
9335 * ==== File::SYNC, File::RSYNC, and File::DSYNC
9336 *
9337 * Flag File::SYNC, File::RSYNC, or File::DSYNC
9338 * specifies synchronization of I/O operations with the underlying file system.
9339 *
9340 * These flags are valid only for POSIX-compliant systems.
9341 *
9342 * - File::SYNC specifies that all write operations (both data and metadata)
9343 * are immediately to be flushed to the underlying storage device.
9344 * This means that the data is written to the storage device,
9345 * and the file's metadata (e.g., file size, timestamps, permissions)
9346 * are also synchronized.
9347 * This guarantees that data is safely stored on the storage medium
9348 * before returning control to the calling program.
9349 * This flag can have a significant impact on performance
9350 * since it requires synchronous writes, which can be slower
9351 * compared to asynchronous writes.
9352 *
9353 * - File::RSYNC specifies that any read operations on the file will not return
9354 * until all outstanding write operations
9355 * (those that have been issued but not completed) are also synchronized.
9356 * This is useful when you want to read the most up-to-date data,
9357 * which may still be in the process of being written.
9358 *
9359 * - File::DSYNC specifies that all _data_ write operations
9360 * are immediately to be flushed to the underlying storage device;
9361 * this differs from File::SYNC, which requires that _metadata_
9362 * also be synchronized.
9363 *
9364 * Note that the behavior of these flags may vary slightly
9365 * depending on the operating system and filesystem being used.
9366 * Additionally, using these flags can have an impact on performance
9367 * due to the synchronous nature of the I/O operations,
9368 * so they should be used judiciously,
9369 * especially in performance-critical applications.
9370 *
9371 * ==== File::NOCTTY
9372 *
9373 * Flag File::NOCTTY specifies that if the stream is a terminal device,
9374 * that device does not become the controlling terminal for the process.
9375 *
9376 * Defined only for POSIX-compliant systems.
9377 *
9378 * ==== File::DIRECT
9379 *
9380 * Flag File::DIRECT requests that cache effects of the I/O to and from the stream
9381 * be minimized.
9382 *
9383 * Defined only for POSIX-compliant systems.
9384 *
9385 * ==== File::NOATIME
9386 *
9387 * Flag File::NOATIME specifies that act of opening the stream
9388 * should not modify its access time (atime).
9389 *
9390 * Defined only for POSIX-compliant systems.
9391 *
9392 * ==== File::NOFOLLOW
9393 *
9394 * Flag File::NOFOLLOW specifies that if path is a symbolic link,
9395 * it should not be followed.
9396 *
9397 * Defined only for POSIX-compliant systems.
9398 *
9399 * ==== File::TMPFILE
9400 *
9401 * Flag File::TMPFILE specifies that the opened stream
9402 * should be a new temporary file.
9403 *
9404 * Defined only for POSIX-compliant systems.
9405 *
9406 * === Other File-Access \Constants
9407 *
9408 * ==== File::NONBLOCK
9409 *
9410 * When possible, the file is opened in nonblocking mode.
9411 * Neither the open operation nor any subsequent I/O operations on
9412 * the file will cause the calling process to wait.
9413 *
9414 * ==== File::BINARY
9415 *
9416 * Flag File::BINARY specifies that the stream is to be accessed in binary mode.
9417 *
9418 * ==== File::SHARE_DELETE
9419 *
9420 * Flag File::SHARE_DELETE enables other processes to open the stream
9421 * with delete access.
9422 *
9423 * Windows only.
9424 *
9425 * If the stream is opened for (local) delete access without File::SHARE_DELETE,
9426 * and another process attempts to open it with delete access,
9427 * the attempt fails and the stream is not opened for that process.
9428 *
9429 * == Locking
9430 *
9431 * Four file constants relate to stream locking;
9432 * see File#flock:
9433 *
9434 * ==== File::LOCK_EX
9435 *
9436 * Flag File::LOCK_EX specifies an exclusive lock;
9437 * only one process a a time may lock the stream.
9438 *
9439 * ==== File::LOCK_NB
9440 *
9441 * Flag File::LOCK_NB specifies non-blocking locking for the stream;
9442 * may be combined with File::LOCK_EX or File::LOCK_SH.
9443 *
9444 * ==== File::LOCK_SH
9445 *
9446 * Flag File::LOCK_SH specifies that multiple processes may lock
9447 * the stream at the same time.
9448 *
9449 * ==== File::LOCK_UN
9450 *
9451 * Flag File::LOCK_UN specifies that the stream is not to be locked.
9452 *
9453 * == Filename Globbing \Constants (File::FNM_*)
9454 *
9455 * Filename-globbing constants may be used with optional argument +flags+
9456 * in calls to the following methods:
9457 *
9458 * - Dir.glob.
9459 * - File.fnmatch.
9460 * - Pathname#fnmatch.
9461 * - Pathname.glob.
9462 * - Pathname#glob.
9463 *
9464 * The constants are:
9465 *
9466 * ==== File::FNM_CASEFOLD
9467 *
9468 * Flag File::FNM_CASEFOLD makes patterns case insensitive
9469 * for File.fnmatch (but not Dir.glob).
9470 *
9471 * ==== File::FNM_DOTMATCH
9472 *
9473 * Flag File::FNM_DOTMATCH makes the <tt>'*'</tt> pattern
9474 * match a filename starting with <tt>'.'</tt>.
9475 *
9476 * ==== File::FNM_EXTGLOB
9477 *
9478 * Flag File::FNM_EXTGLOB enables pattern <tt>'{a,b}'</tt>,
9479 * which matches pattern '_a_' and pattern '_b_';
9480 * behaves like
9481 * a {regexp union}[rdoc-ref:Regexp.union]
9482 * (e.g., <tt>'(?:a|b)'</tt>):
9483 *
9484 * pattern = '{LEGAL,BSDL}'
9485 * Dir.glob(pattern) # => ["LEGAL", "BSDL"]
9486 * Pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>]
9487 * pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>]
9488 *
9489 * ==== File::FNM_NOESCAPE
9490 *
9491 * Flag File::FNM_NOESCAPE disables <tt>'\'</tt> escaping.
9492 *
9493 * ==== File::FNM_PATHNAME
9494 *
9495 * Flag File::FNM_PATHNAME specifies that patterns <tt>'*'</tt> and <tt>'?'</tt>
9496 * do not match the directory separator
9497 * (the value of constant File::SEPARATOR).
9498 *
9499 * ==== File::FNM_SHORTNAME
9500 *
9501 * Flag File::FNM_SHORTNAME allows patterns to match short names if they exist.
9502 *
9503 * Windows only.
9504 *
9505 * ==== File::FNM_SYSCASE
9506 *
9507 * Flag File::FNM_SYSCASE specifies that case sensitivity
9508 * is the same as in the underlying operating system;
9509 * effective for File.fnmatch, but not Dir.glob.
9510 *
9511 * == Other \Constants
9512 *
9513 * ==== File::NULL
9514 *
9515 * Flag File::NULL contains the string value of the null device:
9516 *
9517 * - On a Unix-like OS, <tt>'/dev/null'</tt>.
9518 * - On Windows, <tt>'NUL'</tt>.
9519 *
9520 */
9521 rb_mFConst = rb_define_module_under(rb_cFile, "Constants");
9522 rb_include_module(rb_cIO, rb_mFConst);
9523 /* {File::RDONLY}[rdoc-ref:File::Constants@File-3A-3ARDONLY] */
9524 rb_define_const(rb_mFConst, "RDONLY", INT2FIX(O_RDONLY));
9525 /* {File::WRONLY}[rdoc-ref:File::Constants@File-3A-3AWRONLY] */
9526 rb_define_const(rb_mFConst, "WRONLY", INT2FIX(O_WRONLY));
9527 /* {File::RDWR}[rdoc-ref:File::Constants@File-3A-3ARDWR] */
9528 rb_define_const(rb_mFConst, "RDWR", INT2FIX(O_RDWR));
9529 /* {File::APPEND}[rdoc-ref:File::Constants@File-3A-3AAPPEND] */
9530 rb_define_const(rb_mFConst, "APPEND", INT2FIX(O_APPEND));
9531 /* {File::CREAT}[rdoc-ref:File::Constants@File-3A-3ACREAT] */
9532 rb_define_const(rb_mFConst, "CREAT", INT2FIX(O_CREAT));
9533 /* {File::EXCL}[rdoc-ref:File::Constants@File-3A-3AEXCL] */
9534 rb_define_const(rb_mFConst, "EXCL", INT2FIX(O_EXCL));
9535#if defined(O_NDELAY) || defined(O_NONBLOCK)
9536# ifndef O_NONBLOCK
9537# define O_NONBLOCK O_NDELAY
9538# endif
9539 /* {File::NONBLOCK}[rdoc-ref:File::Constants@File-3A-3ANONBLOCK] */
9540 rb_define_const(rb_mFConst, "NONBLOCK", INT2FIX(O_NONBLOCK));
9541#endif
9542 /* {File::TRUNC}[rdoc-ref:File::Constants@File-3A-3ATRUNC] */
9543 rb_define_const(rb_mFConst, "TRUNC", INT2FIX(O_TRUNC));
9544#ifdef O_NOCTTY
9545 /* {File::NOCTTY}[rdoc-ref:File::Constants@File-3A-3ANOCTTY] */
9546 rb_define_const(rb_mFConst, "NOCTTY", INT2FIX(O_NOCTTY));
9547#endif
9548#ifndef O_BINARY
9549# define O_BINARY 0
9550#endif
9551 /* {File::BINARY}[rdoc-ref:File::Constants@File-3A-3ABINARY] */
9552 rb_define_const(rb_mFConst, "BINARY", INT2FIX(O_BINARY));
9553#ifndef O_SHARE_DELETE
9554# define O_SHARE_DELETE 0
9555#endif
9556 /* {File::SHARE_DELETE}[rdoc-ref:File::Constants@File-3A-3ASHARE_DELETE] */
9557 rb_define_const(rb_mFConst, "SHARE_DELETE", INT2FIX(O_SHARE_DELETE));
9558#ifdef O_SYNC
9559 /* {File::SYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
9560 rb_define_const(rb_mFConst, "SYNC", INT2FIX(O_SYNC));
9561#endif
9562#ifdef O_DSYNC
9563 /* {File::DSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
9564 rb_define_const(rb_mFConst, "DSYNC", INT2FIX(O_DSYNC));
9565#endif
9566#ifdef O_RSYNC
9567 /* {File::RSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
9568 rb_define_const(rb_mFConst, "RSYNC", INT2FIX(O_RSYNC));
9569#endif
9570#ifdef O_NOFOLLOW
9571 /* {File::NOFOLLOW}[rdoc-ref:File::Constants@File-3A-3ANOFOLLOW] */
9572 rb_define_const(rb_mFConst, "NOFOLLOW", INT2FIX(O_NOFOLLOW)); /* FreeBSD, Linux */
9573#endif
9574#ifdef O_NOATIME
9575 /* {File::NOATIME}[rdoc-ref:File::Constants@File-3A-3ANOATIME] */
9576 rb_define_const(rb_mFConst, "NOATIME", INT2FIX(O_NOATIME)); /* Linux */
9577#endif
9578#ifdef O_DIRECT
9579 /* {File::DIRECT}[rdoc-ref:File::Constants@File-3A-3ADIRECT] */
9580 rb_define_const(rb_mFConst, "DIRECT", INT2FIX(O_DIRECT));
9581#endif
9582#ifdef O_TMPFILE
9583 /* {File::TMPFILE}[rdoc-ref:File::Constants@File-3A-3ATMPFILE] */
9584 rb_define_const(rb_mFConst, "TMPFILE", INT2FIX(O_TMPFILE));
9585#endif
9586
9587 /* {File::LOCK_SH}[rdoc-ref:File::Constants@File-3A-3ALOCK_SH] */
9588 rb_define_const(rb_mFConst, "LOCK_SH", INT2FIX(LOCK_SH));
9589 /* {File::LOCK_EX}[rdoc-ref:File::Constants@File-3A-3ALOCK_EX] */
9590 rb_define_const(rb_mFConst, "LOCK_EX", INT2FIX(LOCK_EX));
9591 /* {File::LOCK_UN}[rdoc-ref:File::Constants@File-3A-3ALOCK_UN] */
9592 rb_define_const(rb_mFConst, "LOCK_UN", INT2FIX(LOCK_UN));
9593 /* {File::LOCK_NB}[rdoc-ref:File::Constants@File-3A-3ALOCK_NB] */
9594 rb_define_const(rb_mFConst, "LOCK_NB", INT2FIX(LOCK_NB));
9595
9596 /* {File::NULL}[rdoc-ref:File::Constants@File-3A-3ANULL] */
9597 rb_define_const(rb_mFConst, "NULL", rb_fstring_cstr(ruby_null_device));
9598
9599 rb_define_global_function("test", rb_f_test, -1);
9600
9601 rb_cStat = rb_define_class_under(rb_cFile, "Stat", rb_cObject);
9602 rb_define_alloc_func(rb_cStat, rb_stat_s_alloc);
9603 rb_define_method(rb_cStat, "initialize", rb_stat_init, 1);
9604 rb_define_method(rb_cStat, "initialize_copy", rb_stat_init_copy, 1);
9605
9607
9608 rb_define_method(rb_cStat, "<=>", rb_stat_cmp, 1);
9609
9610 rb_define_method(rb_cStat, "dev", rb_stat_dev, 0);
9611 rb_define_method(rb_cStat, "dev_major", rb_stat_dev_major, 0);
9612 rb_define_method(rb_cStat, "dev_minor", rb_stat_dev_minor, 0);
9613 rb_define_method(rb_cStat, "ino", rb_stat_ino, 0);
9614 rb_define_method(rb_cStat, "mode", rb_stat_mode, 0);
9615 rb_define_method(rb_cStat, "nlink", rb_stat_nlink, 0);
9616 rb_define_method(rb_cStat, "uid", rb_stat_uid, 0);
9617 rb_define_method(rb_cStat, "gid", rb_stat_gid, 0);
9618 rb_define_method(rb_cStat, "rdev", rb_stat_rdev, 0);
9619 rb_define_method(rb_cStat, "rdev_major", rb_stat_rdev_major, 0);
9620 rb_define_method(rb_cStat, "rdev_minor", rb_stat_rdev_minor, 0);
9621 rb_define_method(rb_cStat, "size", rb_stat_size, 0);
9622 rb_define_method(rb_cStat, "blksize", rb_stat_blksize, 0);
9623 rb_define_method(rb_cStat, "blocks", rb_stat_blocks, 0);
9624 rb_define_method(rb_cStat, "atime", rb_stat_atime, 0);
9625 rb_define_method(rb_cStat, "mtime", rb_stat_mtime, 0);
9626 rb_define_method(rb_cStat, "ctime", rb_stat_ctime, 0);
9627 rb_define_method(rb_cStat, "birthtime", rb_stat_birthtime, 0);
9628
9629 rb_define_method(rb_cStat, "inspect", rb_stat_inspect, 0);
9630
9631 rb_define_method(rb_cStat, "ftype", rb_stat_ftype, 0);
9632
9633 rb_define_method(rb_cStat, "directory?", rb_stat_d, 0);
9634 rb_define_method(rb_cStat, "readable?", rb_stat_r, 0);
9635 rb_define_method(rb_cStat, "readable_real?", rb_stat_R, 0);
9636 rb_define_method(rb_cStat, "world_readable?", rb_stat_wr, 0);
9637 rb_define_method(rb_cStat, "writable?", rb_stat_w, 0);
9638 rb_define_method(rb_cStat, "writable_real?", rb_stat_W, 0);
9639 rb_define_method(rb_cStat, "world_writable?", rb_stat_ww, 0);
9640 rb_define_method(rb_cStat, "executable?", rb_stat_x, 0);
9641 rb_define_method(rb_cStat, "executable_real?", rb_stat_X, 0);
9642 rb_define_method(rb_cStat, "file?", rb_stat_f, 0);
9643 rb_define_method(rb_cStat, "zero?", rb_stat_z, 0);
9644 rb_define_method(rb_cStat, "size?", rb_stat_s, 0);
9645 rb_define_method(rb_cStat, "owned?", rb_stat_owned, 0);
9646 rb_define_method(rb_cStat, "grpowned?", rb_stat_grpowned, 0);
9647
9648 rb_define_method(rb_cStat, "pipe?", rb_stat_p, 0);
9649 rb_define_method(rb_cStat, "symlink?", rb_stat_l, 0);
9650 rb_define_method(rb_cStat, "socket?", rb_stat_S, 0);
9651
9652 rb_define_method(rb_cStat, "blockdev?", rb_stat_b, 0);
9653 rb_define_method(rb_cStat, "chardev?", rb_stat_c, 0);
9654
9655 rb_define_method(rb_cStat, "setuid?", rb_stat_suid, 0);
9656 rb_define_method(rb_cStat, "setgid?", rb_stat_sgid, 0);
9657 rb_define_method(rb_cStat, "sticky?", rb_stat_sticky, 0);
9658}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
#define rb_define_global_function(mid, func, arity)
Defines rb_mKernel #mid.
#define PATH_SEP
The delimiter of PATH environment variable.
Definition dosish.h:45
#define GIDT2NUM
Converts a C's gid_t into an instance of rb_cInteger.
Definition gid_t.h:28
#define NUM2GIDT
Converts an instance of rb_cNumeric into C's gid_t.
Definition gid_t.h:33
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1736
#define ENCODING_SET_INLINED(obj, i)
Old name of RB_ENCODING_SET_INLINED.
Definition encoding.h:106
#define ENC_CODERANGE_7BIT
Old name of RUBY_ENC_CODERANGE_7BIT.
Definition coderange.h:180
#define T_FILE
Old name of RUBY_T_FILE.
Definition value_type.h:62
#define rb_str_buf_cat2
Old name of rb_usascii_str_new_cstr.
Definition string.h:1707
#define NUM2ULONG
Old name of RB_NUM2ULONG.
Definition long.h:52
#define ALLOCV
Old name of RB_ALLOCV.
Definition memory.h:404
#define OBJ_INIT_COPY(obj, orig)
Old name of RB_OBJ_INIT_COPY.
Definition object.h:41
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define Qundef
Old name of RUBY_Qundef.
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1708
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define rb_str_buf_new2
Old name of rb_str_buf_new_cstr.
Definition string.h:1704
#define OBJ_FREEZE
Old name of RB_OBJ_FREEZE.
Definition fl_type.h:131
#define ULONG2NUM
Old name of RB_ULONG2NUM.
Definition long.h:60
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define ENCODING_GET(obj)
Old name of RB_ENCODING_GET.
Definition encoding.h:109
#define LONG2FIX
Old name of RB_INT2FIX.
Definition long.h:49
#define MBCLEN_CHARFOUND_LEN(ret)
Old name of ONIGENC_MBCLEN_CHARFOUND_LEN.
Definition encoding.h:517
#define rb_usascii_str_new2
Old name of rb_usascii_str_new_cstr.
Definition string.h:1705
#define ISALPHA
Old name of rb_isalpha.
Definition ctype.h:92
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define TOLOWER
Old name of rb_tolower.
Definition ctype.h:101
#define Qtrue
Old name of RUBY_Qtrue.
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define NIL_P
Old name of RB_NIL_P.
#define ALLOCV_N
Old name of RB_ALLOCV_N.
Definition memory.h:405
#define MBCLEN_CHARFOUND_P(ret)
Old name of ONIGENC_MBCLEN_CHARFOUND_P.
Definition encoding.h:516
#define ISPRINT
Old name of rb_isprint.
Definition ctype.h:86
#define NUM2CHR
Old name of RB_NUM2CHR.
Definition char.h:33
#define ENCODING_GET_INLINED(obj)
Old name of RB_ENCODING_GET_INLINED.
Definition encoding.h:108
#define ENC_CODERANGE_CLEAR(obj)
Old name of RB_ENC_CODERANGE_CLEAR.
Definition coderange.h:187
#define UINT2NUM
Old name of RB_UINT2NUM.
Definition int.h:46
#define CONST_ID
Old name of RUBY_CONST_ID.
Definition symbol.h:47
#define ALLOCV_END
Old name of RB_ALLOCV_END.
Definition memory.h:406
VALUE rb_eNotImpError
NotImplementedError exception.
Definition error.c:1467
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:678
VALUE rb_eIOError
IOError exception.
Definition io.c:180
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1457
VALUE rb_eEncCompatError
Encoding::CompatibilityError exception.
Definition error.c:1464
void rb_enc_raise(rb_encoding *enc, VALUE exc, const char *fmt,...)
Identical to rb_raise(), except it additionally takes an encoding.
Definition error.c:3921
VALUE rb_eSystemCallError
SystemCallError exception.
Definition error.c:1477
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass)
Allocates, then initialises an instance of the given class.
Definition object.c:2315
VALUE rb_cIO
IO class.
Definition io.c:178
VALUE rb_cStat
File::Stat class.
Definition file.c:177
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:240
VALUE rb_inspect(VALUE obj)
Generates a human-readable textual representation of the given object.
Definition object.c:675
VALUE rb_mFileTest
FileTest module.
Definition file.c:176
VALUE rb_equal(VALUE lhs, VALUE rhs)
This function is an optimised version of calling #==.
Definition object.c:140
VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass)
Queries if the given object is an instance (of possibly descendants) of the given class.
Definition object.c:912
VALUE rb_obj_freeze(VALUE obj)
Same as RB_OBJ_FREEZE(), but returns the given object.
Definition object.c:1315
VALUE rb_mComparable
Comparable module.
Definition compar.c:19
VALUE rb_cFile
File class.
Definition file.c:175
VALUE rb_cString
String class.
Definition string.c:85
Encoding relates APIs.
static char * rb_enc_left_char_head(const char *s, const char *p, const char *e, rb_encoding *enc)
Queries the left boundary of a character.
Definition encoding.h:683
VALUE rb_str_conv_enc(VALUE str, rb_encoding *from, rb_encoding *to)
Encoding conversion main routine.
Definition string.c:1393
VALUE rb_enc_str_new_cstr(const char *ptr, rb_encoding *enc)
Identical to rb_enc_str_new(), except it assumes the passed pointer is a pointer to a C string.
Definition string.c:1189
int rb_enc_str_asciionly_p(VALUE str)
Queries if the passed string is "ASCII only".
Definition string.c:1002
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1124
VALUE rb_assoc_new(VALUE car, VALUE cdr)
Identical to rb_ary_new_from_values(), except it expects exactly two parameters.
VALUE rb_ary_new_from_values(rb_len_t n, const VALUE *elts)
Identical to rb_ary_new_from_args(), except how objects are passed.
#define INTEGER_PACK_NATIVE_BYTE_ORDER
Means either INTEGER_PACK_MSBYTE_FIRST or INTEGER_PACK_LSBYTE_FIRST, depending on the host processor'...
Definition bignum.h:550
#define INTEGER_PACK_2COMP
Uses 2's complement representation.
Definition bignum.h:553
#define INTEGER_PACK_LSWORD_FIRST
Stores/interprets the least significant word as the first word.
Definition bignum.h:532
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
void rb_update_max_fd(int fd)
Informs the interpreter that the passed fd can be the max.
Definition io.c:274
int rb_cloexec_open(const char *pathname, int flags, mode_t mode)
Opens a file that closes on exec.
Definition io.c:337
void rb_str_modify_expand(VALUE str, rb_len_t capa)
Identical to rb_str_modify(), except it additionally expands the capacity of the receiver.
Definition string.c:2841
VALUE rb_str_subseq(VALUE str, rb_len_t beg, rb_len_t len)
Identical to rb_str_substr(), except the numbers are interpreted as byte offsets instead of character...
Definition string.c:3291
VALUE rb_str_new_shared(VALUE str)
Identical to rb_str_new_cstr(), except it takes a Ruby's string instead of C's.
Definition string.c:1563
VALUE rb_str_plus(VALUE lhs, VALUE rhs)
Generates a new string, concatenating the former to the latter.
Definition string.c:2581
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3938
VALUE rb_str_tmp_new(rb_len_t len)
Allocates a "temporary" string.
Definition string.c:1811
#define rb_str_new(str, len)
Allocates an instance of rb_cString.
Definition string.h:1523
#define rb_str_buf_cat
Just another name of rb_str_cat.
Definition string.h:1706
#define rb_usascii_str_new(str, len)
Identical to rb_str_new, except it generates a string of "US ASCII" encoding.
Definition string.h:1557
size_t rb_str_capacity(VALUE str)
Queries the capacity of the given string.
Definition string.c:1037
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1569
void rb_str_set_len(VALUE str, rb_len_t len)
Overwrites the length of the string.
Definition string.c:3525
VALUE rb_str_dup(VALUE str)
Duplicates a string.
Definition string.c:2052
VALUE rb_str_ellipsize(VALUE str, rb_len_t len)
Shortens str and adds three dots, an ellipsis, if it is longer than len characters.
Definition string.c:13236
VALUE rb_str_cat(VALUE dst, const char *src, rb_len_t srclen)
Destructively appends the passed contents to the string.
Definition string.c:3706
VALUE rb_str_replace(VALUE dst, VALUE src)
Replaces the contents of the former object with the stringised contents of the latter.
Definition string.c:6700
VALUE rb_str_buf_append(VALUE dst, VALUE src)
Identical to rb_str_cat_cstr(), except it takes Ruby's string instead of C's.
Definition string.c:3904
VALUE rb_str_inspect(VALUE str)
Generates a "readable" version of the receiver.
Definition string.c:8237
int rb_str_cmp(VALUE lhs, VALUE rhs)
Compares two strings, as in strcmp(3).
Definition string.c:4354
VALUE rb_str_buf_new(rb_len_t capa)
Allocates a "string buffer".
Definition string.c:1783
#define rb_str_dup_frozen
Just another name of rb_str_new_frozen.
Definition string.h:656
#define rb_utf8_str_new(str, len)
Identical to rb_str_new, except it generates a string of "UTF-8" encoding.
Definition string.h:1574
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1539
VALUE rb_exec_recursive(VALUE(*f)(VALUE g, VALUE h, int r), VALUE g, VALUE h)
"Recursion" API entry point.
void rb_thread_wait_for(struct timeval time)
Identical to rb_thread_sleep(), except it takes struct timeval instead.
Definition thread.c:1625
VALUE rb_time_nano_new(time_t sec, long nsec)
Identical to rb_time_new(), except it accepts the time in nanoseconds resolution.
Definition time.c:2837
struct timespec rb_time_timespec(VALUE time)
Identical to rb_time_timeval(), except for return type.
Definition time.c:3007
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
#define GetOpenFile
This is an old name of RB_IO_POINTER.
Definition io.h:442
#define FMODE_WRITABLE
The IO is opened for writing.
Definition io.h:165
#define RB_IO_POINTER(obj, fp)
Queries the underlying IO pointer.
Definition io.h:436
void rb_io_check_closed(rb_io_t *fptr)
This badly named function asserts that the passed IO is open.
Definition io.c:804
int len
Length of the buffer.
Definition io.h:8
char * ruby_getcwd(void)
This is our own version of getcwd(3) that uses ruby_xmalloc() instead of system malloc (benefits our ...
Definition util.c:575
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
#define NUM2MODET
Converts a C's mode_t into an instance of rb_cInteger.
Definition mode_t.h:28
#define MODET2NUM
Converts an instance of rb_cNumeric into C's mode_t.
Definition mode_t.h:33
VALUE rb_rescue(type *q, VALUE w, type *e, VALUE r)
An equivalent of rescue clause.
Defines RBIMPL_ATTR_NONSTRING.
#define RBIMPL_ATTR_NONSTRING()
Wraps (or simulates) __attribute__((nonstring))
Definition nonstring.h:36
#define OFFT2NUM
Converts a C's off_t into an instance of rb_cInteger.
Definition off_t.h:33
#define NUM2OFFT
Converts an instance of rb_cNumeric into C's off_t.
Definition off_t.h:44
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
#define RARRAY_AREF(a, i)
References an object in an array.
Definition rarray.h:421
#define StringValue(v)
Ensures that the parameter object is a String.
Definition rstring.h:66
#define StringValuePtr(v)
Identical to StringValue, except it returns a char*.
Definition rstring.h:76
#define RSTRING_GETMEM(str, ptrvar, lenvar)
Convenient macro to obtain the contents and length at once.
Definition rstring.h:450
#define StringValueCStr(v)
Identical to StringValuePtr, except it additionally checks for the contents for viability as a C stri...
Definition rstring.h:89
#define RUBY_TYPED_DEFAULT_FREE
This is a value you can set to rb_data_type_struct::dfree.
Definition rtypeddata.h:81
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:773
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
const char * rb_obj_classname(VALUE obj)
Queries the name of the class of the passed object.
Definition variable.c:531
#define FilePathValue(v)
Ensures that the parameter object is a path.
Definition ruby.h:90
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
#define FilePathStringValue(v)
This macro actually does the same thing as FilePathValue now.
Definition ruby.h:105
#define RTEST
This is an old name of RB_TEST.
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:242
Ruby's IO, metadata and buffers.
Definition io.h:295
enum rb_io_mode mode
mode flags: FMODE_XXXs
Definition io.h:310
int fd
file descriptor.
Definition io.h:306
VALUE pathv
pathname for file
Definition io.h:322
#define UIDT2NUM
Converts a C's uid_t into an instance of rb_cInteger.
Definition uid_t.h:28
#define NUM2UIDT
Converts an instance of rb_cNumeric into C's uid_t.
Definition uid_t.h:33
long rb_len_t
A signed integer type for lengths and indices of objects such as String and Array.
Definition value.h:142
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376
#define RBIMPL_WARNING_IGNORED(flag)
Suppresses a warning.
#define RBIMPL_WARNING_PUSH()
Pushes compiler warning state.
#define RBIMPL_WARNING_POP()
Pops compiler warning state.