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