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