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