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