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