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