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