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