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