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