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