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