Ruby 4.1.0dev (2026-05-15 revision a8bcae043f931d9b79f1cb1fe2c021985d07b984)
file.c (a8bcae043f931d9b79f1cb1fe2c021985d07b984)
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_FREE_IMMEDIATELY | 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 * call-seq:
1563 * File.lstat(filepath) -> stat
1564 *
1565 * Like File::stat, but does not follow the last symbolic link;
1566 * instead, returns a File::Stat object for the link itself.
1567 *
1568 * File.symlink('t.txt', 'symlink')
1569 * File.stat('symlink').size # => 47
1570 * File.lstat('symlink').size # => 5
1571 *
1572 */
1573
1574static VALUE
1575rb_file_s_lstat(VALUE klass, VALUE fname)
1576{
1577#ifdef HAVE_LSTAT
1578 rb_io_stat_data st;
1579
1580 FilePathValue(fname);
1581 fname = rb_str_encode_ospath(fname);
1582 if (lstatx_without_gvl(StringValueCStr(fname), &st, STATX_ALL) == -1) {
1583 rb_sys_fail_path(fname);
1584 }
1585 return rb_statx_new(&st);
1586#else
1587 return rb_file_s_stat(klass, fname);
1588#endif
1589}
1590
1591/*
1592 * call-seq:
1593 * lstat -> stat
1594 *
1595 * Like File#stat, but does not follow the last symbolic link;
1596 * instead, returns a File::Stat object for the link itself:
1597 *
1598 * File.symlink('t.txt', 'symlink')
1599 * f = File.new('symlink')
1600 * f.stat.size # => 47
1601 * f.lstat.size # => 11
1602 *
1603 */
1604
1605static VALUE
1606rb_file_lstat(VALUE obj)
1607{
1608#ifdef HAVE_LSTAT
1609 rb_io_t *fptr;
1610 rb_io_stat_data st;
1611 VALUE path;
1612
1613 GetOpenFile(obj, fptr);
1614 if (NIL_P(fptr->pathv)) return Qnil;
1615 path = rb_str_encode_ospath(fptr->pathv);
1616 if (lstatx_without_gvl(RSTRING_PTR(path), &st, STATX_ALL) == -1) {
1617 rb_sys_fail_path(fptr->pathv);
1618 }
1619 return rb_statx_new(&st);
1620#else
1621 return rb_io_stat(obj);
1622#endif
1623}
1624
1625static int
1626rb_group_member(GETGROUPS_T gid)
1627{
1628#if defined(_WIN32) || !defined(HAVE_GETGROUPS)
1629 return FALSE;
1630#else
1631 int rv = FALSE;
1632 int groups;
1633 VALUE v = 0;
1634 GETGROUPS_T *gary;
1635 int anum = -1;
1636
1637 if (getgid() == gid || getegid() == gid)
1638 return TRUE;
1639
1640 groups = getgroups(0, NULL);
1641 gary = ALLOCV_N(GETGROUPS_T, v, groups);
1642 anum = getgroups(groups, gary);
1643 while (--anum >= 0) {
1644 if (gary[anum] == gid) {
1645 rv = TRUE;
1646 break;
1647 }
1648 }
1649 if (v)
1650 ALLOCV_END(v);
1651
1652 return rv;
1653#endif /* defined(_WIN32) || !defined(HAVE_GETGROUPS) */
1654}
1655
1656#ifndef S_IXUGO
1657# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH)
1658#endif
1659
1660#if defined(S_IXGRP) && !defined(_WIN32) && !defined(__CYGWIN__)
1661#define USE_GETEUID 1
1662#endif
1663
1664#ifndef HAVE_EACCESS
1665int
1666eaccess(const char *path, int mode)
1667{
1668#ifdef USE_GETEUID
1669 struct stat st;
1670 rb_uid_t euid;
1671
1672 euid = geteuid();
1673
1674 /* no setuid nor setgid. run shortcut. */
1675 if (getuid() == euid && getgid() == getegid())
1676 return access(path, mode);
1677
1678 if (STAT(path, &st) < 0)
1679 return -1;
1680
1681 if (euid == 0) {
1682 /* Root can read or write any file. */
1683 if (!(mode & X_OK))
1684 return 0;
1685
1686 /* Root can execute any file that has any one of the execute
1687 bits set. */
1688 if (st.st_mode & S_IXUGO)
1689 return 0;
1690
1691 return -1;
1692 }
1693
1694 if (st.st_uid == euid) /* owner */
1695 mode <<= 6;
1696 else if (rb_group_member(st.st_gid))
1697 mode <<= 3;
1698
1699 if ((int)(st.st_mode & mode) == mode) return 0;
1700
1701 return -1;
1702#else
1703 return access(path, mode);
1704#endif /* USE_GETEUID */
1705}
1706#endif /* HAVE_EACCESS */
1707
1709 const char *path;
1710 int mode;
1711};
1712
1713static void *
1714nogvl_eaccess(void *ptr)
1715{
1716 struct access_arg *aa = ptr;
1717
1718 return (void *)(VALUE)eaccess(aa->path, aa->mode);
1719}
1720
1721static int
1722rb_eaccess(VALUE fname, int mode)
1723{
1724 struct access_arg aa;
1725
1726 FilePathValue(fname);
1727 fname = rb_str_encode_ospath(fname);
1728 aa.path = StringValueCStr(fname);
1729 aa.mode = mode;
1730
1731 return IO_WITHOUT_GVL_INT(nogvl_eaccess, &aa);
1732}
1733
1734static void *
1735nogvl_access(void *ptr)
1736{
1737 struct access_arg *aa = ptr;
1738
1739 return (void *)(VALUE)access(aa->path, aa->mode);
1740}
1741
1742static int
1743rb_access(VALUE fname, int mode)
1744{
1745 struct access_arg aa;
1746
1747 FilePathValue(fname);
1748 fname = rb_str_encode_ospath(fname);
1749 aa.path = StringValueCStr(fname);
1750 aa.mode = mode;
1751
1752 return IO_WITHOUT_GVL_INT(nogvl_access, &aa);
1753}
1754
1755/*
1756 * Document-class: FileTest
1757 *
1758 * FileTest implements file test operations similar to those used in
1759 * File::Stat. It exists as a standalone module, and its methods are
1760 * also insinuated into the File class. (Note that this is not done
1761 * by inclusion: the interpreter cheats).
1762 *
1763 */
1764
1765/*
1766 * call-seq:
1767 * File.directory?(path) -> true or false
1768 *
1769 * With string +object+ given, returns +true+ if +path+ is a string path
1770 * leading to a directory, or to a symbolic link to a directory; +false+ otherwise:
1771 *
1772 * File.directory?('.') # => true
1773 * File.directory?('foo') # => false
1774 * File.symlink('.', 'dirlink') # => 0
1775 * File.directory?('dirlink') # => true
1776 * File.symlink('t,txt', 'filelink') # => 0
1777 * File.directory?('filelink') # => false
1778 *
1779 * Argument +path+ can be an IO object.
1780 *
1781 */
1782
1783VALUE
1784rb_file_directory_p(VALUE obj, VALUE fname)
1785{
1786#ifndef S_ISDIR
1787# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
1788#endif
1789
1790 struct stat st;
1791
1792 if (rb_stat(fname, &st) < 0) return Qfalse;
1793 if (S_ISDIR(st.st_mode)) return Qtrue;
1794 return Qfalse;
1795}
1796
1797/*
1798 * call-seq:
1799 * File.pipe?(filepath) -> true or false
1800 *
1801 * Returns +true+ if +filepath+ points to a pipe, +false+ otherwise:
1802 *
1803 * File.mkfifo('tmp/fifo')
1804 * File.pipe?('tmp/fifo') # => true
1805 * File.pipe?('t.txt') # => false
1806 *
1807 */
1808
1809static VALUE
1810rb_file_pipe_p(VALUE obj, VALUE fname)
1811{
1812#ifdef S_IFIFO
1813# ifndef S_ISFIFO
1814# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
1815# endif
1816
1817 struct stat st;
1818
1819 if (rb_stat(fname, &st) < 0) return Qfalse;
1820 if (S_ISFIFO(st.st_mode)) return Qtrue;
1821
1822#endif
1823 return Qfalse;
1824}
1825
1826/*
1827 * call-seq:
1828 * File.symlink?(filepath) -> true or false
1829 *
1830 * Returns +true+ if +filepath+ points to a symbolic link, +false+ otherwise:
1831 *
1832 * symlink = File.symlink('t.txt', 'symlink')
1833 * File.symlink?('symlink') # => true
1834 * File.symlink?('t.txt') # => false
1835 *
1836 */
1837
1838static VALUE
1839rb_file_symlink_p(VALUE obj, VALUE fname)
1840{
1841#ifndef S_ISLNK
1842# ifdef _S_ISLNK
1843# define S_ISLNK(m) _S_ISLNK(m)
1844# else
1845# ifdef _S_IFLNK
1846# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK)
1847# else
1848# ifdef S_IFLNK
1849# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
1850# endif
1851# endif
1852# endif
1853#endif
1854
1855#ifdef S_ISLNK
1856 struct stat st;
1857
1858 FilePathValue(fname);
1859 fname = rb_str_encode_ospath(fname);
1860 if (lstat_without_gvl(StringValueCStr(fname), &st) < 0) return Qfalse;
1861 if (S_ISLNK(st.st_mode)) return Qtrue;
1862#endif
1863
1864 return Qfalse;
1865}
1866
1867/*
1868 * call-seq:
1869 * File.socket?(filepath) -> true or false
1870 *
1871 * Returns +true+ if +filepath+ points to a socket, +false+ otherwise:
1872 *
1873 * require 'socket'
1874 * File.socket?(Socket.new(:INET, :STREAM)) # => true
1875 * File.socket?(File.new('t.txt')) # => false
1876 *
1877 */
1878
1879static VALUE
1880rb_file_socket_p(VALUE obj, VALUE fname)
1881{
1882#ifndef S_ISSOCK
1883# ifdef _S_ISSOCK
1884# define S_ISSOCK(m) _S_ISSOCK(m)
1885# else
1886# ifdef _S_IFSOCK
1887# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK)
1888# else
1889# ifdef S_IFSOCK
1890# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
1891# endif
1892# endif
1893# endif
1894#endif
1895
1896#ifdef S_ISSOCK
1897 struct stat st;
1898
1899 if (rb_stat(fname, &st) < 0) return Qfalse;
1900 if (S_ISSOCK(st.st_mode)) return Qtrue;
1901#endif
1902
1903 return Qfalse;
1904}
1905
1906/*
1907 * call-seq:
1908 * File.blockdev?(filepath) -> true or false
1909 *
1910 * Returns +true+ if +filepath+ points to a block device, +false+ otherwise:
1911 *
1912 * File.blockdev?('/dev/sda1') # => true
1913 * File.blockdev?(File.new('t.tmp')) # => false
1914 *
1915 */
1916
1917static VALUE
1918rb_file_blockdev_p(VALUE obj, VALUE fname)
1919{
1920#ifndef S_ISBLK
1921# ifdef S_IFBLK
1922# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
1923# else
1924# define S_ISBLK(m) (0) /* anytime false */
1925# endif
1926#endif
1927
1928#ifdef S_ISBLK
1929 struct stat st;
1930
1931 if (rb_stat(fname, &st) < 0) return Qfalse;
1932 if (S_ISBLK(st.st_mode)) return Qtrue;
1933
1934#endif
1935 return Qfalse;
1936}
1937
1938/*
1939 * call-seq:
1940 * File.chardev?(filepath) -> true or false
1941 *
1942 * Returns +true+ if +filepath+ points to a character device, +false+ otherwise.
1943 *
1944 * File.chardev?($stdin) # => true
1945 * File.chardev?('t.txt') # => false
1946 *
1947 */
1948static VALUE
1949rb_file_chardev_p(VALUE obj, VALUE fname)
1950{
1951#ifndef S_ISCHR
1952# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
1953#endif
1954
1955 struct stat st;
1956
1957 if (rb_stat(fname, &st) < 0) return Qfalse;
1958 if (S_ISCHR(st.st_mode)) return Qtrue;
1959
1960 return Qfalse;
1961}
1962
1963/*
1964 * call-seq:
1965 * File.exist?(file_name) -> true or false
1966 *
1967 * Return <code>true</code> if the named file exists.
1968 *
1969 * _file_name_ can be an IO object.
1970 *
1971 * "file exists" means that stat() or fstat() system call is successful.
1972 */
1973
1974static VALUE
1975rb_file_exist_p(VALUE obj, VALUE fname)
1976{
1977 struct stat st;
1978
1979 if (rb_stat(fname, &st) < 0) return Qfalse;
1980 return Qtrue;
1981}
1982
1983/*
1984 * call-seq:
1985 * File.readable?(file_name) -> true or false
1986 *
1987 * Returns <code>true</code> if the named file is readable by the effective
1988 * user and group id of this process. See eaccess(3).
1989 *
1990 * Note that some OS-level security features may cause this to return true
1991 * even though the file is not readable by the effective user/group.
1992 */
1993
1994static VALUE
1995rb_file_readable_p(VALUE obj, VALUE fname)
1996{
1997 return RBOOL(rb_eaccess(fname, R_OK) >= 0);
1998}
1999
2000/*
2001 * call-seq:
2002 * File.readable_real?(file_name) -> true or false
2003 *
2004 * Returns <code>true</code> if the named file is readable by the real
2005 * user and group id of this process. See access(3).
2006 *
2007 * Note that some OS-level security features may cause this to return true
2008 * even though the file is not readable by the real user/group.
2009 */
2010
2011static VALUE
2012rb_file_readable_real_p(VALUE obj, VALUE fname)
2013{
2014 return RBOOL(rb_access(fname, R_OK) >= 0);
2015}
2016
2017#ifndef S_IRUGO
2018# define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH)
2019#endif
2020
2021#ifndef S_IWUGO
2022# define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH)
2023#endif
2024
2025/*
2026 * call-seq:
2027 * File.world_readable?(file_name) -> integer or nil
2028 *
2029 * If <i>file_name</i> is readable by others, returns an integer
2030 * representing the file permission bits of <i>file_name</i>. Returns
2031 * <code>nil</code> otherwise. The meaning of the bits is platform
2032 * dependent; on Unix systems, see <code>stat(2)</code>.
2033 *
2034 * _file_name_ can be an IO object.
2035 *
2036 * File.world_readable?("/etc/passwd") #=> 420
2037 * m = File.world_readable?("/etc/passwd")
2038 * sprintf("%o", m) #=> "644"
2039 */
2040
2041static VALUE
2042rb_file_world_readable_p(VALUE obj, VALUE fname)
2043{
2044#ifdef S_IROTH
2045 struct stat st;
2046
2047 if (rb_stat(fname, &st) < 0) return Qnil;
2048 if ((st.st_mode & (S_IROTH)) == S_IROTH) {
2049 return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
2050 }
2051#endif
2052 return Qnil;
2053}
2054
2055/*
2056 * call-seq:
2057 * File.writable?(file_name) -> true or false
2058 *
2059 * Returns <code>true</code> if the named file is writable by the effective
2060 * user and group id of this process. See eaccess(3).
2061 *
2062 * Note that some OS-level security features may cause this to return true
2063 * even though the file is not writable by the effective user/group.
2064 */
2065
2066static VALUE
2067rb_file_writable_p(VALUE obj, VALUE fname)
2068{
2069 return RBOOL(rb_eaccess(fname, W_OK) >= 0);
2070}
2071
2072/*
2073 * call-seq:
2074 * File.writable_real?(file_name) -> true or false
2075 *
2076 * Returns <code>true</code> if the named file is writable by the real
2077 * user and group id of this process. See access(3).
2078 *
2079 * Note that some OS-level security features may cause this to return true
2080 * even though the file is not writable by the real user/group.
2081 */
2082
2083static VALUE
2084rb_file_writable_real_p(VALUE obj, VALUE fname)
2085{
2086 return RBOOL(rb_access(fname, W_OK) >= 0);
2087}
2088
2089/*
2090 * call-seq:
2091 * File.world_writable?(file_name) -> integer or nil
2092 *
2093 * If <i>file_name</i> is writable by others, returns an integer
2094 * representing the file permission bits of <i>file_name</i>. Returns
2095 * <code>nil</code> otherwise. The meaning of the bits is platform
2096 * dependent; on Unix systems, see <code>stat(2)</code>.
2097 *
2098 * _file_name_ can be an IO object.
2099 *
2100 * File.world_writable?("/tmp") #=> 511
2101 * m = File.world_writable?("/tmp")
2102 * sprintf("%o", m) #=> "777"
2103 */
2104
2105static VALUE
2106rb_file_world_writable_p(VALUE obj, VALUE fname)
2107{
2108#ifdef S_IWOTH
2109 struct stat st;
2110
2111 if (rb_stat(fname, &st) < 0) return Qnil;
2112 if ((st.st_mode & (S_IWOTH)) == S_IWOTH) {
2113 return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
2114 }
2115#endif
2116 return Qnil;
2117}
2118
2119/*
2120 * call-seq:
2121 * File.executable?(file_name) -> true or false
2122 *
2123 * Returns <code>true</code> if the named file is executable by the effective
2124 * user and group id of this process. See eaccess(3).
2125 *
2126 * Windows does not support execute permissions separately from read
2127 * permissions. On Windows, a file is only considered executable if it ends in
2128 * .bat, .cmd, .com, or .exe.
2129 *
2130 * Note that some OS-level security features may cause this to return true
2131 * even though the file is not executable by the effective user/group.
2132 */
2133
2134static VALUE
2135rb_file_executable_p(VALUE obj, VALUE fname)
2136{
2137 return RBOOL(rb_eaccess(fname, X_OK) >= 0);
2138}
2139
2140/*
2141 * call-seq:
2142 * File.executable_real?(file_name) -> true or false
2143 *
2144 * Returns <code>true</code> if the named file is executable by the real
2145 * user and group id of this process. See access(3).
2146 *
2147 * Windows does not support execute permissions separately from read
2148 * permissions. On Windows, a file is only considered executable if it ends in
2149 * .bat, .cmd, .com, or .exe.
2150 *
2151 * Note that some OS-level security features may cause this to return true
2152 * even though the file is not executable by the real user/group.
2153 */
2154
2155static VALUE
2156rb_file_executable_real_p(VALUE obj, VALUE fname)
2157{
2158 return RBOOL(rb_access(fname, X_OK) >= 0);
2159}
2160
2161#ifndef S_ISREG
2162# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
2163#endif
2164
2165/*
2166 * call-seq:
2167 * File.file?(file) -> true or false
2168 *
2169 * Returns +true+ if the named +file+ exists and is a regular file.
2170 *
2171 * +file+ can be an IO object.
2172 *
2173 * If the +file+ argument is a symbolic link, it will resolve the symbolic link
2174 * and use the file referenced by the link.
2175 */
2176
2177static VALUE
2178rb_file_file_p(VALUE obj, VALUE fname)
2179{
2180 struct stat st;
2181
2182 if (rb_stat(fname, &st) < 0) return Qfalse;
2183 return RBOOL(S_ISREG(st.st_mode));
2184}
2185
2186/*
2187 * call-seq:
2188 * File.zero?(file_name) -> true or false
2189 *
2190 * Returns <code>true</code> if the named file exists and has
2191 * a zero size.
2192 *
2193 * _file_name_ can be an IO object.
2194 */
2195
2196static VALUE
2197rb_file_zero_p(VALUE obj, VALUE fname)
2198{
2199 struct stat st;
2200
2201 if (rb_stat(fname, &st) < 0) return Qfalse;
2202 return RBOOL(st.st_size == 0);
2203}
2204
2205/*
2206 * call-seq:
2207 * File.size?(file_name) -> Integer or nil
2208 *
2209 * Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the
2210 * file otherwise.
2211 *
2212 * _file_name_ can be an IO object.
2213 */
2214
2215static VALUE
2216rb_file_size_p(VALUE obj, VALUE fname)
2217{
2218 struct stat st;
2219
2220 if (rb_stat(fname, &st) < 0) return Qnil;
2221 if (st.st_size == 0) return Qnil;
2222 return OFFT2NUM(st.st_size);
2223}
2224
2225/*
2226 * call-seq:
2227 * File.owned?(file_name) -> true or false
2228 *
2229 * Returns <code>true</code> if the named file exists and the
2230 * effective user id of the calling process is the owner of
2231 * the file.
2232 *
2233 * _file_name_ can be an IO object.
2234 */
2235
2236static VALUE
2237rb_file_owned_p(VALUE obj, VALUE fname)
2238{
2239 struct stat st;
2240
2241 if (rb_stat(fname, &st) < 0) return Qfalse;
2242 return RBOOL(st.st_uid == geteuid());
2243}
2244
2245static VALUE
2246rb_file_rowned_p(VALUE obj, VALUE fname)
2247{
2248 struct stat st;
2249
2250 if (rb_stat(fname, &st) < 0) return Qfalse;
2251 return RBOOL(st.st_uid == getuid());
2252}
2253
2254/*
2255 * call-seq:
2256 * File.grpowned?(file_name) -> true or false
2257 *
2258 * Returns <code>true</code> if the named file exists and the
2259 * effective group id of the calling process is the owner of
2260 * the file. Returns <code>false</code> on Windows.
2261 *
2262 * _file_name_ can be an IO object.
2263 */
2264
2265static VALUE
2266rb_file_grpowned_p(VALUE obj, VALUE fname)
2267{
2268#ifndef _WIN32
2269 struct stat st;
2270
2271 if (rb_stat(fname, &st) < 0) return Qfalse;
2272 if (rb_group_member(st.st_gid)) return Qtrue;
2273#endif
2274 return Qfalse;
2275}
2276
2277#if defined(S_ISUID) || defined(S_ISGID) || defined(S_ISVTX)
2278static VALUE
2279check3rdbyte(VALUE fname, int mode)
2280{
2281 struct stat st;
2282
2283 if (rb_stat(fname, &st) < 0) return Qfalse;
2284 return RBOOL(st.st_mode & mode);
2285}
2286#endif
2287
2288/*
2289 * call-seq:
2290 * File.setuid?(file_name) -> true or false
2291 *
2292 * Returns <code>true</code> if the named file has the setuid bit set.
2293 *
2294 * _file_name_ can be an IO object.
2295 */
2296
2297static VALUE
2298rb_file_suid_p(VALUE obj, VALUE fname)
2299{
2300#ifdef S_ISUID
2301 return check3rdbyte(fname, S_ISUID);
2302#else
2303 return Qfalse;
2304#endif
2305}
2306
2307/*
2308 * call-seq:
2309 * File.setgid?(file_name) -> true or false
2310 *
2311 * Returns <code>true</code> if the named file has the setgid bit set.
2312 *
2313 * _file_name_ can be an IO object.
2314 */
2315
2316static VALUE
2317rb_file_sgid_p(VALUE obj, VALUE fname)
2318{
2319#ifdef S_ISGID
2320 return check3rdbyte(fname, S_ISGID);
2321#else
2322 return Qfalse;
2323#endif
2324}
2325
2326/*
2327 * call-seq:
2328 * File.sticky?(file_name) -> true or false
2329 *
2330 * Returns <code>true</code> if the named file has the sticky bit set.
2331 *
2332 * _file_name_ can be an IO object.
2333 */
2334
2335static VALUE
2336rb_file_sticky_p(VALUE obj, VALUE fname)
2337{
2338#ifdef S_ISVTX
2339 return check3rdbyte(fname, S_ISVTX);
2340#else
2341 return Qfalse;
2342#endif
2343}
2344
2345/*
2346 * call-seq:
2347 * File.identical?(file_1, file_2) -> true or false
2348 *
2349 * Returns <code>true</code> if the named files are identical.
2350 *
2351 * _file_1_ and _file_2_ can be an IO object.
2352 *
2353 * open("a", "w") {}
2354 * p File.identical?("a", "a") #=> true
2355 * p File.identical?("a", "./a") #=> true
2356 * File.link("a", "b")
2357 * p File.identical?("a", "b") #=> true
2358 * File.symlink("a", "c")
2359 * p File.identical?("a", "c") #=> true
2360 * open("d", "w") {}
2361 * p File.identical?("a", "d") #=> false
2362 */
2363
2364static VALUE
2365rb_file_identical_p(VALUE obj, VALUE fname1, VALUE fname2)
2366{
2367#ifndef _WIN32
2368 struct stat st1, st2;
2369
2370 if (rb_stat(fname1, &st1) < 0) return Qfalse;
2371 if (rb_stat(fname2, &st2) < 0) return Qfalse;
2372 if (st1.st_dev != st2.st_dev) return Qfalse;
2373 if (st1.st_ino != st2.st_ino) return Qfalse;
2374 return Qtrue;
2375#else
2376 extern VALUE rb_w32_file_identical_p(VALUE, VALUE);
2377 return rb_w32_file_identical_p(fname1, fname2);
2378#endif
2379}
2380
2381/*
2382 * call-seq:
2383 * File.size(file_name) -> integer
2384 *
2385 * Returns the size of <code>file_name</code>.
2386 *
2387 * _file_name_ can be an IO object.
2388 */
2389
2390static VALUE
2391rb_file_s_size(VALUE klass, VALUE fname)
2392{
2393 struct stat st;
2394
2395 if (rb_stat(fname, &st) < 0) {
2396 int e = errno;
2397 FilePathValue(fname);
2398 rb_syserr_fail_path(e, fname);
2399 }
2400 return OFFT2NUM(st.st_size);
2401}
2402
2403static VALUE
2404rb_file_ftype(mode_t mode)
2405{
2406 const char *t;
2407
2408 if (S_ISREG(mode)) {
2409 t = "file";
2410 }
2411 else if (S_ISDIR(mode)) {
2412 t = "directory";
2413 }
2414 else if (S_ISCHR(mode)) {
2415 t = "characterSpecial";
2416 }
2417#ifdef S_ISBLK
2418 else if (S_ISBLK(mode)) {
2419 t = "blockSpecial";
2420 }
2421#endif
2422#ifdef S_ISFIFO
2423 else if (S_ISFIFO(mode)) {
2424 t = "fifo";
2425 }
2426#endif
2427#ifdef S_ISLNK
2428 else if (S_ISLNK(mode)) {
2429 t = "link";
2430 }
2431#endif
2432#ifdef S_ISSOCK
2433 else if (S_ISSOCK(mode)) {
2434 t = "socket";
2435 }
2436#endif
2437 else {
2438 t = "unknown";
2439 }
2440
2441 return rb_fstring_cstr(t);
2442}
2443
2444/*
2445 * call-seq:
2446 * File.ftype(file_name) -> string
2447 *
2448 * Identifies the type of the named file; the return string is one of
2449 * ``<code>file</code>'', ``<code>directory</code>'',
2450 * ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'',
2451 * ``<code>fifo</code>'', ``<code>link</code>'',
2452 * ``<code>socket</code>'', or ``<code>unknown</code>''.
2453 *
2454 * File.ftype("testfile") #=> "file"
2455 * File.ftype("/dev/tty") #=> "characterSpecial"
2456 * File.ftype("/tmp/.X11-unix/X0") #=> "socket"
2457 */
2458
2459static VALUE
2460rb_file_s_ftype(VALUE klass, VALUE fname)
2461{
2462 struct stat st;
2463
2464 FilePathValue(fname);
2465 fname = rb_str_encode_ospath(fname);
2466 if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) {
2467 rb_sys_fail_path(fname);
2468 }
2469
2470 return rb_file_ftype(st.st_mode);
2471}
2472
2473/*
2474 * call-seq:
2475 * File.atime(object) -> new_time
2476 *
2477 * Returns a new Time object containing the time of the most recent
2478 * access (read or write) to the object,
2479 * which may be a string filepath or dirpath, or a File or Dir object:
2480 *
2481 * filepath = 't.tmp'
2482 * File.exist?(filepath) # => false
2483 * File.atime(filepath) # Raises Errno::ENOENT.
2484 * File.write(filepath, 'foo')
2485 * File.atime(filepath) # => 2026-03-31 16:39:37.9290772 -0500
2486 * File.write(filepath, 'bar')
2487 * File.atime(filepath) # => 2026-03-31 16:39:57.7710876 -0500
2488 *
2489 * File.atime('.') # => 2026-03-31 16:47:49.0970483 -0500
2490 * File.atime(File.new('README.md')) # => 2026-03-31 11:15:27.8215934 -0500
2491 * File.atime(Dir.new('.')) # => 2026-03-31 12:39:45.5910591 -0500
2492 *
2493 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
2494 */
2495
2496static VALUE
2497rb_file_s_atime(VALUE klass, VALUE fname)
2498{
2499 struct stat st;
2500
2501 if (rb_stat(fname, &st) < 0) {
2502 int e = errno;
2503 FilePathValue(fname);
2504 rb_syserr_fail_path(e, fname);
2505 }
2506 return stat_time(stat_atimespec(&st));
2507}
2508
2509/*
2510 * call-seq:
2511 * atime -> new_time
2512 *
2513 * Returns a new Time object containing the time of the most recent
2514 * access (read or write) to the file represented by +self+:
2515 *
2516 * filepath = 't.tmp'
2517 * file = File.new(filepath, 'a+')
2518 * file.atime # => 2026-03-31 17:11:27.7285397 -0500
2519 * file.write('foo')
2520 * file.atime # => 2026-03-31 17:11:27.7285397 -0500 # Unchanged; not yet written.
2521 * file.flush
2522 * file.atime # => 2026-03-31 17:12:11.3408054 -0500 # Changed; now written.
2523 * file.close
2524 * File.delete(filename)
2525 *
2526 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
2527 */
2528
2529static VALUE
2530rb_file_atime(VALUE obj)
2531{
2532 rb_io_t *fptr;
2533 struct stat st;
2534
2535 GetOpenFile(obj, fptr);
2536 if (fstat(fptr->fd, &st) == -1) {
2537 rb_sys_fail_path(fptr->pathv);
2538 }
2539 return stat_time(stat_atimespec(&st));
2540}
2541
2542/*
2543 * call-seq:
2544 * File.mtime(file_name) -> time
2545 *
2546 * Returns the modification time for the named file as a Time object.
2547 *
2548 * _file_name_ can be an IO object.
2549 *
2550 * File.mtime("testfile") #=> Tue Apr 08 12:58:04 CDT 2003
2551 *
2552 */
2553
2554static VALUE
2555rb_file_s_mtime(VALUE klass, VALUE fname)
2556{
2557 struct stat st;
2558
2559 if (rb_stat(fname, &st) < 0) {
2560 int e = errno;
2561 FilePathValue(fname);
2562 rb_syserr_fail_path(e, fname);
2563 }
2564 return stat_time(stat_mtimespec(&st));
2565}
2566
2567/*
2568 * call-seq:
2569 * file.mtime -> time
2570 *
2571 * Returns the modification time for <i>file</i>.
2572 *
2573 * File.new("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003
2574 *
2575 */
2576
2577static VALUE
2578rb_file_mtime(VALUE obj)
2579{
2580 rb_io_t *fptr;
2581 struct stat st;
2582
2583 GetOpenFile(obj, fptr);
2584 if (fstat(fptr->fd, &st) == -1) {
2585 rb_sys_fail_path(fptr->pathv);
2586 }
2587 return stat_time(stat_mtimespec(&st));
2588}
2589
2590/*
2591 * call-seq:
2592 * File.ctime(file_name) -> time
2593 *
2594 * Returns the change time for the named file (the time at which
2595 * directory information about the file was changed, not the file
2596 * itself).
2597 *
2598 * _file_name_ can be an IO object.
2599 *
2600 * Note that on Windows (NTFS), returns creation time (birth time).
2601 *
2602 * File.ctime("testfile") #=> Wed Apr 09 08:53:13 CDT 2003
2603 *
2604 */
2605
2606static VALUE
2607rb_file_s_ctime(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_ctimespec(&st));
2617}
2618
2619/*
2620 * call-seq:
2621 * file.ctime -> time
2622 *
2623 * Returns the change time for <i>file</i> (that is, the time directory
2624 * information about the file was changed, not the file itself).
2625 *
2626 * Note that on Windows (NTFS), returns creation time (birth time).
2627 *
2628 * File.new("testfile").ctime #=> Wed Apr 09 08:53:14 CDT 2003
2629 *
2630 */
2631
2632static VALUE
2633rb_file_ctime(VALUE obj)
2634{
2635 rb_io_t *fptr;
2636 struct stat st;
2637
2638 GetOpenFile(obj, fptr);
2639 if (fstat(fptr->fd, &st) == -1) {
2640 rb_sys_fail_path(fptr->pathv);
2641 }
2642 return stat_time(stat_ctimespec(&st));
2643}
2644
2645#if defined(HAVE_STAT_BIRTHTIME)
2646/*
2647 * call-seq:
2648 * File.birthtime(entry_path) -> new_time
2649 *
2650 * Returns a new Time object containing the create time
2651 * of the entry at the given +path+:
2652 *
2653 * path = 't.tmp'
2654 * File.birthtime(path) # Raises Errno::ENOENT: No such file or directory
2655 * File.write(path, 'foo')
2656 * File.birthtime(path) # => 2026-04-14 11:10:43.2891695 -0500
2657 * File.write(path, 'bar')
2658 * File.birthtime(path) # => 2026-04-14 11:10:43.2891695 -0500
2659 * File.delete(path)
2660 * File.birthtime(path) # Raises Errno::ENOENT: No such file or directory
2661 *
2662 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
2663 */
2664
2665VALUE
2666rb_file_s_birthtime(VALUE klass, VALUE fname)
2667{
2668 rb_io_stat_data st;
2669
2670 if (rb_statx(fname, &st, STATX_BTIME) < 0) {
2671 int e = errno;
2672 FilePathValue(fname);
2673 rb_syserr_fail_path(e, fname);
2674 }
2675 return statx_birthtime(&st);
2676}
2677#else
2678# define rb_file_s_birthtime rb_f_notimplement
2679#endif
2680
2681#if defined(HAVE_STAT_BIRTHTIME)
2682/*
2683 * call-seq:
2684 * birthtime -> new_time
2685 *
2686 * Returns a new Time object containing the create time for +self+:
2687 *
2688 * filepath = 't.tmp'
2689 * File.write(filepath, 'foo')
2690 * file = File.new(filepath)
2691 * file.birthtime # => 2026-04-14 15:53:45.002656 -0500
2692 * File.write(filepath, 'bar')
2693 * file.birthtime # => 2026-04-14 15:53:45.002656 -0500
2694 * file.close
2695 * File.delete(filepath)
2696 * file.birthtime # Raises IOError: closed stream
2697 *
2698 * See {File System Timestamps}[rdoc-ref:file/timestamps.md].
2699 */
2700
2701static VALUE
2702rb_file_birthtime(VALUE obj)
2703{
2704 rb_io_t *fptr;
2705 rb_io_stat_data st;
2706
2707 GetOpenFile(obj, fptr);
2708 if (fstatx_without_gvl(fptr, &st, STATX_BTIME) == -1) {
2709 rb_sys_fail_path(fptr->pathv);
2710 }
2711 return statx_birthtime(&st);
2712}
2713#else
2714# define rb_file_birthtime rb_f_notimplement
2715#endif
2716
2717rb_off_t
2718rb_file_size(VALUE file)
2719{
2720 if (RB_TYPE_P(file, T_FILE)) {
2721 rb_io_t *fptr;
2722 struct stat st;
2723
2724 RB_IO_POINTER(file, fptr);
2725 if (fptr->mode & FMODE_WRITABLE) {
2726 rb_io_flush_raw(file, 0);
2727 }
2728
2729 if (fstat(fptr->fd, &st) == -1) {
2730 rb_sys_fail_path(fptr->pathv);
2731 }
2732
2733 return st.st_size;
2734 }
2735 else {
2736 return NUM2OFFT(rb_funcall(file, idSize, 0));
2737 }
2738}
2739
2740/*
2741 * call-seq:
2742 * file.size -> integer
2743 *
2744 * Returns the size of <i>file</i> in bytes.
2745 *
2746 * File.new("testfile").size #=> 66
2747 *
2748 */
2749
2750static VALUE
2751file_size(VALUE self)
2752{
2753 return OFFT2NUM(rb_file_size(self));
2754}
2755
2757 const char *path;
2758 mode_t mode;
2759};
2760
2761static void *
2762nogvl_chmod(void *ptr)
2763{
2764 struct nogvl_chmod_data *data = ptr;
2765 int ret = chmod(data->path, data->mode);
2766 return (void *)(VALUE)ret;
2767}
2768
2769static int
2770rb_chmod(const char *path, mode_t mode)
2771{
2772 struct nogvl_chmod_data data = {
2773 .path = path,
2774 .mode = mode,
2775 };
2776 return IO_WITHOUT_GVL_INT(nogvl_chmod, &data);
2777}
2778
2779static int
2780chmod_internal(const char *path, void *mode)
2781{
2782 return chmod(path, *(mode_t *)mode);
2783}
2784
2785/*
2786 * call-seq:
2787 * File.chmod(mode_int, file_name, ... ) -> integer
2788 *
2789 * Changes permission bits on the named file(s) to the bit pattern
2790 * represented by <i>mode_int</i>. Actual effects are operating system
2791 * dependent (see the beginning of this section). On Unix systems, see
2792 * <code>chmod(2)</code> for details. Returns the number of files
2793 * processed.
2794 *
2795 * File.chmod(0644, "testfile", "out") #=> 2
2796 */
2797
2798static VALUE
2799rb_file_s_chmod(int argc, VALUE *argv, VALUE _)
2800{
2801 mode_t mode;
2802
2803 apply2args(1);
2804 mode = NUM2MODET(*argv++);
2805
2806 return apply2files(chmod_internal, argc, argv, &mode);
2807}
2808
2809#ifdef HAVE_FCHMOD
2810struct nogvl_fchmod_data {
2811 int fd;
2812 mode_t mode;
2813};
2814
2815static VALUE
2816io_blocking_fchmod(void *ptr)
2817{
2818 struct nogvl_fchmod_data *data = ptr;
2819 int ret = fchmod(data->fd, data->mode);
2820 return (VALUE)ret;
2821}
2822
2823static int
2824rb_fchmod(struct rb_io* io, mode_t mode)
2825{
2826 (void)rb_chmod; /* suppress unused-function warning when HAVE_FCHMOD */
2827 struct nogvl_fchmod_data data = {.fd = io->fd, .mode = mode};
2828 return (int)rb_thread_io_blocking_region(io, io_blocking_fchmod, &data);
2829}
2830#endif
2831
2832/*
2833 * call-seq:
2834 * file.chmod(mode_int) -> 0
2835 *
2836 * Changes permission bits on <i>file</i> to the bit pattern
2837 * represented by <i>mode_int</i>. Actual effects are platform
2838 * dependent; on Unix systems, see <code>chmod(2)</code> for details.
2839 * Follows symbolic links. Also see File#lchmod.
2840 *
2841 * f = File.new("out", "w");
2842 * f.chmod(0644) #=> 0
2843 */
2844
2845static VALUE
2846rb_file_chmod(VALUE obj, VALUE vmode)
2847{
2848 rb_io_t *fptr;
2849 mode_t mode;
2850#if !defined HAVE_FCHMOD || !HAVE_FCHMOD
2851 VALUE path;
2852#endif
2853
2854 mode = NUM2MODET(vmode);
2855
2856 GetOpenFile(obj, fptr);
2857#ifdef HAVE_FCHMOD
2858 if (rb_fchmod(fptr, mode) == -1) {
2859 if (HAVE_FCHMOD || errno != ENOSYS)
2860 rb_sys_fail_path(fptr->pathv);
2861 }
2862 else {
2863 if (!HAVE_FCHMOD) return INT2FIX(0);
2864 }
2865#endif
2866#if !defined HAVE_FCHMOD || !HAVE_FCHMOD
2867 if (NIL_P(fptr->pathv)) return Qnil;
2868 path = rb_str_encode_ospath(fptr->pathv);
2869 if (rb_chmod(RSTRING_PTR(path), mode) == -1)
2870 rb_sys_fail_path(fptr->pathv);
2871#endif
2872
2873 return INT2FIX(0);
2874}
2875
2876#if defined(HAVE_LCHMOD)
2877static int
2878lchmod_internal(const char *path, void *mode)
2879{
2880 return lchmod(path, *(mode_t *)mode);
2881}
2882
2883/*
2884 * call-seq:
2885 * File.lchmod(mode_int, file_name, ...) -> integer
2886 *
2887 * Equivalent to File::chmod, but does not follow symbolic links (so
2888 * it will change the permissions associated with the link, not the
2889 * file referenced by the link). Often not available.
2890 *
2891 */
2892
2893static VALUE
2894rb_file_s_lchmod(int argc, VALUE *argv, VALUE _)
2895{
2896 mode_t mode;
2897
2898 apply2args(1);
2899 mode = NUM2MODET(*argv++);
2900
2901 return apply2files(lchmod_internal, argc, argv, &mode);
2902}
2903#else
2904#define rb_file_s_lchmod rb_f_notimplement
2905#endif
2906
2907static inline rb_uid_t
2908to_uid(VALUE u)
2909{
2910 if (NIL_P(u)) {
2911 return (rb_uid_t)-1;
2912 }
2913 return NUM2UIDT(u);
2914}
2915
2916static inline rb_gid_t
2917to_gid(VALUE g)
2918{
2919 if (NIL_P(g)) {
2920 return (rb_gid_t)-1;
2921 }
2922 return NUM2GIDT(g);
2923}
2924
2926 rb_uid_t owner;
2927 rb_gid_t group;
2928};
2929
2930static int
2931chown_internal(const char *path, void *arg)
2932{
2933 struct chown_args *args = arg;
2934 return chown(path, args->owner, args->group);
2935}
2936
2937/*
2938 * call-seq:
2939 * File.chown(owner_int, group_int, file_name, ...) -> integer
2940 *
2941 * Changes the owner and group of the named file(s) to the given
2942 * numeric owner and group id's. Only a process with superuser
2943 * privileges may change the owner of a file. The current owner of a
2944 * file may change the file's group to any group to which the owner
2945 * belongs. A <code>nil</code> or -1 owner or group id is ignored.
2946 * Returns the number of files processed.
2947 *
2948 * File.chown(nil, 100, "testfile")
2949 *
2950 */
2951
2952static VALUE
2953rb_file_s_chown(int argc, VALUE *argv, VALUE _)
2954{
2955 struct chown_args arg;
2956
2957 apply2args(2);
2958 arg.owner = to_uid(*argv++);
2959 arg.group = to_gid(*argv++);
2960
2961 return apply2files(chown_internal, argc, argv, &arg);
2962}
2963
2965 union {
2966 const char *path;
2967 int fd;
2968 } as;
2969 struct chown_args new;
2970};
2971
2972static void *
2973nogvl_chown(void *ptr)
2974{
2975 struct nogvl_chown_data *data = ptr;
2976 return (void *)(VALUE)chown(data->as.path, data->new.owner, data->new.group);
2977}
2978
2979static int
2980rb_chown(const char *path, rb_uid_t owner, rb_gid_t group)
2981{
2982 struct nogvl_chown_data data = {
2983 .as = {.path = path},
2984 .new = {.owner = owner, .group = group},
2985 };
2986 return IO_WITHOUT_GVL_INT(nogvl_chown, &data);
2987}
2988
2989#ifdef HAVE_FCHOWN
2990static void *
2991nogvl_fchown(void *ptr)
2992{
2993 struct nogvl_chown_data *data = ptr;
2994 return (void *)(VALUE)fchown(data->as.fd, data->new.owner, data->new.group);
2995}
2996
2997static int
2998rb_fchown(int fd, rb_uid_t owner, rb_gid_t group)
2999{
3000 (void)rb_chown; /* suppress unused-function warning when HAVE_FCHMOD */
3001 struct nogvl_chown_data data = {
3002 .as = {.fd = fd},
3003 .new = {.owner = owner, .group = group},
3004 };
3005 return IO_WITHOUT_GVL_INT(nogvl_fchown, &data);
3006}
3007#endif
3008
3009/*
3010 * call-seq:
3011 * file.chown(owner_int, group_int ) -> 0
3012 *
3013 * Changes the owner and group of <i>file</i> to the given numeric
3014 * owner and group id's. Only a process with superuser privileges may
3015 * change the owner of a file. The current owner of a file may change
3016 * the file's group to any group to which the owner belongs. A
3017 * <code>nil</code> or -1 owner or group id is ignored. Follows
3018 * symbolic links. See also File#lchown.
3019 *
3020 * File.new("testfile").chown(502, 1000)
3021 *
3022 */
3023
3024static VALUE
3025rb_file_chown(VALUE obj, VALUE owner, VALUE group)
3026{
3027 rb_io_t *fptr;
3028 rb_uid_t o;
3029 rb_gid_t g;
3030#ifndef HAVE_FCHOWN
3031 VALUE path;
3032#endif
3033
3034 o = to_uid(owner);
3035 g = to_gid(group);
3036 GetOpenFile(obj, fptr);
3037#ifndef HAVE_FCHOWN
3038 if (NIL_P(fptr->pathv)) return Qnil;
3039 path = rb_str_encode_ospath(fptr->pathv);
3040 if (rb_chown(RSTRING_PTR(path), o, g) == -1)
3041 rb_sys_fail_path(fptr->pathv);
3042#else
3043 if (rb_fchown(fptr->fd, o, g) == -1)
3044 rb_sys_fail_path(fptr->pathv);
3045#endif
3046
3047 return INT2FIX(0);
3048}
3049
3050#if defined(HAVE_LCHOWN)
3051static int
3052lchown_internal(const char *path, void *arg)
3053{
3054 struct chown_args *args = arg;
3055 return lchown(path, args->owner, args->group);
3056}
3057
3058/*
3059 * call-seq:
3060 * File.lchown(owner_int, group_int, file_name,..) -> integer
3061 *
3062 * Equivalent to File::chown, but does not follow symbolic
3063 * links (so it will change the owner associated with the link, not the
3064 * file referenced by the link). Often not available. Returns number
3065 * of files in the argument list.
3066 *
3067 */
3068
3069static VALUE
3070rb_file_s_lchown(int argc, VALUE *argv, VALUE _)
3071{
3072 struct chown_args arg;
3073
3074 apply2args(2);
3075 arg.owner = to_uid(*argv++);
3076 arg.group = to_gid(*argv++);
3077
3078 return apply2files(lchown_internal, argc, argv, &arg);
3079}
3080#else
3081#define rb_file_s_lchown rb_f_notimplement
3082#endif
3083
3085 const struct timespec* tsp;
3086 VALUE atime, mtime;
3087 int follow; /* Whether to act on symlinks (1) or their referent (0) */
3088};
3089
3090#ifdef UTIME_EINVAL
3091NORETURN(static void utime_failed(struct apply_arg *));
3092
3093static void
3094utime_failed(struct apply_arg *aa)
3095{
3096 int e = aa->errnum;
3097 VALUE path = aa->fn[aa->i].path;
3098 struct utime_args *ua = aa->arg;
3099
3100 if (ua->tsp && e == EINVAL) {
3101 VALUE e[2], a = Qnil, m = Qnil;
3102 int d = 0;
3103 VALUE atime = ua->atime;
3104 VALUE mtime = ua->mtime;
3105
3106 if (!NIL_P(atime)) {
3107 a = rb_inspect(atime);
3108 }
3109 if (!NIL_P(mtime) && mtime != atime && !rb_equal(atime, mtime)) {
3110 m = rb_inspect(mtime);
3111 }
3112 if (NIL_P(a)) e[0] = m;
3113 else if (NIL_P(m) || rb_str_cmp(a, m) == 0) e[0] = a;
3114 else {
3115 e[0] = rb_str_plus(a, rb_str_new_cstr(" or "));
3116 rb_str_append(e[0], m);
3117 d = 1;
3118 }
3119 if (!NIL_P(e[0])) {
3120 if (path) {
3121 if (!d) e[0] = rb_str_dup(e[0]);
3122 rb_str_append(rb_str_cat2(e[0], " for "), path);
3123 }
3124 e[1] = INT2FIX(EINVAL);
3126 }
3127 }
3128 rb_syserr_fail_path(e, path);
3129}
3130#endif /* UTIME_EINVAL */
3131
3132#if defined(HAVE_UTIMES)
3133
3134# if !defined(HAVE_UTIMENSAT)
3135/* utimensat() is not found, runtime check is not needed */
3136# elif defined(__APPLE__) && \
3137 (!defined(MAC_OS_X_VERSION_13_0) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_13_0))
3138
3139# if __has_attribute(availability) && __has_warning("-Wunguarded-availability-new")
3140typedef int utimensat_func(int, const char *, const struct timespec [2], int);
3141
3143RBIMPL_WARNING_IGNORED(-Wunguarded-availability-new)
3144static inline utimensat_func *
3145rb_utimensat(void)
3146{
3147 return &utimensat;
3148}
3150
3151# define utimensat rb_utimensat()
3152# else /* __API_AVAILABLE macro does nothing on gcc */
3153__attribute__((weak)) int utimensat(int, const char *, const struct timespec [2], int);
3154# endif /* utimesat availability */
3155# endif /* __APPLE__ && < MAC_OS_X_VERSION_13_0 */
3156
3157static int
3158utime_internal(const char *path, void *arg)
3159{
3160 struct utime_args *v = arg;
3161 const struct timespec *tsp = v->tsp;
3162 struct timeval tvbuf[2], *tvp = NULL;
3163
3164#if defined(HAVE_UTIMENSAT)
3165# if defined(__APPLE__)
3166 const int try_utimensat = utimensat != NULL;
3167 const int try_utimensat_follow = utimensat != NULL;
3168# else /* !__APPLE__ */
3169# define TRY_UTIMENSAT 1
3170 static int try_utimensat = 1;
3171# ifdef AT_SYMLINK_NOFOLLOW
3172 static int try_utimensat_follow = 1;
3173# else
3174 const int try_utimensat_follow = 0;
3175# endif
3176# endif /* __APPLE__ */
3177 int flags = 0;
3178
3179 if (v->follow ? try_utimensat_follow : try_utimensat) {
3180# ifdef AT_SYMLINK_NOFOLLOW
3181 if (v->follow) {
3182 flags = AT_SYMLINK_NOFOLLOW;
3183 }
3184# endif
3185
3186 int result = utimensat(AT_FDCWD, path, tsp, flags);
3187# ifdef TRY_UTIMENSAT
3188 if (result < 0 && errno == ENOSYS) {
3189# ifdef AT_SYMLINK_NOFOLLOW
3190 try_utimensat_follow = 0;
3191# endif /* AT_SYMLINK_NOFOLLOW */
3192 if (!v->follow)
3193 try_utimensat = 0;
3194 }
3195 else
3196# endif /* TRY_UTIMESAT */
3197 return result;
3198 }
3199#endif /* defined(HAVE_UTIMENSAT) */
3200
3201 if (tsp) {
3202 tvbuf[0].tv_sec = tsp[0].tv_sec;
3203 tvbuf[0].tv_usec = (int)(tsp[0].tv_nsec / 1000);
3204 tvbuf[1].tv_sec = tsp[1].tv_sec;
3205 tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
3206 tvp = tvbuf;
3207 }
3208#ifdef HAVE_LUTIMES
3209 if (v->follow) return lutimes(path, tvp);
3210#endif
3211 return utimes(path, tvp);
3212}
3213
3214#else /* !defined(HAVE_UTIMES) */
3215
3216#if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H
3217struct utimbuf {
3218 long actime;
3219 long modtime;
3220};
3221#endif
3222
3223static int
3224utime_internal(const char *path, void *arg)
3225{
3226 struct utime_args *v = arg;
3227 const stat_timestamp *tsp = v->tsp;
3228 struct utimbuf utbuf, *utp = NULL;
3229 if (tsp) {
3230 utbuf.actime = tsp[0].tv_sec;
3231 utbuf.modtime = tsp[1].tv_sec;
3232 utp = &utbuf;
3233 }
3234 return utime(path, utp);
3235}
3236#endif /* !defined(HAVE_UTIMES) */
3237
3238static VALUE
3239utime_internal_i(int argc, VALUE *argv, int follow)
3240{
3241 struct utime_args args;
3242 struct timespec tss[2], *tsp = NULL;
3243
3244 apply2args(2);
3245 args.atime = *argv++;
3246 args.mtime = *argv++;
3247
3248 args.follow = follow;
3249
3250 if (!NIL_P(args.atime) || !NIL_P(args.mtime)) {
3251 tsp = tss;
3252 tsp[0] = rb_time_timespec(args.atime);
3253 if (args.atime == args.mtime)
3254 tsp[1] = tsp[0];
3255 else
3256 tsp[1] = rb_time_timespec(args.mtime);
3257 }
3258 args.tsp = tsp;
3259
3260 return apply2files(utime_internal, argc, argv, &args);
3261}
3262
3263/*
3264 * call-seq:
3265 * File.utime(atime, mtime, file_name, ...) -> integer
3266 *
3267 * Sets the access and modification times of each named file to the
3268 * first two arguments. If a file is a symlink, this method acts upon
3269 * its referent rather than the link itself; for the inverse
3270 * behavior see File.lutime. Returns the number of file
3271 * names in the argument list.
3272 */
3273
3274static VALUE
3275rb_file_s_utime(int argc, VALUE *argv, VALUE _)
3276{
3277 return utime_internal_i(argc, argv, FALSE);
3278}
3279
3280#if defined(HAVE_UTIMES) && (defined(HAVE_LUTIMES) || (defined(HAVE_UTIMENSAT) && defined(AT_SYMLINK_NOFOLLOW)))
3281
3282/*
3283 * call-seq:
3284 * File.lutime(atime, mtime, file_name, ...) -> integer
3285 *
3286 * Sets the access and modification times of each named file to the
3287 * first two arguments. If a file is a symlink, this method acts upon
3288 * the link itself as opposed to its referent; for the inverse
3289 * behavior, see File.utime. Returns the number of file
3290 * names in the argument list.
3291 */
3292
3293static VALUE
3294rb_file_s_lutime(int argc, VALUE *argv, VALUE _)
3295{
3296 return utime_internal_i(argc, argv, TRUE);
3297}
3298#else
3299#define rb_file_s_lutime rb_f_notimplement
3300#endif
3301
3302#ifdef RUBY_FUNCTION_NAME_STRING
3303# define syserr_fail2(e, s1, s2) syserr_fail2_in(RUBY_FUNCTION_NAME_STRING, e, s1, s2)
3304#else
3305# define syserr_fail2_in(func, e, s1, s2) syserr_fail2(e, s1, s2)
3306#endif
3307#define sys_fail2(s1, s2) syserr_fail2(errno, s1, s2)
3308NORETURN(static void syserr_fail2_in(const char *,int,VALUE,VALUE));
3309static void
3310syserr_fail2_in(const char *func, int e, VALUE s1, VALUE s2)
3311{
3312 VALUE str;
3313#ifdef MAX_PATH
3314 const int max_pathlen = MAX_PATH;
3315#else
3316 const int max_pathlen = MAXPATHLEN;
3317#endif
3318
3319 if (e == EEXIST) {
3320 rb_syserr_fail_path(e, rb_str_ellipsize(s2, max_pathlen));
3321 }
3322 str = rb_str_new_cstr("(");
3323 rb_str_append(str, rb_str_ellipsize(s1, max_pathlen));
3324 rb_str_cat2(str, ", ");
3325 rb_str_append(str, rb_str_ellipsize(s2, max_pathlen));
3326 rb_str_cat2(str, ")");
3327#ifdef RUBY_FUNCTION_NAME_STRING
3328 rb_syserr_fail_path_in(func, e, str);
3329#else
3330 rb_syserr_fail_path(e, str);
3331#endif
3332}
3333
3334#ifdef HAVE_LINK
3335/*
3336 * call-seq:
3337 * File.link(old_name, new_name) -> 0
3338 *
3339 * Creates a new name for an existing file using a hard link. Will not
3340 * overwrite <i>new_name</i> if it already exists (raising a subclass
3341 * of SystemCallError). Not available on all platforms.
3342 *
3343 * File.link("testfile", ".testfile") #=> 0
3344 * IO.readlines(".testfile")[0] #=> "This is line one\n"
3345 */
3346
3347static VALUE
3348rb_file_s_link(VALUE klass, VALUE from, VALUE to)
3349{
3350 FilePathValue(from);
3351 FilePathValue(to);
3352 from = rb_str_encode_ospath(from);
3353 to = rb_str_encode_ospath(to);
3354
3355 if (link(StringValueCStr(from), StringValueCStr(to)) < 0) {
3356 sys_fail2(from, to);
3357 }
3358 return INT2FIX(0);
3359}
3360#else
3361#define rb_file_s_link rb_f_notimplement
3362#endif
3363
3364#ifdef HAVE_SYMLINK
3365/*
3366 * call-seq:
3367 * File.symlink(old_name, new_name) -> 0
3368 *
3369 * Creates a symbolic link called <i>new_name</i> for the existing file
3370 * <i>old_name</i>. Raises a NotImplemented exception on
3371 * platforms that do not support symbolic links.
3372 *
3373 * File.symlink("testfile", "link2test") #=> 0
3374 *
3375 */
3376
3377static VALUE
3378rb_file_s_symlink(VALUE klass, VALUE from, VALUE to)
3379{
3380 FilePathValue(from);
3381 FilePathValue(to);
3382 from = rb_str_encode_ospath(from);
3383 to = rb_str_encode_ospath(to);
3384
3385 if (symlink(StringValueCStr(from), StringValueCStr(to)) < 0) {
3386 sys_fail2(from, to);
3387 }
3388 return INT2FIX(0);
3389}
3390#else
3391#define rb_file_s_symlink rb_f_notimplement
3392#endif
3393
3394#ifdef HAVE_READLINK
3395/*
3396 * call-seq:
3397 * File.readlink(link_name) -> file_name
3398 *
3399 * Returns the name of the file referenced by the given link.
3400 * Not available on all platforms.
3401 *
3402 * File.symlink("testfile", "link2test") #=> 0
3403 * File.readlink("link2test") #=> "testfile"
3404 */
3405
3406static VALUE
3407rb_file_s_readlink(VALUE klass, VALUE path)
3408{
3409 return rb_readlink(path, rb_filesystem_encoding());
3410}
3411
3412struct readlink_arg {
3413 const char *path;
3414 char *buf;
3415 size_t size;
3416};
3417
3418static void *
3419nogvl_readlink(void *ptr)
3420{
3421 struct readlink_arg *ra = ptr;
3422
3423 return (void *)(VALUE)readlink(ra->path, ra->buf, ra->size);
3424}
3425
3426static ssize_t
3427readlink_without_gvl(VALUE path, VALUE buf, size_t size)
3428{
3429 struct readlink_arg ra;
3430
3431 ra.path = RSTRING_PTR(path);
3432 ra.buf = RSTRING_PTR(buf);
3433 ra.size = size;
3434
3435 return (ssize_t)IO_WITHOUT_GVL(nogvl_readlink, &ra);
3436}
3437
3438VALUE
3439rb_readlink(VALUE path, rb_encoding *enc)
3440{
3441 int size = 100;
3442 ssize_t rv;
3443 VALUE v;
3444
3445 FilePathValue(path);
3446 path = rb_str_encode_ospath(path);
3447 v = rb_enc_str_new(0, size, enc);
3448 while ((rv = readlink_without_gvl(path, v, size)) == size
3449#ifdef _AIX
3450 || (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */
3451#endif
3452 ) {
3453 rb_str_modify_expand(v, size);
3454 size *= 2;
3455 rb_str_set_len(v, size);
3456 }
3457 if (rv < 0) {
3458 int e = errno;
3459 rb_str_resize(v, 0);
3460 rb_syserr_fail_path(e, path);
3461 }
3462 rb_str_resize(v, rv);
3463
3464 return v;
3465}
3466#else
3467#define rb_file_s_readlink rb_f_notimplement
3468#endif
3469
3470static int
3471unlink_internal(const char *path, void *arg)
3472{
3473 return unlink(path);
3474}
3475
3476/*
3477 * call-seq:
3478 * File.delete(file_name, ...) -> integer
3479 * File.unlink(file_name, ...) -> integer
3480 *
3481 * Deletes the named files, returning the number of names
3482 * passed as arguments. Raises an exception on any error.
3483 * Since the underlying implementation relies on the
3484 * <code>unlink(2)</code> system call, the type of
3485 * exception raised depends on its error type (see
3486 * https://man7.org/linux/man-pages/man2/unlink.2.html) and has the form of
3487 * e.g. Errno::ENOENT.
3488 *
3489 * See also Dir::rmdir.
3490 */
3491
3492static VALUE
3493rb_file_s_unlink(int argc, VALUE *argv, VALUE klass)
3494{
3495 return apply2files(unlink_internal, argc, argv, 0);
3496}
3497
3499 const char *src;
3500 const char *dst;
3501};
3502
3503static void *
3504no_gvl_rename(void *ptr)
3505{
3506 struct rename_args *ra = ptr;
3507
3508 return (void *)(VALUE)rename(ra->src, ra->dst);
3509}
3510
3511/*
3512 * call-seq:
3513 * File.rename(old_name, new_name) -> 0
3514 *
3515 * Renames the given file to the new name. Raises a SystemCallError
3516 * if the file cannot be renamed.
3517 *
3518 * File.rename("afile", "afile.bak") #=> 0
3519 */
3520
3521static VALUE
3522rb_file_s_rename(VALUE klass, VALUE from, VALUE to)
3523{
3524 struct rename_args ra;
3525 VALUE f, t;
3526
3527 FilePathValue(from);
3528 FilePathValue(to);
3529 f = rb_str_encode_ospath(from);
3530 t = rb_str_encode_ospath(to);
3531 ra.src = StringValueCStr(f);
3532 ra.dst = StringValueCStr(t);
3533#if defined __CYGWIN__
3534 errno = 0;
3535#endif
3536 if (IO_WITHOUT_GVL_INT(no_gvl_rename, &ra) < 0) {
3537 int e = errno;
3538#if defined DOSISH
3539 switch (e) {
3540 case EEXIST:
3541 if (chmod(ra.dst, 0666) == 0 &&
3542 unlink(ra.dst) == 0 &&
3543 rename(ra.src, ra.dst) == 0)
3544 return INT2FIX(0);
3545 }
3546#endif
3547 syserr_fail2(e, from, to);
3548 }
3549
3550 return INT2FIX(0);
3551}
3552
3553/*
3554 * call-seq:
3555 * File.umask() -> integer
3556 * File.umask(integer) -> integer
3557 *
3558 * Returns the current umask value for this process. If the optional
3559 * argument is given, set the umask to that value and return the
3560 * previous value. Umask values are <em>subtracted</em> from the
3561 * default permissions, so a umask of <code>0222</code> would make a
3562 * file read-only for everyone.
3563 *
3564 * File.umask(0006) #=> 18
3565 * File.umask #=> 6
3566 */
3567
3568static VALUE
3569rb_file_s_umask(int argc, VALUE *argv, VALUE _)
3570{
3571 mode_t omask = 0;
3572
3573 switch (argc) {
3574 case 0:
3575 omask = umask(0);
3576 umask(omask);
3577 break;
3578 case 1:
3579 omask = umask(NUM2MODET(argv[0]));
3580 break;
3581 default:
3582 rb_error_arity(argc, 0, 1);
3583 }
3584 return MODET2NUM(omask);
3585}
3586
3587#ifdef __CYGWIN__
3588#undef DOSISH
3589#endif
3590#if defined __CYGWIN__ || defined DOSISH
3591#define DOSISH_UNC
3592#define DOSISH_DRIVE_LETTER
3593#define FILE_ALT_SEPARATOR '\\'
3594#endif
3595#ifdef FILE_ALT_SEPARATOR
3596#define isdirsep(x) ((x) == '/' || (x) == FILE_ALT_SEPARATOR)
3597# ifdef DOSISH
3598static const char file_alt_separator[] = {FILE_ALT_SEPARATOR, '\0'};
3599# endif
3600#else
3601#define isdirsep(x) ((x) == '/')
3602#endif
3603
3604#ifndef USE_NTFS
3605# if defined _WIN32
3606# define USE_NTFS 1
3607# else
3608# define USE_NTFS 0
3609# endif
3610#endif
3611
3612#ifndef USE_NTFS_ADS
3613# if USE_NTFS
3614# define USE_NTFS_ADS 1
3615# else
3616# define USE_NTFS_ADS 0
3617# endif
3618#endif
3619
3620#if USE_NTFS
3621#define istrailinggarbage(x) ((x) == '.' || (x) == ' ')
3622#else
3623#define istrailinggarbage(x) 0
3624#endif
3625
3626#if USE_NTFS_ADS
3627# define isADS(x) ((x) == ':')
3628#else
3629# define isADS(x) 0
3630#endif
3631
3632#define enc_mbclen_needed(enc) (!rb_str_encindex_fastpath(rb_enc_to_index(enc)))
3633
3634#define Next(p, e, mb_enc, enc) ((p) + ((mb_enc) ? rb_enc_mbclen((p), (e), (enc)) : 1))
3635#define Inc(p, e, mb_enc, enc) ((p) = Next((p), (e), (mb_enc), (enc)))
3636
3637#if defined(DOSISH_UNC)
3638#define has_unc(buf) (isdirsep((buf)[0]) && isdirsep((buf)[1]))
3639#else
3640#define has_unc(buf) 0
3641#endif
3642
3643#ifdef DOSISH_DRIVE_LETTER
3644static inline int
3645has_drive_letter(const char *buf)
3646{
3647 if (ISALPHA(buf[0]) && buf[1] == ':') {
3648 return 1;
3649 }
3650 else {
3651 return 0;
3652 }
3653}
3654
3655#ifndef _WIN32
3656static char*
3657getcwdofdrv(int drv)
3658{
3659 char drive[4];
3660 char *drvcwd, *oldcwd;
3661
3662 drive[0] = drv;
3663 drive[1] = ':';
3664 drive[2] = '\0';
3665
3666 /* the only way that I know to get the current directory
3667 of a particular drive is to change chdir() to that drive,
3668 so save the old cwd before chdir()
3669 */
3670 oldcwd = ruby_getcwd();
3671 if (chdir(drive) == 0) {
3672 drvcwd = ruby_getcwd();
3673 chdir(oldcwd);
3674 xfree(oldcwd);
3675 }
3676 else {
3677 /* perhaps the drive is not exist. we return only drive letter */
3678 drvcwd = strdup(drive);
3679 }
3680 return drvcwd;
3681}
3682
3683static inline int
3684not_same_drive(VALUE path, int drive)
3685{
3686 const char *p = RSTRING_PTR(path);
3687 if (RSTRING_LEN(path) < 2) return 0;
3688 if (has_drive_letter(p)) {
3689 return TOLOWER(p[0]) != TOLOWER(drive);
3690 }
3691 else {
3692 return has_unc(p);
3693 }
3694}
3695#endif /* _WIN32 */
3696#endif /* DOSISH_DRIVE_LETTER */
3697
3698static inline char *
3699skiproot(const char *path, const char *end)
3700{
3701#ifdef DOSISH_DRIVE_LETTER
3702 if (path + 2 <= end && has_drive_letter(path)) path += 2;
3703#endif
3704 while (path < end && isdirsep(*path)) path++;
3705 return (char *)path;
3706}
3707
3708static inline char *
3709enc_path_next(const char *s, const char *e, bool mb_enc, rb_encoding *enc)
3710{
3711 while (s < e && !isdirsep(*s)) {
3712 Inc(s, e, mb_enc, enc);
3713 }
3714 return (char *)s;
3715}
3716
3717#define nextdirsep rb_enc_path_next
3718char *
3719rb_enc_path_next(const char *s, const char *e, rb_encoding *enc)
3720{
3721 return enc_path_next(s, e, enc_mbclen_needed(enc), enc);
3722}
3723
3724#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
3725#define skipprefix enc_path_skip_prefix
3726#else
3727#define skipprefix(path, end, mb_enc, enc) (path)
3728#endif
3729static inline char *
3730enc_path_skip_prefix(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
3731{
3732#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
3733#ifdef DOSISH_UNC
3734 if (path + 2 <= end && isdirsep(path[0]) && isdirsep(path[1])) {
3735 path += 2;
3736 while (path < end && isdirsep(*path)) path++;
3737 if ((path = enc_path_next(path, end, mb_enc, enc)) < end &&
3738 path + 2 <= end && !isdirsep(path[1])) {
3739 path = enc_path_next(path + 1, end, mb_enc, enc);
3740 }
3741 return (char *)path;
3742 }
3743#endif
3744#ifdef DOSISH_DRIVE_LETTER
3745 if (path + 2 <= end && has_drive_letter(path))
3746 return (char *)(path + 2);
3747#endif
3748#endif /* defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) */
3749 return (char *)path;
3750}
3751
3752char *
3753rb_enc_path_skip_prefix(const char *path, const char *end, rb_encoding *enc)
3754{
3755 return enc_path_skip_prefix(path, end, enc_mbclen_needed(enc), enc);
3756}
3757
3758static inline char *
3759skipprefixroot(const char *path, const char *end, rb_encoding *enc)
3760{
3761#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
3762 char *p = skipprefix(path, end, enc_mbclen_needed(enc), enc);
3763 while (p < end && isdirsep(*p)) p++;
3764 return p;
3765#else
3766 return skiproot(path, end);
3767#endif
3768}
3769
3770char *
3771rb_enc_path_skip_prefix_root(const char *path, const char *end, rb_encoding *enc)
3772{
3773 return skipprefixroot(path, end, enc);
3774}
3775
3776static char *
3777enc_path_last_separator(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
3778{
3779 char *last = NULL;
3780 while (path < end) {
3781 if (isdirsep(*path)) {
3782 const char *tmp = path++;
3783 while (path < end && isdirsep(*path)) path++;
3784 if (path >= end) break;
3785 last = (char *)tmp;
3786 }
3787 else {
3788 Inc(path, end, mb_enc, enc);
3789 }
3790 }
3791 return last;
3792}
3793char *
3794rb_enc_path_last_separator(const char *path, const char *end, rb_encoding *enc)
3795{
3796 return enc_path_last_separator(path, end, enc_mbclen_needed(enc), enc);
3797}
3798
3799static inline char *
3800strrdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
3801{
3802 if (RB_UNLIKELY(mb_enc)) {
3803 return enc_path_last_separator(path, end, mb_enc, enc);
3804 }
3805
3806 const char *cursor = end - 1;
3807
3808 while (isdirsep(cursor[0])) {
3809 cursor--;
3810 }
3811
3812 while (cursor >= path) {
3813 if (isdirsep(cursor[0])) {
3814 while (cursor > path && isdirsep(cursor[-1])) {
3815 cursor--;
3816 }
3817 return (char *)cursor;
3818 }
3819 cursor--;
3820 }
3821 return NULL;
3822}
3823
3824static char *
3825chompdirsep(const char *path, const char *end, bool mb_enc, rb_encoding *enc)
3826{
3827 while (path < end) {
3828 if (isdirsep(*path)) {
3829 const char *last = path++;
3830 while (path < end && isdirsep(*path)) path++;
3831 if (path >= end) return (char *)last;
3832 }
3833 else {
3834 Inc(path, end, mb_enc, enc);
3835 }
3836 }
3837 return (char *)path;
3838}
3839
3840char *
3841rb_enc_path_end(const char *path, const char *end, rb_encoding *enc)
3842{
3843 if (path < end && isdirsep(*path)) path++;
3844 return chompdirsep(path, end, enc_mbclen_needed(enc), enc);
3845}
3846
3847static rb_encoding *
3848fs_enc_check(VALUE path1, VALUE path2)
3849{
3850 rb_encoding *enc = rb_enc_check_str(path1, path2);
3851 int encidx = rb_enc_to_index(enc);
3852 if (encidx == ENCINDEX_US_ASCII) {
3853 encidx = rb_enc_get_index(path1);
3854 if (encidx == ENCINDEX_US_ASCII)
3855 encidx = rb_enc_get_index(path2);
3856 enc = rb_enc_from_index(encidx);
3857 }
3858 return enc;
3859}
3860
3861#if USE_NTFS
3862static char *
3863ntfs_tail(const char *path, const char *end, rb_encoding *enc)
3864{
3865 bool mb_enc = enc_mbclen_needed(enc);
3866 while (path < end && *path == '.') path++;
3867 while (path < end && !isADS(*path)) {
3868 if (istrailinggarbage(*path)) {
3869 const char *last = path++;
3870 while (path < end && istrailinggarbage(*path)) path++;
3871 if (path >= end || isADS(*path)) return (char *)last;
3872 }
3873 else if (isdirsep(*path)) {
3874 const char *last = path++;
3875 while (path < end && isdirsep(*path)) path++;
3876 if (path >= end) return (char *)last;
3877 if (isADS(*path)) path++;
3878 }
3879 else {
3880 Inc(path, end, mb_enc, enc);
3881 }
3882 }
3883 return (char *)path;
3884}
3885#endif /* USE_NTFS */
3886
3887#define BUFCHECK(cond) do {\
3888 bdiff = p - buf;\
3889 if (cond) {\
3890 do {buflen *= 2;} while (cond);\
3891 rb_str_resize(result, buflen);\
3892 buf = RSTRING_PTR(result);\
3893 p = buf + bdiff;\
3894 pend = buf + buflen;\
3895 }\
3896} while (0)
3897
3898#define BUFINIT() (\
3899 p = buf = RSTRING_PTR(result),\
3900 buflen = RSTRING_LEN(result),\
3901 pend = p + buflen)
3902
3903#ifdef __APPLE__
3904# define SKIPPATHSEP(p) ((*(p)) ? 1 : 0)
3905#else
3906# define SKIPPATHSEP(p) 1
3907#endif
3908
3909#define BUFCOPY(srcptr, srclen) do { \
3910 const int skip = SKIPPATHSEP(p); \
3911 rb_str_set_len(result, p-buf+skip); \
3912 BUFCHECK(bdiff + ((srclen)+skip) >= buflen); \
3913 p += skip; \
3914 memcpy(p, (srcptr), (srclen)); \
3915 p += (srclen); \
3916} while (0)
3917
3918#define WITH_ROOTDIFF(stmt) do { \
3919 long rootdiff = root - buf; \
3920 stmt; \
3921 root = buf + rootdiff; \
3922} while (0)
3923
3924static VALUE
3925copy_home_path(VALUE result, const char *dir)
3926{
3927 char *buf;
3928 long dirlen;
3929 int encidx;
3930
3931 dirlen = strlen(dir);
3932 rb_str_resize(result, dirlen);
3933 memcpy(buf = RSTRING_PTR(result), dir, dirlen);
3934 encidx = rb_filesystem_encindex();
3935 rb_enc_associate_index(result, encidx);
3936#if defined FILE_ALT_SEPARATOR
3937 rb_encoding *enc = rb_enc_from_index(encidx);
3938 bool mb_enc = enc_mbclen_needed(enc);
3939 for (char *p = buf, *bend = p + dirlen; p < bend; Inc(p, bend, mb_enc, enc)) {
3940 if (*p == FILE_ALT_SEPARATOR) {
3941 *p = '/';
3942 }
3943 }
3944#endif
3945 return result;
3946}
3947
3948VALUE
3949rb_home_dir_of(VALUE user, VALUE result)
3950{
3951#ifdef HAVE_PWD_H
3952 VALUE dirname = rb_getpwdirnam_for_login(user);
3953 if (dirname == Qnil) {
3954 rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user);
3955 }
3956 const char *dir = RSTRING_PTR(dirname);
3957#else
3958 extern char *getlogin(void);
3959 const char *pwPtr = 0;
3960 const char *login;
3961 # define endpwent() ((void)0)
3962 const char *dir, *username = RSTRING_PTR(user);
3963 rb_encoding *enc = rb_enc_get(user);
3964#if defined _WIN32
3965 rb_encoding *fsenc = rb_utf8_encoding();
3966#else
3967 rb_encoding *fsenc = rb_filesystem_encoding();
3968#endif
3969 if (enc != fsenc) {
3970 dir = username = RSTRING_PTR(rb_str_conv_enc(user, enc, fsenc));
3971 }
3972
3973 if ((login = getlogin()) && strcasecmp(username, login) == 0)
3974 dir = pwPtr = getenv("HOME");
3975 if (!pwPtr) {
3976 rb_raise(rb_eArgError, "user %"PRIsVALUE" doesn't exist", user);
3977 }
3978#endif
3979 copy_home_path(result, dir);
3980 return result;
3981}
3982
3983#ifndef _WIN32 /* this encompasses rb_file_expand_path_internal */
3984VALUE
3985rb_default_home_dir(VALUE result)
3986{
3987 const char *dir = getenv("HOME");
3988
3989#if defined HAVE_PWD_H
3990 if (!dir) {
3991 /* We'll look up the user's default home dir in the password db by
3992 * login name, if possible, and failing that will fall back to looking
3993 * the information up by uid (as would be needed for processes that
3994 * are not a descendant of login(1) or a work-alike).
3995 *
3996 * While the lookup by uid is more likely to succeed (since we always
3997 * have a uid, but may or may not have a login name), we prefer first
3998 * looking up by name to accommodate the possibility of multiple login
3999 * names (each with its own record in the password database, so each
4000 * with a potentially different home directory) being mapped to the
4001 * same uid (as explicitly allowed for by POSIX; see getlogin(3posix)).
4002 */
4003 VALUE login_name = rb_getlogin();
4004
4005# if !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID)
4006 /* This is a corner case, but for backward compatibility reasons we
4007 * want to emit this error if neither the lookup by login name nor
4008 * lookup by getuid() has a chance of succeeding.
4009 */
4010 if (NIL_P(login_name)) {
4011 rb_raise(rb_eArgError, "couldn't find login name -- expanding '~'");
4012 }
4013# endif /* !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID) */
4014
4015 VALUE pw_dir = rb_getpwdirnam_for_login(login_name);
4016 if (NIL_P(pw_dir)) {
4017 pw_dir = rb_getpwdiruid();
4018 if (NIL_P(pw_dir)) {
4019 rb_raise(rb_eArgError, "couldn't find home for uid '%ld'", (long)getuid());
4020 }
4021 }
4022
4023 /* found it */
4024 copy_home_path(result, RSTRING_PTR(pw_dir));
4025 rb_str_resize(pw_dir, 0);
4026 return result;
4027 }
4028#endif /* defined HAVE_PWD_H */
4029 if (!dir) {
4030 rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'");
4031 }
4032 return copy_home_path(result, dir);
4033}
4034
4035static VALUE
4036ospath_new(const char *ptr, long len, rb_encoding *fsenc)
4037{
4038#if NORMALIZE_UTF8PATH
4039 VALUE path = rb_str_normalize_ospath(ptr, len);
4040 rb_enc_associate(path, fsenc);
4041 return path;
4042#else
4043 return rb_enc_str_new(ptr, len, fsenc);
4044#endif
4045}
4046
4047static char *
4048append_fspath(VALUE result, VALUE fname, char *dir, rb_encoding **enc, rb_encoding *fsenc)
4049{
4050 char *buf, *cwdp = dir;
4051 VALUE dirname = Qnil;
4052 size_t dirlen = strlen(dir), buflen = rb_str_capacity(result);
4053
4054 if (NORMALIZE_UTF8PATH || *enc != fsenc) {
4055 dirname = ospath_new(dir, dirlen, fsenc);
4056 if (!rb_enc_compatible(fname, dirname)) {
4057 xfree(dir);
4058 /* rb_enc_check must raise because the two encodings are not
4059 * compatible. */
4060 rb_enc_check(fname, dirname);
4061 rb_bug("unreachable");
4062 }
4063 rb_encoding *direnc = fs_enc_check(fname, dirname);
4064 if (direnc != fsenc) {
4065 dirname = rb_str_conv_enc(dirname, fsenc, direnc);
4066 RSTRING_GETMEM(dirname, cwdp, dirlen);
4067 }
4068 else if (NORMALIZE_UTF8PATH) {
4069 RSTRING_GETMEM(dirname, cwdp, dirlen);
4070 }
4071 *enc = direnc;
4072 }
4073 do {buflen *= 2;} while (dirlen > buflen);
4074 rb_str_resize(result, buflen);
4075 buf = RSTRING_PTR(result);
4076 memcpy(buf, cwdp, dirlen);
4077 xfree(dir);
4078 if (!NIL_P(dirname)) rb_str_resize(dirname, 0);
4079 rb_enc_associate(result, *enc);
4080 return buf + dirlen;
4081}
4082
4083VALUE
4084rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
4085{
4086 const char *s, *b, *fend;
4087 char *buf, *p, *pend, *root;
4088 size_t buflen, bdiff;
4089 rb_encoding *enc, *fsenc = rb_filesystem_encoding();
4090
4091 s = StringValuePtr(fname);
4092 fend = s + RSTRING_LEN(fname);
4093 enc = rb_str_enc_get(fname);
4094 bool mb_enc = enc_mbclen_needed(enc);
4095 if (!mb_enc && RTEST(dname)) {
4096 mb_enc = enc_mbclen_needed(rb_str_enc_get(dname));
4097 }
4098
4099 BUFINIT();
4100
4101 if (s < fend && s[0] == '~' && abs_mode == 0) { /* execute only if NOT absolute_path() */
4102 long userlen = 0;
4103 if (s + 1 == fend || isdirsep(s[1])) {
4104 buf = 0;
4105 b = 0;
4106 rb_str_set_len(result, 0);
4107 if (++s < fend) ++s;
4108 rb_default_home_dir(result);
4109 }
4110 else {
4111 s = nextdirsep(b = s, fend, enc);
4112 b++; /* b[0] is '~' */
4113 userlen = s - b;
4114 BUFCHECK(bdiff + userlen >= buflen);
4115 memcpy(p, b, userlen);
4116 ENC_CODERANGE_CLEAR(result);
4117 rb_str_set_len(result, userlen);
4118 rb_enc_associate(result, enc);
4119 rb_home_dir_of(result, result);
4120 buf = p + 1;
4121 p += userlen;
4122 }
4123 if (!rb_is_absolute_path(RSTRING_PTR(result))) {
4124 if (userlen) {
4125 rb_enc_raise(enc, rb_eArgError, "non-absolute home of %.*s%.0"PRIsVALUE,
4126 (int)userlen, b, fname);
4127 }
4128 else {
4129 rb_raise(rb_eArgError, "non-absolute home");
4130 }
4131 }
4132 BUFINIT();
4133 p = pend;
4134 }
4135#ifdef DOSISH_DRIVE_LETTER
4136 /* skip drive letter */
4137 else if (s + 1 < fend && has_drive_letter(s)) {
4138 if (s + 2 < fend && isdirsep(s[2])) {
4139 /* specified drive letter, and full path */
4140 /* skip drive letter */
4141 BUFCHECK(bdiff + 2 >= buflen);
4142 memcpy(p, s, 2);
4143 p += 2;
4144 s += 2;
4145 rb_enc_copy(result, fname);
4146 }
4147 else {
4148 /* specified drive, but not full path */
4149 int same = 0;
4150 if (!NIL_P(dname) && !not_same_drive(dname, s[0])) {
4151 rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
4152 BUFINIT();
4153 if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) {
4154 /* ok, same drive */
4155 same = 1;
4156 }
4157 }
4158 if (!same) {
4159 char *e = append_fspath(result, fname, getcwdofdrv(*s), &enc, fsenc);
4160 BUFINIT();
4161 p = e;
4162 }
4163 else {
4164 rb_enc_associate(result, enc = fs_enc_check(result, fname));
4165 p = pend;
4166 }
4167 p = chompdirsep(skiproot(buf, p), p, mb_enc, enc);
4168 s += 2;
4169 }
4170 }
4171#endif /* DOSISH_DRIVE_LETTER */
4172 else if (s == fend || !rb_is_absolute_path(s)) {
4173 if (!NIL_P(dname)) {
4174 rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
4175 rb_enc_associate(result, fs_enc_check(result, fname));
4176 BUFINIT();
4177 p = pend;
4178 }
4179 else {
4180 char *e = append_fspath(result, fname, ruby_getcwd(), &enc, fsenc);
4181 BUFINIT();
4182 p = e;
4183 }
4184#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
4185 if (s < fend && isdirsep(*s)) {
4186 /* specified full path, but not drive letter nor UNC */
4187 /* we need to get the drive letter or UNC share name */
4188 p = skipprefix(buf, p, mb_enc, enc);
4189 }
4190 else
4191#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */
4192 p = chompdirsep(skiproot(buf, p), p, mb_enc, enc);
4193 }
4194 else {
4195 size_t len;
4196 b = s;
4197 do s++; while (s < fend && isdirsep(*s));
4198 len = s - b;
4199 p = buf + len;
4200 BUFCHECK(bdiff >= buflen);
4201 memset(buf, '/', len);
4202 rb_str_set_len(result, len);
4203 rb_enc_associate(result, fs_enc_check(result, fname));
4204 }
4205 if (p > buf && p[-1] == '/')
4206 --p;
4207 else {
4208 rb_str_set_len(result, p-buf);
4209 BUFCHECK(bdiff + 1 >= buflen);
4210 *p = '/';
4211 }
4212
4213 rb_str_set_len(result, p-buf+1);
4214 BUFCHECK(bdiff + 1 >= buflen);
4215 p[1] = 0;
4216 root = skipprefix(buf, p+1, mb_enc, enc);
4217
4218 b = s;
4219 while (s < fend) {
4220 switch (*s) {
4221 case '.':
4222 if (b == s++) { /* beginning of path element */
4223 if (s == fend) {
4224 b = s;
4225 break;
4226 }
4227 switch (*s) {
4228 case '.':
4229 if (s+1 == fend || isdirsep(*(s+1))) {
4230 /* We must go back to the parent */
4231 char *n;
4232 *p = '\0';
4233 if (!(n = strrdirsep(root, p, mb_enc, enc))) {
4234 *p = '/';
4235 }
4236 else {
4237 p = n;
4238 }
4239 b = ++s;
4240 }
4241#if USE_NTFS
4242 else {
4243 do ++s; while (s < fend && istrailinggarbage(*s));
4244 }
4245#endif /* USE_NTFS */
4246 break;
4247 case '/':
4248#if defined FILE_ALT_SEPARATOR
4249 case FILE_ALT_SEPARATOR:
4250#endif
4251 b = ++s;
4252 break;
4253 default:
4254 /* ordinary path element, beginning don't move */
4255 break;
4256 }
4257 }
4258#if USE_NTFS
4259 else {
4260 --s;
4261 case ' ': {
4262 const char *e = s;
4263 while (s < fend && istrailinggarbage(*s)) s++;
4264 if (s >= fend) {
4265 s = e;
4266 goto endpath;
4267 }
4268 }
4269 }
4270#endif /* USE_NTFS */
4271 break;
4272 case '/':
4273#if defined FILE_ALT_SEPARATOR
4274 case FILE_ALT_SEPARATOR:
4275#endif
4276 if (s > b) {
4277 WITH_ROOTDIFF(BUFCOPY(b, s-b));
4278 *p = '/';
4279 }
4280 b = ++s;
4281 break;
4282 default:
4283#ifdef __APPLE__
4284 {
4285 int n = ignored_char_p(s, fend, enc);
4286 if (n) {
4287 if (s > b) {
4288 WITH_ROOTDIFF(BUFCOPY(b, s-b));
4289 *p = '\0';
4290 }
4291 b = s += n;
4292 break;
4293 }
4294 }
4295#endif /* __APPLE__ */
4296 Inc(s, fend, mb_enc, enc);
4297 break;
4298 }
4299 }
4300
4301 if (s > b) {
4302#if USE_NTFS
4303# if USE_NTFS_ADS
4304 static const char prime[] = ":$DATA";
4305 enum {prime_len = sizeof(prime) -1};
4306# endif
4307 endpath:
4308# if USE_NTFS_ADS
4309 if (s > b + prime_len && strncasecmp(s - prime_len, prime, prime_len) == 0) {
4310 /* alias of stream */
4311 /* get rid of a bug of x64 VC++ */
4312 if (isADS(*(s - (prime_len+1)))) {
4313 s -= prime_len + 1; /* prime */
4314 }
4315 else if (memchr(b, ':', s - prime_len - b)) {
4316 s -= prime_len; /* alternative */
4317 }
4318 }
4319# endif /* USE_NTFS_ADS */
4320#endif /* USE_NTFS */
4321 BUFCOPY(b, s-b);
4322 rb_str_set_len(result, p-buf);
4323 }
4324 if (p == skiproot(buf, p + !!*p) - 1) p++;
4325
4326#if USE_NTFS
4327 *p = '\0';
4328 if ((s = strrdirsep(b = buf, p, enc)) != 0 && !strpbrk(s, "*?")) {
4329 VALUE tmp, v;
4330 size_t len;
4331 int encidx;
4332 WCHAR *wstr;
4333 WIN32_FIND_DATAW wfd;
4334 HANDLE h;
4335#ifdef __CYGWIN__
4336#ifdef HAVE_CYGWIN_CONV_PATH
4337 char *w32buf = NULL;
4338 const int flags = CCP_POSIX_TO_WIN_A | CCP_RELATIVE;
4339#else
4340 char w32buf[MAXPATHLEN];
4341#endif /* HAVE_CYGWIN_CONV_PATH */
4342 const char *path;
4343 ssize_t bufsize;
4344 int lnk_added = 0, is_symlink = 0;
4345 struct stat st;
4346 p = (char *)s;
4347 len = strlen(p);
4348 if (lstat_without_gvl(buf, &st) == 0 && S_ISLNK(st.st_mode)) {
4349 is_symlink = 1;
4350 if (len > 4 && STRCASECMP(p + len - 4, ".lnk") != 0) {
4351 lnk_added = 1;
4352 }
4353 }
4354 path = *buf ? buf : "/";
4355#ifdef HAVE_CYGWIN_CONV_PATH
4356 bufsize = cygwin_conv_path(flags, path, NULL, 0);
4357 if (bufsize > 0) {
4358 bufsize += len;
4359 if (lnk_added) bufsize += 4;
4360 w32buf = ALLOCA_N(char, bufsize);
4361 if (cygwin_conv_path(flags, path, w32buf, bufsize) == 0) {
4362 b = w32buf;
4363 }
4364 }
4365#else /* !HAVE_CYGWIN_CONV_PATH */
4366 bufsize = MAXPATHLEN;
4367 if (cygwin_conv_to_win32_path(path, w32buf) == 0) {
4368 b = w32buf;
4369 }
4370#endif /* !HAVE_CYGWIN_CONV_PATH */
4371 if (is_symlink && b == w32buf) {
4372 *p = '\\';
4373 strlcat(w32buf, p, bufsize);
4374 if (lnk_added) {
4375 strlcat(w32buf, ".lnk", bufsize);
4376 }
4377 }
4378 else {
4379 lnk_added = 0;
4380 }
4381 *p = '/';
4382#endif /* __CYGWIN__ */
4383 rb_str_set_len(result, p - buf + strlen(p));
4384 encidx = ENCODING_GET(result);
4385 tmp = result;
4386 if (encidx != ENCINDEX_UTF_8 && !is_ascii_string(result)) {
4387 tmp = rb_str_encode_ospath(result);
4388 }
4389 len = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, NULL, 0);
4390 wstr = ALLOCV_N(WCHAR, v, len);
4391 MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, wstr, len);
4392 if (tmp != result) rb_str_set_len(tmp, 0);
4393 h = FindFirstFileW(wstr, &wfd);
4394 ALLOCV_END(v);
4395 if (h != INVALID_HANDLE_VALUE) {
4396 size_t wlen;
4397 FindClose(h);
4398 len = lstrlenW(wfd.cFileName);
4399#ifdef __CYGWIN__
4400 if (lnk_added && len > 4 &&
4401 wcscasecmp(wfd.cFileName + len - 4, L".lnk") == 0) {
4402 wfd.cFileName[len -= 4] = L'\0';
4403 }
4404#else
4405 p = (char *)s;
4406#endif
4407 ++p;
4408 wlen = (int)len;
4409 len = WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, NULL, 0, NULL, NULL);
4410 if (tmp == result) {
4411 BUFCHECK(bdiff + len >= buflen);
4412 WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, p, len + 1, NULL, NULL);
4413 }
4414 else {
4416 WideCharToMultiByte(CP_UTF8, 0, wfd.cFileName, wlen, RSTRING_PTR(tmp), len + 1, NULL, NULL);
4417 rb_str_cat_conv_enc_opts(result, bdiff, RSTRING_PTR(tmp), len,
4418 rb_utf8_encoding(), 0, Qnil);
4419 BUFINIT();
4420 rb_str_resize(tmp, 0);
4421 }
4422 p += len;
4423 }
4424#ifdef __CYGWIN__
4425 else {
4426 p += strlen(p);
4427 }
4428#endif
4429 }
4430#endif /* USE_NTFS */
4431
4432 rb_str_set_len(result, p - buf);
4433 rb_enc_check(fname, result);
4434 ENC_CODERANGE_CLEAR(result);
4435 return result;
4436}
4437#endif /* !_WIN32 (this ifdef started above rb_default_home_dir) */
4438
4439#define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, 1)
4440
4441static VALUE
4442str_shrink(VALUE str)
4443{
4444 rb_str_resize(str, RSTRING_LEN(str));
4445 return str;
4446}
4447
4448#define expand_path(fname, dname, abs_mode, long_name, result) \
4449 str_shrink(rb_file_expand_path_internal(fname, dname, abs_mode, long_name, result))
4450
4451#define check_expand_path_args(fname, dname) \
4452 (((fname) = rb_get_path(fname)), \
4453 (void)(NIL_P(dname) ? (dname) : ((dname) = rb_get_path(dname))))
4454
4455static VALUE
4456file_expand_path_1(VALUE fname)
4457{
4458 return rb_file_expand_path_internal(fname, Qnil, 0, 0, EXPAND_PATH_BUFFER());
4459}
4460
4461VALUE
4462rb_file_expand_path(VALUE fname, VALUE dname)
4463{
4464 check_expand_path_args(fname, dname);
4465 return expand_path(fname, dname, 0, 1, EXPAND_PATH_BUFFER());
4466}
4467
4468VALUE
4469rb_file_expand_path_fast(VALUE fname, VALUE dname)
4470{
4471 return expand_path(fname, dname, 0, 0, EXPAND_PATH_BUFFER());
4472}
4473
4474VALUE
4475rb_file_s_expand_path(int argc, const VALUE *argv)
4476{
4477 rb_check_arity(argc, 1, 2);
4478 return rb_file_expand_path(argv[0], argc > 1 ? argv[1] : Qnil);
4479}
4480
4481/*
4482 * call-seq:
4483 * File.expand_path(file_name [, dir_string] ) -> abs_file_name
4484 *
4485 * Converts a pathname to an absolute pathname. Relative paths are
4486 * referenced from the current working directory of the process unless
4487 * +dir_string+ is given, in which case it will be used as the
4488 * starting point. The given pathname may start with a
4489 * ``<code>~</code>'', which expands to the process owner's home
4490 * directory (the environment variable +HOME+ must be set
4491 * correctly). ``<code>~</code><i>user</i>'' expands to the named
4492 * user's home directory.
4493 *
4494 * File.expand_path("~oracle/bin") #=> "/home/oracle/bin"
4495 *
4496 * A simple example of using +dir_string+ is as follows.
4497 * File.expand_path("ruby", "/usr/bin") #=> "/usr/bin/ruby"
4498 *
4499 * A more complex example which also resolves parent directory is as follows.
4500 * Suppose we are in bin/mygem and want the absolute path of lib/mygem.rb.
4501 *
4502 * File.expand_path("../../lib/mygem.rb", __FILE__)
4503 * #=> ".../path/to/project/lib/mygem.rb"
4504 *
4505 * So first it resolves the parent of __FILE__, that is bin/, then go to the
4506 * parent, the root of the project and appends +lib/mygem.rb+.
4507 */
4508
4509static VALUE
4510s_expand_path(int c, const VALUE * v, VALUE _)
4511{
4512 return rb_file_s_expand_path(c, v);
4513}
4514
4515VALUE
4516rb_file_absolute_path(VALUE fname, VALUE dname)
4517{
4518 check_expand_path_args(fname, dname);
4519 return expand_path(fname, dname, 1, 1, EXPAND_PATH_BUFFER());
4520}
4521
4522VALUE
4523rb_file_s_absolute_path(int argc, const VALUE *argv)
4524{
4525 rb_check_arity(argc, 1, 2);
4526 return rb_file_absolute_path(argv[0], argc > 1 ? argv[1] : Qnil);
4527}
4528
4529/*
4530 * call-seq:
4531 * File.absolute_path(file_name [, dir_string] ) -> abs_file_name
4532 *
4533 * Converts a pathname to an absolute pathname. Relative paths are
4534 * referenced from the current working directory of the process unless
4535 * <i>dir_string</i> is given, in which case it will be used as the
4536 * starting point. If the given pathname starts with a ``<code>~</code>''
4537 * it is NOT expanded, it is treated as a normal directory name.
4538 *
4539 * File.absolute_path("~oracle/bin") #=> "<relative_path>/~oracle/bin"
4540 */
4541
4542static VALUE
4543s_absolute_path(int c, const VALUE * v, VALUE _)
4544{
4545 return rb_file_s_absolute_path(c, v);
4546}
4547
4548/*
4549 * call-seq:
4550 * File.absolute_path?(file_name) -> true or false
4551 *
4552 * Returns <code>true</code> if +file_name+ is an absolute path, and
4553 * <code>false</code> otherwise.
4554 *
4555 * File.absolute_path?("c:/foo") #=> false (on Linux), true (on Windows)
4556 */
4557
4558static VALUE
4559s_absolute_path_p(VALUE klass, VALUE fname)
4560{
4561 VALUE path = rb_get_path(fname);
4562
4563 if (!rb_is_absolute_path(RSTRING_PTR(path))) return Qfalse;
4564 return Qtrue;
4565}
4566
4567enum rb_realpath_mode {
4568 RB_REALPATH_CHECK,
4569 RB_REALPATH_DIR,
4570 RB_REALPATH_STRICT,
4571 RB_REALPATH_MODE_MAX
4572};
4573
4574static int
4575realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE fallback,
4576 VALUE loopcheck, enum rb_realpath_mode mode, int last)
4577{
4578 const char *pend = unresolved + strlen(unresolved);
4579 rb_encoding *enc = rb_enc_get(*resolvedp);
4580 ID resolving;
4581 CONST_ID(resolving, "resolving");
4582 while (unresolved < pend) {
4583 const char *testname = unresolved;
4584 const char *unresolved_firstsep = rb_enc_path_next(unresolved, pend, enc);
4585 long testnamelen = unresolved_firstsep - unresolved;
4586 const char *unresolved_nextname = unresolved_firstsep;
4587 while (unresolved_nextname < pend && isdirsep(*unresolved_nextname))
4588 unresolved_nextname++;
4589 unresolved = unresolved_nextname;
4590 if (testnamelen == 1 && testname[0] == '.') {
4591 }
4592 else if (testnamelen == 2 && testname[0] == '.' && testname[1] == '.') {
4593 if (*prefixlenp < RSTRING_LEN(*resolvedp)) {
4594 bool mb_enc = enc_mbclen_needed(enc);
4595 const char *resolved_str = RSTRING_PTR(*resolvedp);
4596 const char *resolved_names = resolved_str + *prefixlenp;
4597 const char *lastsep = strrdirsep(resolved_names, resolved_str + RSTRING_LEN(*resolvedp), mb_enc, enc);
4598 long len = lastsep ? lastsep - resolved_names : 0;
4599 rb_str_resize(*resolvedp, *prefixlenp + len);
4600 }
4601 }
4602 else {
4603 VALUE checkval;
4604 VALUE testpath = rb_str_dup(*resolvedp);
4605 if (*prefixlenp < RSTRING_LEN(testpath))
4606 rb_str_cat2(testpath, "/");
4607#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER)
4608 if (*prefixlenp > 1 && *prefixlenp == RSTRING_LEN(testpath)) {
4609 const char *prefix = RSTRING_PTR(testpath);
4610 const char *last = rb_enc_left_char_head(prefix, prefix + *prefixlenp - 1, prefix + *prefixlenp, enc);
4611 if (!isdirsep(*last)) rb_str_cat2(testpath, "/");
4612 }
4613#endif
4614 rb_str_cat(testpath, testname, testnamelen);
4615 checkval = rb_hash_aref(loopcheck, testpath);
4616 if (!NIL_P(checkval)) {
4617 if (checkval == ID2SYM(resolving)) {
4618 if (mode == RB_REALPATH_CHECK) {
4619 errno = ELOOP;
4620 return -1;
4621 }
4622 rb_syserr_fail_path(ELOOP, testpath);
4623 }
4624 else {
4625 *resolvedp = rb_str_dup(checkval);
4626 }
4627 }
4628 else {
4629 struct stat sbuf;
4630 int ret;
4631 ret = lstat_without_gvl(RSTRING_PTR(testpath), &sbuf);
4632 if (ret == -1) {
4633 int e = errno;
4634 if (e == ENOENT && !NIL_P(fallback)) {
4635 if (stat_without_gvl(RSTRING_PTR(fallback), &sbuf) == 0) {
4636 rb_str_replace(*resolvedp, fallback);
4637 return 0;
4638 }
4639 }
4640 if (mode == RB_REALPATH_CHECK) return -1;
4641 if (e == ENOENT) {
4642 if (mode == RB_REALPATH_STRICT || !last || *unresolved_firstsep)
4643 rb_syserr_fail_path(e, testpath);
4644 *resolvedp = testpath;
4645 break;
4646 }
4647 else {
4648 rb_syserr_fail_path(e, testpath);
4649 }
4650 }
4651#ifdef HAVE_READLINK
4652 if (S_ISLNK(sbuf.st_mode)) {
4653 VALUE link;
4654 VALUE link_orig = Qnil;
4655 const char *link_prefix, *link_names;
4656 long link_prefixlen;
4657 rb_hash_aset(loopcheck, testpath, ID2SYM(resolving));
4658 link = rb_readlink(testpath, enc);
4659 link_prefix = RSTRING_PTR(link);
4660 link_names = skipprefixroot(link_prefix, link_prefix + RSTRING_LEN(link), rb_enc_get(link));
4661 link_prefixlen = link_names - link_prefix;
4662 if (link_prefixlen > 0) {
4663 rb_encoding *tmpenc, *linkenc = rb_enc_get(link);
4664 link_orig = link;
4665 link = rb_str_subseq(link, 0, link_prefixlen);
4666 tmpenc = fs_enc_check(*resolvedp, link);
4667 if (tmpenc != linkenc) link = rb_str_conv_enc(link, linkenc, tmpenc);
4668 *resolvedp = link;
4669 *prefixlenp = link_prefixlen;
4670 }
4671 if (realpath_rec(prefixlenp, resolvedp, link_names, testpath,
4672 loopcheck, mode, !*unresolved_firstsep))
4673 return -1;
4674 RB_GC_GUARD(link_orig);
4675 rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp));
4676 }
4677 else
4678#endif /* HAVE_READLINK */
4679 {
4680 VALUE s = rb_str_dup_frozen(testpath);
4681 rb_hash_aset(loopcheck, s, s);
4682 *resolvedp = testpath;
4683 }
4684 }
4685 }
4686 }
4687 return 0;
4688}
4689
4690static VALUE
4691rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode)
4692{
4693 long prefixlen;
4694 VALUE resolved;
4695 VALUE unresolved_path;
4696 VALUE loopcheck;
4697 VALUE curdir = Qnil;
4698
4699 rb_encoding *enc;
4700 char *path_names = NULL, *basedir_names = NULL, *curdir_names = NULL;
4701 char *ptr, *prefixptr = NULL, *pend;
4702 long len;
4703
4704 unresolved_path = rb_str_dup_frozen(path);
4705
4706 if (!NIL_P(basedir)) {
4707 FilePathValue(basedir);
4708 basedir = TO_OSPATH(rb_str_dup_frozen(basedir));
4709 }
4710
4711 enc = rb_enc_get(unresolved_path);
4712 unresolved_path = TO_OSPATH(unresolved_path);
4713 RSTRING_GETMEM(unresolved_path, ptr, len);
4714 path_names = skipprefixroot(ptr, ptr + len, rb_enc_get(unresolved_path));
4715 if (ptr != path_names) {
4716 resolved = rb_str_subseq(unresolved_path, 0, path_names - ptr);
4717 goto root_found;
4718 }
4719
4720 if (!NIL_P(basedir)) {
4721 RSTRING_GETMEM(basedir, ptr, len);
4722 basedir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(basedir));
4723 if (ptr != basedir_names) {
4724 resolved = rb_str_subseq(basedir, 0, basedir_names - ptr);
4725 goto root_found;
4726 }
4727 }
4728
4729 curdir = rb_dir_getwd_ospath();
4730 RSTRING_GETMEM(curdir, ptr, len);
4731 curdir_names = skipprefixroot(ptr, ptr + len, rb_enc_get(curdir));
4732 resolved = rb_str_subseq(curdir, 0, curdir_names - ptr);
4733
4734 root_found:
4735 RSTRING_GETMEM(resolved, prefixptr, prefixlen);
4736 pend = prefixptr + prefixlen;
4737 bool mb_enc = enc_mbclen_needed(enc);
4738 ptr = chompdirsep(prefixptr, pend, mb_enc, enc);
4739 if (ptr < pend) {
4740 prefixlen = ++ptr - prefixptr;
4741 rb_str_set_len(resolved, prefixlen);
4742 }
4743#ifdef FILE_ALT_SEPARATOR
4744 while (prefixptr < ptr) {
4745 if (*prefixptr == FILE_ALT_SEPARATOR) {
4746 *prefixptr = '/';
4747 }
4748 Inc(prefixptr, pend, mb_enc, enc);
4749 }
4750#endif
4751
4752 switch (rb_enc_to_index(enc)) {
4753 case ENCINDEX_ASCII_8BIT:
4754 case ENCINDEX_US_ASCII:
4755 rb_enc_associate_index(resolved, rb_filesystem_encindex());
4756 }
4757
4758 loopcheck = rb_hash_new();
4759 if (curdir_names) {
4760 if (realpath_rec(&prefixlen, &resolved, curdir_names, Qnil, loopcheck, mode, 0))
4761 return Qnil;
4762 }
4763 if (basedir_names) {
4764 if (realpath_rec(&prefixlen, &resolved, basedir_names, Qnil, loopcheck, mode, 0))
4765 return Qnil;
4766 }
4767 if (realpath_rec(&prefixlen, &resolved, path_names, Qnil, loopcheck, mode, 1))
4768 return Qnil;
4769
4770 if (origenc && origenc != rb_enc_get(resolved)) {
4771 if (rb_enc_str_asciionly_p(resolved)) {
4772 rb_enc_associate(resolved, origenc);
4773 }
4774 else {
4775 resolved = rb_str_conv_enc(resolved, NULL, origenc);
4776 }
4777 }
4778
4779 RB_GC_GUARD(unresolved_path);
4780 RB_GC_GUARD(curdir);
4781 return resolved;
4782}
4783
4784static VALUE rb_file_join(long argc, VALUE *args);
4785
4786#ifndef HAVE_REALPATH
4787static VALUE
4788rb_check_realpath_emulate_try(VALUE arg)
4789{
4790 VALUE *args = (VALUE *)arg;
4791 return rb_check_realpath_emulate(args[0], args[1], (rb_encoding *)args[2], RB_REALPATH_CHECK);
4792}
4793
4794static VALUE
4795rb_check_realpath_emulate_rescue(VALUE arg, VALUE exc)
4796{
4797 return Qnil;
4798}
4799#elif !defined(NEEDS_REALPATH_BUFFER) && defined(__APPLE__) && \
4800 (!defined(MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6))
4801/* realpath() on OSX < 10.6 doesn't implement automatic allocation */
4802# include <sys/syslimits.h>
4803# define NEEDS_REALPATH_BUFFER 1
4804#endif /* HAVE_REALPATH */
4805
4806static VALUE
4807rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode)
4808{
4809#ifdef HAVE_REALPATH
4810 VALUE unresolved_path;
4811 char *resolved_ptr = NULL;
4812 VALUE resolved;
4813# if defined(NEEDS_REALPATH_BUFFER) && NEEDS_REALPATH_BUFFER
4814 char resolved_buffer[PATH_MAX];
4815# else
4816 char *const resolved_buffer = NULL;
4817# endif
4818
4819 if (mode == RB_REALPATH_DIR) {
4820 return rb_check_realpath_emulate(basedir, path, origenc, mode);
4821 }
4822
4823 unresolved_path = rb_str_dup_frozen(path);
4824 if (*RSTRING_PTR(unresolved_path) != '/' && !NIL_P(basedir)) {
4825 VALUE paths[2] = {basedir, unresolved_path};
4826 unresolved_path = rb_file_join(2, paths);
4827 }
4828 if (origenc) unresolved_path = TO_OSPATH(unresolved_path);
4829
4830 if ((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), resolved_buffer)) == NULL) {
4831 /*
4832 wasi-libc 22 and later support realpath(3) but return ENOTSUP
4833 when the underlying host syscall returns it.
4834 glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb,
4835 returning ENOTDIR in that case.
4836 glibc realpath(3) can also return ENOENT for paths that exist,
4837 such as /dev/fd/5.
4838 Fallback to the emulated approach in either of those cases. */
4839 if (errno == ENOTSUP ||
4840 errno == ENOTDIR ||
4841 (errno == ENOENT && rb_file_exist_p(0, unresolved_path))) {
4842 return rb_check_realpath_emulate(basedir, path, origenc, mode);
4843
4844 }
4845 if (mode == RB_REALPATH_CHECK) {
4846 return Qnil;
4847 }
4848 rb_sys_fail_path(unresolved_path);
4849 }
4850 resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding());
4851# if !(defined(NEEDS_REALPATH_BUFFER) && NEEDS_REALPATH_BUFFER)
4852 free(resolved_ptr);
4853# endif
4854
4855# if !defined(__linux__) && !defined(__APPLE__)
4856 /* As `resolved` is a String in the filesystem encoding, no
4857 * conversion is needed */
4858 struct stat st;
4859 if (stat_without_gvl(RSTRING_PTR(resolved), &st) < 0) {
4860 if (mode == RB_REALPATH_CHECK) {
4861 return Qnil;
4862 }
4863 rb_sys_fail_path(unresolved_path);
4864 }
4865# endif /* !defined(__linux__) && !defined(__APPLE__) */
4866
4867 if (origenc && origenc != rb_enc_get(resolved)) {
4868 if (!rb_enc_str_asciionly_p(resolved)) {
4869 resolved = rb_str_conv_enc(resolved, NULL, origenc);
4870 }
4871 rb_enc_associate(resolved, origenc);
4872 }
4873
4874 if (is_broken_string(resolved)) {
4875 rb_enc_associate(resolved, rb_filesystem_encoding());
4876 if (is_broken_string(resolved)) {
4877 rb_enc_associate(resolved, rb_ascii8bit_encoding());
4878 }
4879 }
4880
4881 RB_GC_GUARD(unresolved_path);
4882 return resolved;
4883#else /* !HAVE_REALPATH */
4884 if (mode == RB_REALPATH_CHECK) {
4885 VALUE arg[3];
4886 arg[0] = basedir;
4887 arg[1] = path;
4888 arg[2] = (VALUE)origenc;
4889
4890 return rb_rescue(rb_check_realpath_emulate_try, (VALUE)arg,
4891 rb_check_realpath_emulate_rescue, Qnil);
4892 }
4893 else {
4894 return rb_check_realpath_emulate(basedir, path, origenc, mode);
4895 }
4896#endif /* HAVE_REALPATH */
4897}
4898
4899VALUE
4900rb_realpath_internal(VALUE basedir, VALUE path, int strict)
4901{
4902 const enum rb_realpath_mode mode =
4903 strict ? RB_REALPATH_STRICT : RB_REALPATH_DIR;
4904 return rb_check_realpath_internal(basedir, path, rb_enc_get(path), mode);
4905}
4906
4907VALUE
4908rb_check_realpath(VALUE basedir, VALUE path, rb_encoding *enc)
4909{
4910 return rb_check_realpath_internal(basedir, path, enc, RB_REALPATH_CHECK);
4911}
4912
4913/*
4914 * call-seq:
4915 * File.realpath(pathname [, dir_string]) -> real_pathname
4916 *
4917 * Returns the real (absolute) pathname of _pathname_ in the actual
4918 * filesystem not containing symlinks or useless dots.
4919 *
4920 * If _dir_string_ is given, it is used as a base directory
4921 * for interpreting relative pathname instead of the current directory.
4922 *
4923 * All components of the pathname must exist when this method is
4924 * called.
4925 */
4926static VALUE
4927rb_file_s_realpath(int argc, VALUE *argv, VALUE klass)
4928{
4929 VALUE basedir = (rb_check_arity(argc, 1, 2) > 1) ? argv[1] : Qnil;
4930 VALUE path = argv[0];
4931 FilePathValue(path);
4932 return rb_realpath_internal(basedir, path, 1);
4933}
4934
4935/*
4936 * call-seq:
4937 * File.realdirpath(pathname [, dir_string]) -> real_pathname
4938 *
4939 * Returns the real (absolute) pathname of _pathname_ in the actual filesystem.
4940 * The real pathname doesn't contain symlinks or useless dots.
4941 *
4942 * If _dir_string_ is given, it is used as a base directory
4943 * for interpreting relative pathname instead of the current directory.
4944 *
4945 * The last component of the real pathname can be nonexistent.
4946 */
4947static VALUE
4948rb_file_s_realdirpath(int argc, VALUE *argv, VALUE klass)
4949{
4950 VALUE basedir = (rb_check_arity(argc, 1, 2) > 1) ? argv[1] : Qnil;
4951 VALUE path = argv[0];
4952 FilePathValue(path);
4953 return rb_realpath_internal(basedir, path, 0);
4954}
4955
4956static size_t
4957rmext(const char *p, long l0, long l1, const char *e, long l2, rb_encoding *enc)
4958{
4959 int len1, len2;
4960 unsigned int c;
4961 const char *s, *last;
4962
4963 if (!e || !l2) return 0;
4964
4965 c = rb_enc_codepoint_len(e, e + l2, &len1, enc);
4966 if (rb_enc_ascget(e + len1, e + l2, &len2, enc) == '*' && len1 + len2 == l2) {
4967 if (c == '.') return l0;
4968 s = p;
4969 e = p + l1;
4970 last = e;
4971 while (s < e) {
4972 if (rb_enc_codepoint_len(s, e, &len1, enc) == c) last = s;
4973 s += len1;
4974 }
4975 return last - p;
4976 }
4977 if (l1 < l2) return l1;
4978
4979 s = p+l1-l2;
4980 if (!at_char_boundary(p, s, p+l1, enc)) return 0;
4981#if CASEFOLD_FILESYSTEM
4982#define fncomp strncasecmp
4983#else
4984#define fncomp strncmp
4985#endif
4986 if (fncomp(s, e, l2) == 0) {
4987 return l1-l2;
4988 }
4989 return 0;
4990}
4991
4992static inline const char *
4993enc_find_basename(const char *name, long *baselen, long *alllen, bool mb_enc, rb_encoding *enc)
4994{
4995 const char *p, *q, *e, *end;
4996 long f = 0, n = -1;
4997
4998 long len = (alllen ? (size_t)*alllen : strlen(name));
4999
5000 if (len <= 0) {
5001 return name;
5002 }
5003
5004 end = name + len;
5005 name = skipprefix(name, end, mb_enc, enc);
5006#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
5007 const char *root = name;
5008#endif
5009
5010 while (name < end && isdirsep(*name)) {
5011 name++;
5012 }
5013
5014 if (name == end) {
5015 p = name - 1;
5016 f = 1;
5017#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
5018 if (name != root) {
5019 /* has slashes */
5020 }
5021#ifdef DOSISH_DRIVE_LETTER
5022 else if (*p == ':') {
5023 p++;
5024 f = 0;
5025 }
5026#endif /* DOSISH_DRIVE_LETTER */
5027#ifdef DOSISH_UNC
5028 else {
5029 p = "/";
5030 }
5031#endif /* DOSISH_UNC */
5032#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */
5033 }
5034 else {
5035 p = strrdirsep(name, end, mb_enc, enc);
5036 if (!p) {
5037 p = name;
5038 }
5039 else {
5040 while (isdirsep(*p)) {
5041 p++; /* skip last / */
5042 }
5043 }
5044#if USE_NTFS
5045 n = ntfs_tail(p, end, enc) - p;
5046#else
5047 n = chompdirsep(p, end, mb_enc, enc) - p;
5048#endif
5049 for (q = p; q - p < n && *q == '.'; q++);
5050 for (e = 0; q - p < n; Inc(q, end, mb_enc, enc)) {
5051 if (*q == '.') e = q;
5052 }
5053 if (e) {
5054 f = e - p;
5055 }
5056 else {
5057 f = n;
5058 }
5059 }
5060
5061 if (baselen) {
5062 *baselen = f;
5063 }
5064 if (alllen) {
5065 *alllen = n;
5066 }
5067 return p;
5068}
5069
5070const char *
5071ruby_enc_find_basename(const char *name, long *baselen, long *alllen, rb_encoding *enc)
5072{
5073 return enc_find_basename(name, baselen, alllen, enc_mbclen_needed(enc), enc);
5074}
5075
5076/*
5077 * call-seq:
5078 * File.basename(path, suffix = '') -> new_string
5079 *
5080 * Returns a new string containing all or part of the last entry of the given +path+.
5081 * Entries are delimited by the value of constant File::SEPARATOR
5082 * and, if non-nil, the value of constant File::ALT_SEPARATOR.
5083 *
5084 * When +suffix+ is the empty string <tt>''</tt>,
5085 * returns all of the last entry:
5086 *
5087 * File.basename('foo/bar/baz/bat.txt') # => "bat.txt"
5088 * File.basename('foo/bar/baz') # => "baz"
5089 *
5090 * File::SEPARATOR # => "/"
5091 * File.basename('foo/bar.txt////') # => "bar.txt"
5092 * File::ALT_SEPARATOR # => "\\" # On Windows.
5093 * File.basename('foo/bar.txt//\\\\//') # => "bar.txt"
5094 *
5095 * When +suffix+ is <tt>'.*'</tt>,
5096 * the last {filename extension}[https://en.wikipedia.org/wiki/Filename_extension],
5097 * if any, is removed:
5098 *
5099 * File.basename('foo/bar.txt', '.*') # => "bar"
5100 * File.basename('foo/bar.txt.old', '.*') # => "bar.txt"
5101 * File.basename('foo/bar', '.*') # => "bar"
5102 *
5103 * When +suffix+ is any string other than <tt>''</tt> or <tt>'.*'</tt>,
5104 * the matching trailing substring, if any, is removed:
5105 *
5106 * File.basename('foo/bar.txt', '.txt') # => "bar"
5107 * File.basename('foo/bar.txt', 'txt') # => "bar."
5108 * File.basename('foo/bar.txt', '*') # => "bar.txt"
5109 * File.basename('foo/bar.txt', '.') # => "bar.txt"
5110 *
5111 */
5112
5113static VALUE
5114rb_file_s_basename(int argc, VALUE *argv, VALUE _)
5115{
5116 VALUE fname, fext = Qnil;
5117 const char *name, *p, *fp = 0;
5118 long f = 0, n;
5119 rb_encoding *enc;
5120
5121 argc = rb_check_arity(argc, 1, 2);
5122 fname = argv[0];
5123 CheckPath(fname, name);
5124 if (argc == 2) {
5125 fext = argv[1];
5126 fp = StringValueCStr(fext);
5127 check_path_encoding(fext);
5128 }
5129 if (NIL_P(fext) || !(enc = rb_enc_compatible(fname, fext))) {
5130 enc = rb_str_enc_get(fname);
5131 }
5132
5133 n = RSTRING_LEN(fname);
5134 if (n <= 0 || !*name) {
5135 return rb_enc_str_new(0, 0, enc);
5136 }
5137
5138 bool mb_enc = enc_mbclen_needed(enc);
5139 p = enc_find_basename(name, &f, &n, mb_enc, enc);
5140 if (n >= 0) {
5141 if (!fp) {
5142 f = n;
5143 }
5144 else {
5145 if (!(f = rmext(p, f, n, fp, RSTRING_LEN(fext), enc))) {
5146 f = n;
5147 }
5148 RB_GC_GUARD(fext);
5149 }
5150 if (f == RSTRING_LEN(fname)) {
5151 return rb_str_new_shared(fname);
5152 }
5153 }
5154
5155 return rb_enc_str_new(p, f, enc);
5156}
5157
5158static VALUE rb_file_dirname_n(VALUE fname, int n);
5159
5160/*
5161 * call-seq:
5162 * File.dirname(file_name, level = 1) -> dir_name
5163 *
5164 * Returns all components of the filename given in <i>file_name</i>
5165 * except the last one (after first stripping trailing separators).
5166 * The filename can be formed using both File::SEPARATOR and
5167 * File::ALT_SEPARATOR as the separator when File::ALT_SEPARATOR is
5168 * not <code>nil</code>.
5169 *
5170 * File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work"
5171 *
5172 * If +level+ is given, removes the last +level+ components, not only
5173 * one.
5174 *
5175 * File.dirname("/home/gumby/work/ruby.rb", 2) #=> "/home/gumby"
5176 * File.dirname("/home/gumby/work/ruby.rb", 4) #=> "/"
5177 */
5178
5179static VALUE
5180rb_file_s_dirname(int argc, VALUE *argv, VALUE klass)
5181{
5182 int n = 1;
5183 if ((argc = rb_check_arity(argc, 1, 2)) > 1) {
5184 n = NUM2INT(argv[1]);
5185 }
5186 return rb_file_dirname_n(argv[0], n);
5187}
5188
5189VALUE
5190rb_file_dirname(VALUE fname)
5191{
5192 return rb_file_dirname_n(fname, 1);
5193}
5194
5195static VALUE
5196rb_file_dirname_n(VALUE fname, int n)
5197{
5198 const char *name, *root, *p, *end;
5199 VALUE dirname;
5200
5201 if (n < 0) rb_raise(rb_eArgError, "negative level: %d", n);
5202 CheckPath(fname, name);
5203 end = name + RSTRING_LEN(fname);
5204
5205 bool mb_enc = !rb_str_enc_fastpath(fname);
5206 rb_encoding *enc = rb_str_enc_get(fname);
5207
5208 root = skiproot(name, end);
5209#ifdef DOSISH_UNC
5210 if (root > name + 1 && isdirsep(*name))
5211 root = skipprefix(name = root - 2, end, mb_enc, enc);
5212#else
5213 if (root > name + 1)
5214 name = root - 1;
5215#endif
5216 if (n > (end - root + 1) / 2) {
5217 p = root;
5218 }
5219 else {
5220 p = end;
5221 while (n) {
5222 if (!(p = strrdirsep(root, p, mb_enc, enc))) {
5223 p = root;
5224 break;
5225 }
5226 n--;
5227 }
5228 }
5229
5230 if (p == name) {
5231 return rb_enc_str_new(".", 1, enc);
5232 }
5233#ifdef DOSISH_DRIVE_LETTER
5234 if (name + 3 < end && has_drive_letter(name) && isdirsep(*(name + 2))) {
5235 const char *top = skiproot(name + 2, end);
5236 dirname = rb_enc_str_new(name, 3, enc);
5237 rb_str_cat(dirname, top, p - top);
5238 }
5239 else
5240#endif
5241 dirname = rb_enc_str_new(name, p - name, enc);
5242#ifdef DOSISH_DRIVE_LETTER
5243 if (root == name + 2 && p == root && name[1] == ':')
5244 rb_str_cat(dirname, ".", 1);
5245#endif
5246 return dirname;
5247}
5248
5249static inline const char *
5250enc_find_extname(const char *name, long *len, bool mb_enc, rb_encoding *enc)
5251{
5252 const char *p, *e, *end = name + (len ? *len : (long)strlen(name));
5253
5254 p = strrdirsep(name, end, mb_enc, enc); /* get the last path component */
5255 if (!p)
5256 p = name;
5257 else
5258 do name = ++p; while (isdirsep(*p));
5259
5260 e = 0;
5261 while (*p && *p == '.') p++;
5262 while (*p) {
5263 if (*p == '.' || istrailinggarbage(*p)) {
5264#if USE_NTFS
5265 const char *last = p++, *dot = last;
5266 while (istrailinggarbage(*p)) {
5267 if (*p == '.') dot = p;
5268 p++;
5269 }
5270 if (!*p || isADS(*p)) {
5271 p = last;
5272 break;
5273 }
5274 if (*last == '.' || dot > last) e = dot;
5275 continue;
5276#else
5277 e = p; /* get the last dot of the last component */
5278#endif /* USE_NTFS */
5279 }
5280#if USE_NTFS
5281 else if (isADS(*p)) {
5282 break;
5283 }
5284#endif
5285 else if (isdirsep(*p))
5286 break;
5287 Inc(p, end, mb_enc, enc);
5288 }
5289
5290 if (len) {
5291 /* no dot, or the only dot is first or end? */
5292 if (!e || e == name)
5293 *len = 0;
5294 else if (e+1 == p)
5295 *len = 1;
5296 else
5297 *len = p - e;
5298 }
5299 return e;
5300}
5301
5302/*
5303 * accept a String, and return the pointer of the extension.
5304 * if len is passed, set the length of extension to it.
5305 * returned pointer is in ``name'' or NULL.
5306 * returns *len
5307 * no dot NULL 0
5308 * dotfile top 0
5309 * end with dot dot 1
5310 * .ext dot len of .ext
5311 * .ext:stream dot len of .ext without :stream (NTFS only)
5312 *
5313 */
5314const char *
5315ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc)
5316{
5317 return enc_find_extname(name, len, enc_mbclen_needed(enc), enc);
5318}
5319
5320/*
5321 * call-seq:
5322 * File.extname(path) -> string
5323 *
5324 * Returns the extension (the portion of file name in +path+
5325 * starting from the last period).
5326 *
5327 * If +path+ is a dotfile, or starts with a period, then the starting
5328 * dot is not dealt with the start of the extension.
5329 *
5330 * An empty string will also be returned when the period is the last character
5331 * in +path+.
5332 *
5333 * On Windows, trailing dots are truncated.
5334 *
5335 * File.extname("test.rb") #=> ".rb"
5336 * File.extname("a/b/d/test.rb") #=> ".rb"
5337 * File.extname(".a/b/d/test.rb") #=> ".rb"
5338 * File.extname("foo.") #=> "" on Windows
5339 * File.extname("foo.") #=> "." on non-Windows
5340 * File.extname("test") #=> ""
5341 * File.extname(".profile") #=> ""
5342 * File.extname(".profile.sh") #=> ".sh"
5343 *
5344 */
5345
5346static VALUE
5347rb_file_s_extname(VALUE klass, VALUE fname)
5348{
5349 const char *name;
5350 CheckPath(fname, name);
5351 long len = RSTRING_LEN(fname);
5352
5353 if (len < 1) {
5354 return rb_enc_str_new(0, 0, rb_str_enc_get(fname));
5355 }
5356
5357 bool mb_enc = !rb_str_enc_fastpath(fname);
5358 rb_encoding *enc = rb_str_enc_get(fname);
5359
5360 const char *ext = enc_find_extname(name, &len, mb_enc, enc);
5361 return rb_enc_str_new(ext, len, enc);
5362}
5363
5364/*
5365 * call-seq:
5366 * File.path(path) -> string
5367 *
5368 * Returns the string representation of the path
5369 *
5370 * File.path(File::NULL) #=> "/dev/null"
5371 * File.path(Pathname.new("/tmp")) #=> "/tmp"
5372 *
5373 * If +path+ is not a String:
5374 *
5375 * 1. If it has the +to_path+ method, that method will be called to
5376 * coerce to a String.
5377 *
5378 * 2. Otherwise, or if the coerced result is not a String too, the
5379 * standard coercion using +to_str+ method will take place on that
5380 * object. (See also String.try_convert)
5381 *
5382 * The coerced string must satisfy the following conditions:
5383 *
5384 * 1. It must be in an ASCII-compatible encoding; otherwise, an
5385 * Encoding::CompatibilityError is raised.
5386 *
5387 * 2. It must not contain the NUL character (<tt>\0</tt>); otherwise,
5388 * an ArgumentError is raised.
5389 */
5390
5391static VALUE
5392rb_file_s_path(VALUE klass, VALUE fname)
5393{
5394 return rb_get_path(fname);
5395}
5396
5397/*
5398 * call-seq:
5399 * File.split(file_name) -> array
5400 *
5401 * Splits the given string into a directory and a file component and
5402 * returns them in a two-element array. See also File::dirname and
5403 * File::basename.
5404 *
5405 * File.split("/home/gumby/.profile") #=> ["/home/gumby", ".profile"]
5406 */
5407
5408static VALUE
5409rb_file_s_split(VALUE klass, VALUE path)
5410{
5411 FilePathStringValue(path); /* get rid of converting twice */
5412 return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path,Qundef));
5413}
5414
5415static VALUE rb_file_join_ary(VALUE ary);
5416
5417static VALUE
5418file_inspect_join(VALUE ary, VALUE arg, int recur)
5419{
5420 if (recur || ary == arg) rb_raise(rb_eArgError, "recursive array");
5421 return rb_file_join_ary(arg);
5422}
5423
5424static VALUE
5425rb_file_join_ary(VALUE ary)
5426{
5427 long len, i;
5428 VALUE result, tmp;
5429 const char *name, *tail;
5430 int checked = TRUE;
5431 rb_encoding *enc;
5432
5433 if (RARRAY_LEN(ary) == 0) return rb_str_new(0, 0);
5434
5435 len = 1;
5436 for (i=0; i<RARRAY_LEN(ary); i++) {
5437 tmp = RARRAY_AREF(ary, i);
5438 if (RB_TYPE_P(tmp, T_STRING)) {
5439 check_path_encoding(tmp);
5440 len += RSTRING_LEN(tmp);
5441 }
5442 else {
5443 len += 10;
5444 }
5445 }
5446 len += RARRAY_LEN(ary) - 1;
5447 result = rb_str_buf_new(len);
5448 RBASIC_CLEAR_CLASS(result);
5449 for (i=0; i<RARRAY_LEN(ary); i++) {
5450 tmp = RARRAY_AREF(ary, i);
5451 switch (OBJ_BUILTIN_TYPE(tmp)) {
5452 case T_STRING:
5453 if (!checked) check_path_encoding(tmp);
5454 StringValueCStr(tmp);
5455 break;
5456 case T_ARRAY:
5457 if (ary == tmp) {
5458 rb_raise(rb_eArgError, "recursive array");
5459 }
5460 else {
5461 tmp = rb_exec_recursive(file_inspect_join, ary, tmp);
5462 }
5463 break;
5464 default:
5466 checked = FALSE;
5467 }
5468 RSTRING_GETMEM(result, name, len);
5469 if (i == 0) {
5470 rb_enc_copy(result, tmp);
5471 }
5472 else {
5473 tail = chompdirsep(name, name + len, true, rb_enc_get(result));
5474 if (RSTRING_PTR(tmp) && isdirsep(RSTRING_PTR(tmp)[0])) {
5475 rb_str_set_len(result, tail - name);
5476 }
5477 else if (!*tail) {
5478 rb_str_cat(result, "/", 1);
5479 }
5480 }
5481 enc = fs_enc_check(result, tmp);
5482 rb_str_buf_append(result, tmp);
5483 rb_enc_associate(result, enc);
5484 }
5485 RBASIC_SET_CLASS_RAW(result, rb_cString);
5486
5487 return result;
5488}
5489
5490static inline VALUE
5491rb_file_join_fastpath(long argc, VALUE *args)
5492{
5493 long size = argc;
5494
5495 long i;
5496 for (i = 0; i < argc; i++) {
5497 VALUE tmp = args[i];
5498 if (RB_LIKELY(RB_TYPE_P(tmp, T_STRING) && rb_str_enc_fastpath(tmp))) {
5499 size += RSTRING_LEN(tmp);
5500 }
5501 else {
5502 return 0;
5503 }
5504 }
5505
5506 VALUE result = rb_str_buf_new(size);
5507
5508 int encidx = ENCODING_GET_INLINED(args[0]);
5509 ENCODING_SET_INLINED(result, encidx);
5510 rb_str_buf_append(result, args[0]);
5511
5512 const char *name = RSTRING_PTR(result);
5513 for (i = 1; i < argc; i++) {
5514 VALUE tmp = args[i];
5515 long len = RSTRING_LEN(result);
5516
5517 const char *tmp_s;
5518 long tmp_len;
5519 RSTRING_GETMEM(tmp, tmp_s, tmp_len);
5520
5521 if (isdirsep(tmp_s[0])) {
5522 // right side has a leading separator, remove left side separators.
5523 long trailing_seps = 0;
5524 while (isdirsep(name[len - trailing_seps - 1])) {
5525 trailing_seps++;
5526 }
5527 rb_str_set_len(result, len - trailing_seps);
5528 }
5529 else if (!isdirsep(name[len - 1])) {
5530 // neither side have a separator, append one;
5531 rb_str_cat(result, "/", 1);
5532 }
5533
5534 if (RB_UNLIKELY(ENCODING_GET_INLINED(tmp) != encidx)) {
5535 rb_encoding *new_enc = fs_enc_check(result, tmp);
5536 rb_enc_associate(result, new_enc);
5537 encidx = rb_enc_to_index(new_enc);
5538 }
5539
5540 rb_str_buf_cat(result, tmp_s, tmp_len);
5541 }
5542
5543 rb_str_null_check(result);
5544 return result;
5545}
5546
5547static inline VALUE
5548rb_file_join(long argc, VALUE *args)
5549{
5550 if (RB_UNLIKELY(argc == 0)) {
5551 return rb_str_new(0, 0);
5552 }
5553
5554 VALUE result = rb_file_join_fastpath(argc, args);
5555 if (RB_LIKELY(result)) {
5556 return result;
5557 }
5558
5559 return rb_file_join_ary(rb_ary_new_from_values(argc, args));
5560}
5561/*
5562 * call-seq:
5563 * File.join(string, ...) -> string
5564 *
5565 * Returns a new string formed by joining the strings using
5566 * <code>"/"</code>.
5567 *
5568 * File.join("usr", "mail", "gumby") #=> "usr/mail/gumby"
5569 *
5570 */
5571
5572static VALUE
5573rb_file_s_join(int argc, VALUE *argv, VALUE klass)
5574{
5575 return rb_file_join(argc, argv);
5576}
5577
5578#if defined(HAVE_TRUNCATE)
5579struct truncate_arg {
5580 const char *path;
5581 rb_off_t pos;
5582};
5583
5584static void *
5585nogvl_truncate(void *ptr)
5586{
5587 struct truncate_arg *ta = ptr;
5588 return (void *)(VALUE)truncate(ta->path, ta->pos);
5589}
5590
5591/*
5592 * call-seq:
5593 * File.truncate(file_name, integer) -> 0
5594 *
5595 * Truncates the file <i>file_name</i> to be at most <i>integer</i>
5596 * bytes long. Not available on all platforms.
5597 *
5598 * f = File.new("out", "w")
5599 * f.write("1234567890") #=> 10
5600 * f.close #=> nil
5601 * File.truncate("out", 5) #=> 0
5602 * File.size("out") #=> 5
5603 *
5604 */
5605
5606static VALUE
5607rb_file_s_truncate(VALUE klass, VALUE path, VALUE len)
5608{
5609 struct truncate_arg ta;
5610 int r;
5611
5612 ta.pos = NUM2OFFT(len);
5613 FilePathValue(path);
5614 path = rb_str_encode_ospath(path);
5615 ta.path = StringValueCStr(path);
5616
5617 r = IO_WITHOUT_GVL_INT(nogvl_truncate, &ta);
5618 if (r < 0)
5619 rb_sys_fail_path(path);
5620 return INT2FIX(0);
5621}
5622#else
5623#define rb_file_s_truncate rb_f_notimplement
5624#endif
5625
5626#if defined(HAVE_FTRUNCATE)
5627struct ftruncate_arg {
5628 int fd;
5629 rb_off_t pos;
5630};
5631
5632static VALUE
5633nogvl_ftruncate(void *ptr)
5634{
5635 struct ftruncate_arg *fa = ptr;
5636
5637 return (VALUE)ftruncate(fa->fd, fa->pos);
5638}
5639
5640/*
5641 * call-seq:
5642 * file.truncate(integer) -> 0
5643 *
5644 * Truncates <i>file</i> to at most <i>integer</i> bytes. The file
5645 * must be opened for writing. Not available on all platforms.
5646 *
5647 * f = File.new("out", "w")
5648 * f.syswrite("1234567890") #=> 10
5649 * f.truncate(5) #=> 0
5650 * f.close() #=> nil
5651 * File.size("out") #=> 5
5652 */
5653
5654static VALUE
5655rb_file_truncate(VALUE obj, VALUE len)
5656{
5657 rb_io_t *fptr;
5658 struct ftruncate_arg fa;
5659
5660 fa.pos = NUM2OFFT(len);
5661 GetOpenFile(obj, fptr);
5662 if (!(fptr->mode & FMODE_WRITABLE)) {
5663 rb_raise(rb_eIOError, "not opened for writing");
5664 }
5665 rb_io_flush_raw(obj, 0);
5666 fa.fd = fptr->fd;
5667 if ((int)rb_io_blocking_region(fptr, nogvl_ftruncate, &fa) < 0) {
5668 rb_sys_fail_path(fptr->pathv);
5669 }
5670 return INT2FIX(0);
5671}
5672#else
5673#define rb_file_truncate rb_f_notimplement
5674#endif
5675
5676# ifndef LOCK_SH
5677# define LOCK_SH 1
5678# endif
5679# ifndef LOCK_EX
5680# define LOCK_EX 2
5681# endif
5682# ifndef LOCK_NB
5683# define LOCK_NB 4
5684# endif
5685# ifndef LOCK_UN
5686# define LOCK_UN 8
5687# endif
5688
5689#ifdef __CYGWIN__
5690#include <winerror.h>
5691#endif
5692
5693static VALUE
5694rb_thread_flock(void *data)
5695{
5696#ifdef __CYGWIN__
5697 int old_errno = errno;
5698#endif
5699 int *op = data, ret = flock(op[0], op[1]);
5700
5701#ifdef __CYGWIN__
5702 if (GetLastError() == ERROR_NOT_LOCKED) {
5703 ret = 0;
5704 errno = old_errno;
5705 }
5706#endif
5707 return (VALUE)ret;
5708}
5709
5710/* :markup: markdown
5711 *
5712 * call-seq:
5713 * flock(locking_constant) -> 0 or false
5714 *
5715 * Locks or unlocks file `self` according to the given `locking_constant`,
5716 * a bitwise OR of the values in the table below.
5717 *
5718 * Not available on all platforms.
5719 *
5720 * Returns `false` if `File::LOCK_NB` is specified and the operation would have blocked;
5721 * otherwise returns `0`.
5722 *
5723 * | Constant | Lock | Effect
5724 * |-----------------|--------------|-----------------------------------------------------------------------------------------------------------------|
5725 * | `File::LOCK_EX` | Exclusive | Only one process may hold an exclusive lock for `self` at a time. |
5726 * | `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>. |
5727 * | `File::LOCK_SH` | Shared | Multiple processes may each hold a shared lock for `self` at the same time. |
5728 * | `File::LOCK_UN` | Unlock | Remove an existing lock held by this process. |
5729 *
5730 * Example:
5731 *
5732 * ```ruby
5733 * # Update a counter using an exclusive lock.
5734 * # Don't use File::WRONLY because it truncates the file.
5735 * File.open('counter', File::RDWR | File::CREAT, 0644) do |f|
5736 * f.flock(File::LOCK_EX)
5737 * value = f.read.to_i + 1
5738 * f.rewind
5739 * f.write("#{value}\n")
5740 * f.flush
5741 * f.truncate(f.pos)
5742 * end
5743 *
5744 * # Read the counter using a shared lock.
5745 * File.open('counter', 'r') do |f|
5746 * f.flock(File::LOCK_SH)
5747 * f.read
5748 * end
5749 * ```
5750 *
5751 */
5752
5753static VALUE
5754rb_file_flock(VALUE obj, VALUE operation)
5755{
5756 rb_io_t *fptr;
5757 int op[2], op1;
5758 struct timeval time;
5759
5760 op[1] = op1 = NUM2INT(operation);
5761 GetOpenFile(obj, fptr);
5762 op[0] = fptr->fd;
5763
5764 if (fptr->mode & FMODE_WRITABLE) {
5765 rb_io_flush_raw(obj, 0);
5766 }
5767 while ((int)rb_io_blocking_region(fptr, rb_thread_flock, op) < 0) {
5768 int e = errno;
5769 switch (e) {
5770 case EAGAIN:
5771 case EACCES:
5772#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
5773 case EWOULDBLOCK:
5774#endif
5775 if (op1 & LOCK_NB) return Qfalse;
5776
5777 time.tv_sec = 0;
5778 time.tv_usec = 100 * 1000; /* 0.1 sec */
5779 rb_thread_wait_for(time);
5780 rb_io_check_closed(fptr);
5781 continue;
5782
5783 case EINTR:
5784#if defined(ERESTART)
5785 case ERESTART:
5786#endif
5787 break;
5788
5789 default:
5790 rb_syserr_fail_path(e, fptr->pathv);
5791 }
5792 }
5793 return INT2FIX(0);
5794}
5795
5796static void
5797test_check(int n, int argc, VALUE *argv)
5798{
5799 int i;
5800
5801 n+=1;
5802 rb_check_arity(argc, n, n);
5803 for (i=1; i<n; i++) {
5804 if (!RB_TYPE_P(argv[i], T_FILE)) {
5805 FilePathValue(argv[i]);
5806 }
5807 }
5808}
5809
5810#define CHECK(n) test_check((n), argc, argv)
5811
5812/*
5813 * :markup: markdown
5814 *
5815 * call-seq:
5816 * test(char, path0, path1 = nil) -> object
5817 *
5818 * Performs a test on one or both of the <i>filesystem entities</i> at the given paths
5819 * `path0` and `path1`:
5820 *
5821 * - Each path `path0` or `path1` points to a file, directory, device, pipe, etc.
5822 * - Character `char` selects a specific test.
5823 *
5824 * The tests:
5825 *
5826 * - Each of these tests operates only on the entity at `path0`,
5827 * and returns `true` or `false`;
5828 * for a non-existent entity, returns `false` (does not raise exception):
5829 *
5830 * | Character | Test |
5831 * |:------------:|:--------------------------------------------------------------------------|
5832 * | <tt>'b'</tt> | Whether the entity is a block device. |
5833 * | <tt>'c'</tt> | Whether the entity is a character device. |
5834 * | <tt>'d'</tt> | Whether the entity is a directory. |
5835 * | <tt>'e'</tt> | Whether the entity is an existing entity. |
5836 * | <tt>'f'</tt> | Whether the entity is an existing regular file. |
5837 * | <tt>'g'</tt> | Whether the entity's setgid bit is set. |
5838 * | <tt>'G'</tt> | Whether the entity's group ownership is equal to the caller's. |
5839 * | <tt>'k'</tt> | Whether the entity's sticky bit is set. |
5840 * | <tt>'l'</tt> | Whether the entity is a symbolic link. |
5841 * | <tt>'o'</tt> | Whether the entity is owned by the caller's effective uid. |
5842 * | <tt>'O'</tt> | Like <tt>'o'</tt>, but uses the real uid (not the effective uid). |
5843 * | <tt>'p'</tt> | Whether the entity is a FIFO device (named pipe). |
5844 * | <tt>'r'</tt> | Whether the entity is readable by the caller's effective uid/gid. |
5845 * | <tt>'R'</tt> | Like <tt>'r'</tt>, but uses the real uid/gid (not the effective uid/gid). |
5846 * | <tt>'S'</tt> | Whether the entity is a socket. |
5847 * | <tt>'u'</tt> | Whether the entity's setuid bit is set. |
5848 * | <tt>'w'</tt> | Whether the entity is writable by the caller's effective uid/gid. |
5849 * | <tt>'W'</tt> | Like <tt>'w'</tt>, but uses the real uid/gid (not the effective uid/gid). |
5850 * | <tt>'x'</tt> | Whether the entity is executable by the caller's effective uid/gid. |
5851 * | <tt>'X'</tt> | Like <tt>'x'</tt>, but uses the real uid/gid (not the effective uid/git). |
5852 * | <tt>'z'</tt> | Whether the entity exists and is of length zero. |
5853 *
5854 * - This test operates only on the entity at `path0`,
5855 * and returns an integer size or `nil`:
5856 *
5857 * | Character | Test |
5858 * |:------------:|:---------------------------------------------------------------------------------------------|
5859 * | <tt>'s'</tt> | Returns positive integer size if the entity exists and has non-zero length, `nil` otherwise. |
5860 *
5861 * - Each of these tests operates only on the entity at `path0`,
5862 * and returns a Time object;
5863 * raises an exception if the entity does not exist:
5864 *
5865 * | Character | Test |
5866 * |:------------:|:---------------------------------------|
5867 * | <tt>'A'</tt> | Last access time for the entity. |
5868 * | <tt>'C'</tt> | Last change time for the entity. |
5869 * | <tt>'M'</tt> | Last modification time for the entity. |
5870 *
5871 * - Each of these tests operates on the modification time (`mtime`)
5872 * of each of the entities at `path0` and `path1`,
5873 * and returns a `true` or `false`;
5874 * returns `false` if either entity does not exist:
5875 *
5876 * | Character | Test |
5877 * |:------------:|:----------------------------------------------------------------|
5878 * | <tt>'<'</tt> | Whether the `mtime` at `path0` is less than that at `path1`. |
5879 * | <tt>'='</tt> | Whether the `mtime` at `path0` is equal to that at `path1`. |
5880 * | <tt>'>'</tt> | Whether the `mtime` at `path0` is greater than that at `path1`. |
5881 *
5882 * - This test operates on the content of each of the entities at `path0` and `path1`,
5883 * and returns a `true` or `false`;
5884 * returns `false` if either entity does not exist:
5885 *
5886 * | Character | Test |
5887 * |:------------:|:----------------------------------------------|
5888 * | <tt>'-'</tt> | Whether the entities exist and are identical. |
5889 *
5890 */
5891
5892static VALUE
5893rb_f_test(int argc, VALUE *argv, VALUE _)
5894{
5895 int cmd;
5896
5897 if (argc == 0) rb_check_arity(argc, 2, 3);
5898 cmd = NUM2CHR(argv[0]);
5899 if (cmd == 0) {
5900 goto unknown;
5901 }
5902 if (strchr("bcdefgGkloOprRsSuwWxXz", cmd)) {
5903 CHECK(1);
5904 switch (cmd) {
5905 case 'b':
5906 return rb_file_blockdev_p(0, argv[1]);
5907
5908 case 'c':
5909 return rb_file_chardev_p(0, argv[1]);
5910
5911 case 'd':
5912 return rb_file_directory_p(0, argv[1]);
5913
5914 case 'e':
5915 return rb_file_exist_p(0, argv[1]);
5916
5917 case 'f':
5918 return rb_file_file_p(0, argv[1]);
5919
5920 case 'g':
5921 return rb_file_sgid_p(0, argv[1]);
5922
5923 case 'G':
5924 return rb_file_grpowned_p(0, argv[1]);
5925
5926 case 'k':
5927 return rb_file_sticky_p(0, argv[1]);
5928
5929 case 'l':
5930 return rb_file_symlink_p(0, argv[1]);
5931
5932 case 'o':
5933 return rb_file_owned_p(0, argv[1]);
5934
5935 case 'O':
5936 return rb_file_rowned_p(0, argv[1]);
5937
5938 case 'p':
5939 return rb_file_pipe_p(0, argv[1]);
5940
5941 case 'r':
5942 return rb_file_readable_p(0, argv[1]);
5943
5944 case 'R':
5945 return rb_file_readable_real_p(0, argv[1]);
5946
5947 case 's':
5948 return rb_file_size_p(0, argv[1]);
5949
5950 case 'S':
5951 return rb_file_socket_p(0, argv[1]);
5952
5953 case 'u':
5954 return rb_file_suid_p(0, argv[1]);
5955
5956 case 'w':
5957 return rb_file_writable_p(0, argv[1]);
5958
5959 case 'W':
5960 return rb_file_writable_real_p(0, argv[1]);
5961
5962 case 'x':
5963 return rb_file_executable_p(0, argv[1]);
5964
5965 case 'X':
5966 return rb_file_executable_real_p(0, argv[1]);
5967
5968 case 'z':
5969 return rb_file_zero_p(0, argv[1]);
5970 }
5971 }
5972
5973 if (strchr("MAC", cmd)) {
5974 struct stat st;
5975 VALUE fname = argv[1];
5976
5977 CHECK(1);
5978 if (rb_stat(fname, &st) == -1) {
5979 int e = errno;
5980 FilePathValue(fname);
5981 rb_syserr_fail_path(e, fname);
5982 }
5983
5984 switch (cmd) {
5985 case 'A':
5986 return stat_atime(&st);
5987 case 'M':
5988 return stat_mtime(&st);
5989 case 'C':
5990 return stat_ctime(&st);
5991 }
5992 }
5993
5994 if (cmd == '-') {
5995 CHECK(2);
5996 return rb_file_identical_p(0, argv[1], argv[2]);
5997 }
5998
5999 if (strchr("=<>", cmd)) {
6000 struct stat st1, st2;
6001 stat_timestamp t1, t2;
6002
6003 CHECK(2);
6004 if (rb_stat(argv[1], &st1) < 0) return Qfalse;
6005 if (rb_stat(argv[2], &st2) < 0) return Qfalse;
6006
6007 t1 = stat_mtimespec(&st1);
6008 t2 = stat_mtimespec(&st2);
6009
6010 switch (cmd) {
6011 case '=':
6012 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec) return Qtrue;
6013 return Qfalse;
6014
6015 case '>':
6016 if (t1.tv_sec > t2.tv_sec) return Qtrue;
6017 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec) return Qtrue;
6018 return Qfalse;
6019
6020 case '<':
6021 if (t1.tv_sec < t2.tv_sec) return Qtrue;
6022 if (t1.tv_sec == t2.tv_sec && t1.tv_nsec < t2.tv_nsec) return Qtrue;
6023 return Qfalse;
6024 }
6025 }
6026 unknown:
6027 /* unknown command */
6028 if (ISPRINT(cmd)) {
6029 rb_raise(rb_eArgError, "unknown command '%s%c'", cmd == '\'' || cmd == '\\' ? "\\" : "", cmd);
6030 }
6031 else {
6032 rb_raise(rb_eArgError, "unknown command \"\\x%02X\"", cmd);
6033 }
6035}
6036
6037
6038/*
6039 * Document-class: File::Stat
6040 *
6041 * A \File::Stat object contains information about an entry in the file system.
6042 *
6043 * Each of these methods returns a new \File::Stat object:
6044 *
6045 * - File#lstat.
6046 * - File::Stat.new.
6047 * - File::lstat.
6048 * - File::stat.
6049 * - IO#stat.
6050 *
6051 * === Snapshot
6052 *
6053 * A new \File::Stat object takes an immediate "snapshot" of the entry's information;
6054 * the captured information is never updated,
6055 * regardless of changes in the actual entry:
6056 *
6057 * The entry must exist when File::Stat.new is called:
6058 *
6059 * filepath = 't.tmp'
6060 * File.exist?(filepath) # => false
6061 * File::Stat.new(filepath) # Raises Errno::ENOENT: No such file or directory.
6062 * File.write(filepath, 'foo') # Create the file.
6063 * stat = File::Stat.new(filepath) # Okay.
6064 *
6065 * Later changes to the actual entry do not change the \File::Stat object:
6066 *
6067 * File.atime(filepath) # => 2026-04-01 11:51:38.0014518 -0500
6068 * stat.atime # => 2026-04-01 11:51:38.0014518 -0500
6069 * File.write(filepath, 'bar')
6070 * File.atime(filepath) # => 2026-04-01 11:58:11.922614 -0500
6071 * stat.atime # => 2026-04-01 11:51:38.0014518 -0500
6072 * File.delete(filepath)
6073 * stat.atime # => 2026-04-01 11:51:38.0014518 -0500
6074 *
6075 * === OS-Dependencies
6076 *
6077 * Methods in a \File::Stat object may return platform-dependents values,
6078 * and not all values are meaningful on all systems;
6079 * for example, File::Stat#blocks returns +nil+ on Windows,
6080 * but returns an integer on Linux.
6081 *
6082 * See also Kernel#test.
6083 */
6084
6085static VALUE
6086rb_stat_s_alloc(VALUE klass)
6087{
6088 VALUE obj;
6089 stat_alloc(rb_cStat, &obj);
6090 return obj;
6091}
6092
6093/*
6094 * call-seq:
6095 * File::Stat.new(file_name) -> stat
6096 *
6097 * Create a File::Stat object for the given file name (raising an
6098 * exception if the file doesn't exist).
6099 */
6100
6101static VALUE
6102rb_stat_init(VALUE obj, VALUE fname)
6103{
6104 rb_io_stat_data st;
6105
6106 FilePathValue(fname);
6107 fname = rb_str_encode_ospath(fname);
6108 if (STATX(StringValueCStr(fname), &st, STATX_ALL) == -1) {
6109 rb_sys_fail_path(fname);
6110 }
6111
6112 struct rb_stat *rb_st;
6113 TypedData_Get_Struct(obj, struct rb_stat, &stat_data_type, rb_st);
6114
6115 rb_st->stat = st;
6116 rb_st->initialized = true;
6117
6118 return Qnil;
6119}
6120
6121/* :nodoc: */
6122static VALUE
6123rb_stat_init_copy(VALUE copy, VALUE orig)
6124{
6125 if (!OBJ_INIT_COPY(copy, orig)) return copy;
6126
6127 struct rb_stat *orig_rb_st;
6128 TypedData_Get_Struct(orig, struct rb_stat, &stat_data_type, orig_rb_st);
6129
6130 struct rb_stat *copy_rb_st;
6131 TypedData_Get_Struct(copy, struct rb_stat, &stat_data_type, copy_rb_st);
6132
6133 *copy_rb_st = *orig_rb_st;
6134 return copy;
6135}
6136
6137/*
6138 * call-seq:
6139 * stat.ftype -> string
6140 *
6141 * Identifies the type of <i>stat</i>. The return string is one of:
6142 * ``<code>file</code>'', ``<code>directory</code>'',
6143 * ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'',
6144 * ``<code>fifo</code>'', ``<code>link</code>'',
6145 * ``<code>socket</code>'', or ``<code>unknown</code>''.
6146 *
6147 * File.stat("/dev/tty").ftype #=> "characterSpecial"
6148 *
6149 */
6150
6151static VALUE
6152rb_stat_ftype(VALUE obj)
6153{
6154 return rb_file_ftype(get_stat(obj)->ST_(mode));
6155}
6156
6157/*
6158 * call-seq:
6159 * stat.directory? -> true or false
6160 *
6161 * Returns <code>true</code> if <i>stat</i> is a directory,
6162 * <code>false</code> otherwise.
6163 *
6164 * File.stat("testfile").directory? #=> false
6165 * File.stat(".").directory? #=> true
6166 */
6167
6168static VALUE
6169rb_stat_d(VALUE obj)
6170{
6171 if (S_ISDIR(get_stat(obj)->ST_(mode))) return Qtrue;
6172 return Qfalse;
6173}
6174
6175/*
6176 * call-seq:
6177 * stat.pipe? -> true or false
6178 *
6179 * Returns <code>true</code> if the operating system supports pipes and
6180 * <i>stat</i> is a pipe; <code>false</code> otherwise.
6181 */
6182
6183static VALUE
6184rb_stat_p(VALUE obj)
6185{
6186#ifdef S_IFIFO
6187 if (S_ISFIFO(get_stat(obj)->ST_(mode))) return Qtrue;
6188
6189#endif
6190 return Qfalse;
6191}
6192
6193/*
6194 * call-seq:
6195 * stat.symlink? -> true or false
6196 *
6197 * Returns <code>true</code> if <i>stat</i> is a symbolic link,
6198 * <code>false</code> if it isn't or if the operating system doesn't
6199 * support this feature. As File::stat automatically follows symbolic
6200 * links, #symlink? will always be <code>false</code> for an object
6201 * returned by File::stat.
6202 *
6203 * File.symlink("testfile", "alink") #=> 0
6204 * File.stat("alink").symlink? #=> false
6205 * File.lstat("alink").symlink? #=> true
6206 *
6207 */
6208
6209static VALUE
6210rb_stat_l(VALUE obj)
6211{
6212#ifdef S_ISLNK
6213 if (S_ISLNK(get_stat(obj)->ST_(mode))) return Qtrue;
6214#endif
6215 return Qfalse;
6216}
6217
6218/*
6219 * call-seq:
6220 * stat.socket? -> true or false
6221 *
6222 * Returns <code>true</code> if <i>stat</i> is a socket,
6223 * <code>false</code> if it isn't or if the operating system doesn't
6224 * support this feature.
6225 *
6226 * File.stat("testfile").socket? #=> false
6227 *
6228 */
6229
6230static VALUE
6231rb_stat_S(VALUE obj)
6232{
6233#ifdef S_ISSOCK
6234 if (S_ISSOCK(get_stat(obj)->ST_(mode))) return Qtrue;
6235
6236#endif
6237 return Qfalse;
6238}
6239
6240/*
6241 * call-seq:
6242 * stat.blockdev? -> true or false
6243 *
6244 * Returns <code>true</code> if the file is a block device,
6245 * <code>false</code> if it isn't or if the operating system doesn't
6246 * support this feature.
6247 *
6248 * File.stat("testfile").blockdev? #=> false
6249 * File.stat("/dev/hda1").blockdev? #=> true
6250 *
6251 */
6252
6253static VALUE
6254rb_stat_b(VALUE obj)
6255{
6256#ifdef S_ISBLK
6257 if (S_ISBLK(get_stat(obj)->ST_(mode))) return Qtrue;
6258
6259#endif
6260 return Qfalse;
6261}
6262
6263/*
6264 * call-seq:
6265 * stat.chardev? -> true or false
6266 *
6267 * Returns <code>true</code> if the file is a character device,
6268 * <code>false</code> if it isn't or if the operating system doesn't
6269 * support this feature.
6270 *
6271 * File.stat("/dev/tty").chardev? #=> true
6272 *
6273 */
6274
6275static VALUE
6276rb_stat_c(VALUE obj)
6277{
6278 if (S_ISCHR(get_stat(obj)->ST_(mode))) return Qtrue;
6279
6280 return Qfalse;
6281}
6282
6283/*
6284 * call-seq:
6285 * stat.owned? -> true or false
6286 *
6287 * Returns <code>true</code> if the effective user id of the process is
6288 * the same as the owner of <i>stat</i>.
6289 *
6290 * File.stat("testfile").owned? #=> true
6291 * File.stat("/etc/passwd").owned? #=> false
6292 *
6293 */
6294
6295static VALUE
6296rb_stat_owned(VALUE obj)
6297{
6298 if (get_stat(obj)->ST_(uid) == geteuid()) return Qtrue;
6299 return Qfalse;
6300}
6301
6302static VALUE
6303rb_stat_rowned(VALUE obj)
6304{
6305 if (get_stat(obj)->ST_(uid) == getuid()) return Qtrue;
6306 return Qfalse;
6307}
6308
6309/*
6310 * call-seq:
6311 * stat.grpowned? -> true or false
6312 *
6313 * Returns true if the effective group id of the process is the same as
6314 * the group id of <i>stat</i>. On Windows, returns <code>false</code>.
6315 *
6316 * File.stat("testfile").grpowned? #=> true
6317 * File.stat("/etc/passwd").grpowned? #=> false
6318 *
6319 */
6320
6321static VALUE
6322rb_stat_grpowned(VALUE obj)
6323{
6324#ifndef _WIN32
6325 if (rb_group_member(get_stat(obj)->ST_(gid))) return Qtrue;
6326#endif
6327 return Qfalse;
6328}
6329
6330/*
6331 * call-seq:
6332 * stat.readable? -> true or false
6333 *
6334 * Returns <code>true</code> if <i>stat</i> is readable by the
6335 * effective user id of this process.
6336 *
6337 * File.stat("testfile").readable? #=> true
6338 *
6339 */
6340
6341static VALUE
6342rb_stat_r(VALUE obj)
6343{
6344 rb_io_stat_data *st = get_stat(obj);
6345
6346#ifdef USE_GETEUID
6347 if (geteuid() == 0) return Qtrue;
6348#endif
6349#ifdef S_IRUSR
6350 if (rb_stat_owned(obj))
6351 return RBOOL(st->ST_(mode) & S_IRUSR);
6352#endif
6353#ifdef S_IRGRP
6354 if (rb_stat_grpowned(obj))
6355 return RBOOL(st->ST_(mode) & S_IRGRP);
6356#endif
6357#ifdef S_IROTH
6358 if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
6359#endif
6360 return Qtrue;
6361}
6362
6363/*
6364 * call-seq:
6365 * stat.readable_real? -> true or false
6366 *
6367 * Returns <code>true</code> if <i>stat</i> is readable by the real
6368 * user id of this process.
6369 *
6370 * File.stat("testfile").readable_real? #=> true
6371 *
6372 */
6373
6374static VALUE
6375rb_stat_R(VALUE obj)
6376{
6377 rb_io_stat_data *st = get_stat(obj);
6378
6379#ifdef USE_GETEUID
6380 if (getuid() == 0) return Qtrue;
6381#endif
6382#ifdef S_IRUSR
6383 if (rb_stat_rowned(obj))
6384 return RBOOL(st->ST_(mode) & S_IRUSR);
6385#endif
6386#ifdef S_IRGRP
6387 if (rb_group_member(get_stat(obj)->ST_(gid)))
6388 return RBOOL(st->ST_(mode) & S_IRGRP);
6389#endif
6390#ifdef S_IROTH
6391 if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
6392#endif
6393 return Qtrue;
6394}
6395
6396/*
6397 * call-seq:
6398 * stat.world_readable? -> integer or nil
6399 *
6400 * If <i>stat</i> is readable by others, returns an integer
6401 * representing the file permission bits of <i>stat</i>. Returns
6402 * <code>nil</code> otherwise. The meaning of the bits is platform
6403 * dependent; on Unix systems, see <code>stat(2)</code>.
6404 *
6405 * m = File.stat("/etc/passwd").world_readable? #=> 420
6406 * sprintf("%o", m) #=> "644"
6407 */
6408
6409static VALUE
6410rb_stat_wr(VALUE obj)
6411{
6412#ifdef S_IROTH
6413 rb_io_stat_data *st = get_stat(obj);
6414 if ((st->ST_(mode) & (S_IROTH)) == S_IROTH) {
6415 return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
6416 }
6417#endif
6418 return Qnil;
6419}
6420
6421/*
6422 * call-seq:
6423 * stat.writable? -> true or false
6424 *
6425 * Returns <code>true</code> if <i>stat</i> is writable by the
6426 * effective user id of this process.
6427 *
6428 * File.stat("testfile").writable? #=> true
6429 *
6430 */
6431
6432static VALUE
6433rb_stat_w(VALUE obj)
6434{
6435 rb_io_stat_data *st = get_stat(obj);
6436
6437#ifdef USE_GETEUID
6438 if (geteuid() == 0) return Qtrue;
6439#endif
6440#ifdef S_IWUSR
6441 if (rb_stat_owned(obj))
6442 return RBOOL(st->ST_(mode) & S_IWUSR);
6443#endif
6444#ifdef S_IWGRP
6445 if (rb_stat_grpowned(obj))
6446 return RBOOL(st->ST_(mode) & S_IWGRP);
6447#endif
6448#ifdef S_IWOTH
6449 if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
6450#endif
6451 return Qtrue;
6452}
6453
6454/*
6455 * call-seq:
6456 * stat.writable_real? -> true or false
6457 *
6458 * Returns <code>true</code> if <i>stat</i> is writable by the real
6459 * user id of this process.
6460 *
6461 * File.stat("testfile").writable_real? #=> true
6462 *
6463 */
6464
6465static VALUE
6466rb_stat_W(VALUE obj)
6467{
6468 rb_io_stat_data *st = get_stat(obj);
6469
6470#ifdef USE_GETEUID
6471 if (getuid() == 0) return Qtrue;
6472#endif
6473#ifdef S_IWUSR
6474 if (rb_stat_rowned(obj))
6475 return RBOOL(st->ST_(mode) & S_IWUSR);
6476#endif
6477#ifdef S_IWGRP
6478 if (rb_group_member(get_stat(obj)->ST_(gid)))
6479 return RBOOL(st->ST_(mode) & S_IWGRP);
6480#endif
6481#ifdef S_IWOTH
6482 if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
6483#endif
6484 return Qtrue;
6485}
6486
6487/*
6488 * call-seq:
6489 * stat.world_writable? -> integer or nil
6490 *
6491 * If <i>stat</i> is writable by others, returns an integer
6492 * representing the file permission bits of <i>stat</i>. Returns
6493 * <code>nil</code> otherwise. The meaning of the bits is platform
6494 * dependent; on Unix systems, see <code>stat(2)</code>.
6495 *
6496 * m = File.stat("/tmp").world_writable? #=> 511
6497 * sprintf("%o", m) #=> "777"
6498 */
6499
6500static VALUE
6501rb_stat_ww(VALUE obj)
6502{
6503#ifdef S_IWOTH
6504 rb_io_stat_data *st = get_stat(obj);
6505 if ((st->ST_(mode) & (S_IWOTH)) == S_IWOTH) {
6506 return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
6507 }
6508#endif
6509 return Qnil;
6510}
6511
6512/*
6513 * call-seq:
6514 * stat.executable? -> true or false
6515 *
6516 * Returns <code>true</code> if <i>stat</i> is executable or if the
6517 * operating system doesn't distinguish executable files from
6518 * nonexecutable files. The tests are made using the effective owner of
6519 * the process.
6520 *
6521 * File.stat("testfile").executable? #=> false
6522 *
6523 */
6524
6525static VALUE
6526rb_stat_x(VALUE obj)
6527{
6528 rb_io_stat_data *st = get_stat(obj);
6529
6530#ifdef USE_GETEUID
6531 if (geteuid() == 0) {
6532 return RBOOL(st->ST_(mode) & S_IXUGO);
6533 }
6534#endif
6535#ifdef S_IXUSR
6536 if (rb_stat_owned(obj))
6537 return RBOOL(st->ST_(mode) & S_IXUSR);
6538#endif
6539#ifdef S_IXGRP
6540 if (rb_stat_grpowned(obj))
6541 return RBOOL(st->ST_(mode) & S_IXGRP);
6542#endif
6543#ifdef S_IXOTH
6544 if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
6545#endif
6546 return Qtrue;
6547}
6548
6549/*
6550 * call-seq:
6551 * stat.executable_real? -> true or false
6552 *
6553 * Same as <code>executable?</code>, but tests using the real owner of
6554 * the process.
6555 */
6556
6557static VALUE
6558rb_stat_X(VALUE obj)
6559{
6560 rb_io_stat_data *st = get_stat(obj);
6561
6562#ifdef USE_GETEUID
6563 if (getuid() == 0) {
6564 return RBOOL(st->ST_(mode) & S_IXUGO);
6565 }
6566#endif
6567#ifdef S_IXUSR
6568 if (rb_stat_rowned(obj))
6569 return RBOOL(st->ST_(mode) & S_IXUSR);
6570#endif
6571#ifdef S_IXGRP
6572 if (rb_group_member(get_stat(obj)->ST_(gid)))
6573 return RBOOL(st->ST_(mode) & S_IXGRP);
6574#endif
6575#ifdef S_IXOTH
6576 if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
6577#endif
6578 return Qtrue;
6579}
6580
6581/*
6582 * call-seq:
6583 * stat.file? -> true or false
6584 *
6585 * Returns <code>true</code> if <i>stat</i> is a regular file (not
6586 * a device file, pipe, socket, etc.).
6587 *
6588 * File.stat("testfile").file? #=> true
6589 *
6590 */
6591
6592static VALUE
6593rb_stat_f(VALUE obj)
6594{
6595 if (S_ISREG(get_stat(obj)->ST_(mode))) return Qtrue;
6596 return Qfalse;
6597}
6598
6599/*
6600 * call-seq:
6601 * stat.zero? -> true or false
6602 *
6603 * Returns <code>true</code> if <i>stat</i> is a zero-length file;
6604 * <code>false</code> otherwise.
6605 *
6606 * File.stat("testfile").zero? #=> false
6607 *
6608 */
6609
6610static VALUE
6611rb_stat_z(VALUE obj)
6612{
6613 if (get_stat(obj)->ST_(size) == 0) return Qtrue;
6614 return Qfalse;
6615}
6616
6617/*
6618 * call-seq:
6619 * stat.size? -> Integer or nil
6620 *
6621 * Returns +nil+ if <i>stat</i> is a zero-length file, the size of
6622 * the file otherwise.
6623 *
6624 * File.stat("testfile").size? #=> 66
6625 * File.stat(File::NULL).size? #=> nil
6626 *
6627 */
6628
6629static VALUE
6630rb_stat_s(VALUE obj)
6631{
6632 rb_off_t size = get_stat(obj)->ST_(size);
6633
6634 if (size == 0) return Qnil;
6635 return OFFT2NUM(size);
6636}
6637
6638/*
6639 * call-seq:
6640 * stat.setuid? -> true or false
6641 *
6642 * Returns <code>true</code> if <i>stat</i> has the set-user-id
6643 * permission bit set, <code>false</code> if it doesn't or if the
6644 * operating system doesn't support this feature.
6645 *
6646 * File.stat("/bin/su").setuid? #=> true
6647 */
6648
6649static VALUE
6650rb_stat_suid(VALUE obj)
6651{
6652#ifdef S_ISUID
6653 if (get_stat(obj)->ST_(mode) & S_ISUID) return Qtrue;
6654#endif
6655 return Qfalse;
6656}
6657
6658/*
6659 * call-seq:
6660 * stat.setgid? -> true or false
6661 *
6662 * Returns <code>true</code> if <i>stat</i> has the set-group-id
6663 * permission bit set, <code>false</code> if it doesn't or if the
6664 * operating system doesn't support this feature.
6665 *
6666 * File.stat("/usr/sbin/lpc").setgid? #=> true
6667 *
6668 */
6669
6670static VALUE
6671rb_stat_sgid(VALUE obj)
6672{
6673#ifdef S_ISGID
6674 if (get_stat(obj)->ST_(mode) & S_ISGID) return Qtrue;
6675#endif
6676 return Qfalse;
6677}
6678
6679/*
6680 * call-seq:
6681 * stat.sticky? -> true or false
6682 *
6683 * Returns <code>true</code> if <i>stat</i> has its sticky bit set,
6684 * <code>false</code> if it doesn't or if the operating system doesn't
6685 * support this feature.
6686 *
6687 * File.stat("testfile").sticky? #=> false
6688 *
6689 */
6690
6691static VALUE
6692rb_stat_sticky(VALUE obj)
6693{
6694#ifdef S_ISVTX
6695 if (get_stat(obj)->ST_(mode) & S_ISVTX) return Qtrue;
6696#endif
6697 return Qfalse;
6698}
6699
6700#if !defined HAVE_MKFIFO && defined HAVE_MKNOD && defined S_IFIFO
6701#define mkfifo(path, mode) mknod(path, (mode)&~S_IFMT|S_IFIFO, 0)
6702#define HAVE_MKFIFO
6703#endif
6704
6705#ifdef HAVE_MKFIFO
6706struct mkfifo_arg {
6707 const char *path;
6708 mode_t mode;
6709};
6710
6711static void *
6712nogvl_mkfifo(void *ptr)
6713{
6714 struct mkfifo_arg *ma = ptr;
6715
6716 return (void *)(VALUE)mkfifo(ma->path, ma->mode);
6717}
6718
6719/*
6720 * call-seq:
6721 * File.mkfifo(file_name, mode=0666) => 0
6722 *
6723 * Creates a FIFO special file with name _file_name_. _mode_
6724 * specifies the FIFO's permissions. It is modified by the process's
6725 * umask in the usual way: the permissions of the created file are
6726 * (mode & ~umask).
6727 */
6728
6729static VALUE
6730rb_file_s_mkfifo(int argc, VALUE *argv, VALUE _)
6731{
6732 VALUE path;
6733 struct mkfifo_arg ma;
6734
6735 ma.mode = 0666;
6736 rb_check_arity(argc, 1, 2);
6737 if (argc > 1) {
6738 ma.mode = NUM2MODET(argv[1]);
6739 }
6740 path = argv[0];
6741 FilePathValue(path);
6742 path = rb_str_encode_ospath(path);
6743 ma.path = RSTRING_PTR(path);
6744 if (IO_WITHOUT_GVL(nogvl_mkfifo, &ma)) {
6745 rb_sys_fail_path(path);
6746 }
6747 return INT2FIX(0);
6748}
6749#else
6750#define rb_file_s_mkfifo rb_f_notimplement
6751#endif
6752
6753static VALUE rb_mFConst;
6754
6755void
6756rb_file_const(const char *name, VALUE value)
6757{
6758 rb_define_const(rb_mFConst, name, value);
6759}
6760
6761int
6762rb_is_absolute_path(const char *path)
6763{
6764#ifdef DOSISH_DRIVE_LETTER
6765 if (has_drive_letter(path) && isdirsep(path[2])) return 1;
6766#endif
6767#ifdef DOSISH_UNC
6768 if (isdirsep(path[0]) && isdirsep(path[1])) return 1;
6769#endif
6770#ifndef DOSISH
6771 if (path[0] == '/') return 1;
6772#endif
6773 return 0;
6774}
6775
6776int
6777ruby_is_fd_loadable(int fd)
6778{
6779#ifdef _WIN32
6780 return 1;
6781#else
6782 struct stat st;
6783
6784 if (fstat(fd, &st) < 0)
6785 return 0;
6786
6787 if (S_ISREG(st.st_mode))
6788 return 1;
6789
6790 if (S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode))
6791 return -1;
6792
6793 if (S_ISDIR(st.st_mode))
6794 errno = EISDIR;
6795 else
6796 errno = ENXIO;
6797
6798 return 0;
6799#endif
6800}
6801
6802#ifndef _WIN32
6803int
6804rb_file_load_ok(const char *path)
6805{
6806 int ret = 1;
6807 /*
6808 open(2) may block if path is FIFO and it's empty. Let's use O_NONBLOCK.
6809 FIXME: Why O_NDELAY is checked?
6810 */
6811 int mode = (O_RDONLY |
6812#if defined O_NONBLOCK
6813 O_NONBLOCK |
6814#elif defined O_NDELAY
6815 O_NDELAY |
6816#endif
6817 0);
6818 int fd = rb_cloexec_open(path, mode, 0);
6819 if (fd < 0) {
6820 if (!rb_gc_for_fd(errno)) return 0;
6821 fd = rb_cloexec_open(path, mode, 0);
6822 if (fd < 0) return 0;
6823 }
6824 rb_update_max_fd(fd);
6825 ret = ruby_is_fd_loadable(fd);
6826 (void)close(fd);
6827 return ret;
6828}
6829#endif
6830
6831static int
6832is_explicit_relative(const char *path)
6833{
6834 if (*path++ != '.') return 0;
6835 if (*path == '.') path++;
6836 return isdirsep(*path);
6837}
6838
6839static VALUE
6840copy_path_class(VALUE path, VALUE orig)
6841{
6842 int encidx = rb_enc_get_index(orig);
6843 if (encidx == ENCINDEX_ASCII_8BIT || encidx == ENCINDEX_US_ASCII)
6844 encidx = rb_filesystem_encindex();
6845 rb_enc_associate_index(path, encidx);
6846 str_shrink(path);
6847 RBASIC_SET_CLASS(path, rb_obj_class(orig));
6848 OBJ_FREEZE(path);
6849 return path;
6850}
6851
6852int
6853rb_find_file_ext(VALUE *filep, const char *const *ext)
6854{
6855 const char *f = StringValueCStr(*filep);
6856 VALUE fname = *filep, load_path, tmp;
6857 long i, j, fnlen;
6858 int expanded = 0;
6859
6860 if (!ext[0]) return 0;
6861
6862 if (f[0] == '~') {
6863 fname = file_expand_path_1(fname);
6864 f = RSTRING_PTR(fname);
6865 *filep = fname;
6866 expanded = 1;
6867 }
6868
6869 if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) {
6870 if (!expanded) fname = file_expand_path_1(fname);
6871 fnlen = RSTRING_LEN(fname);
6872 for (i=0; ext[i]; i++) {
6873 rb_str_cat2(fname, ext[i]);
6874 if (rb_file_load_ok(RSTRING_PTR(fname))) {
6875 *filep = copy_path_class(fname, *filep);
6876 return (int)(i+1);
6877 }
6878 rb_str_set_len(fname, fnlen);
6879 }
6880 return 0;
6881 }
6882
6883 RB_GC_GUARD(load_path) = rb_get_expanded_load_path();
6884 if (!load_path) return 0;
6885
6886 fname = rb_str_dup(*filep);
6887 RBASIC_CLEAR_CLASS(fname);
6888 fnlen = RSTRING_LEN(fname);
6889 tmp = rb_str_tmp_new(MAXPATHLEN + 2);
6890 rb_enc_associate_index(tmp, rb_usascii_encindex());
6891 for (j=0; ext[j]; j++) {
6892 rb_str_cat2(fname, ext[j]);
6893 for (i = 0; i < RARRAY_LEN(load_path); i++) {
6894 VALUE str = RARRAY_AREF(load_path, i);
6895
6896 RB_GC_GUARD(str) = rb_get_path(str);
6897 if (RSTRING_LEN(str) == 0) continue;
6898 rb_file_expand_path_internal(fname, str, 0, 0, tmp);
6899 if (rb_file_load_ok(RSTRING_PTR(tmp))) {
6900 *filep = copy_path_class(tmp, *filep);
6901 return (int)(j+1);
6902 }
6903 }
6904 rb_str_set_len(fname, fnlen);
6905 }
6906 rb_str_resize(tmp, 0);
6907 RB_GC_GUARD(load_path);
6908 return 0;
6909}
6910
6911VALUE
6912rb_find_file(VALUE path)
6913{
6914 VALUE tmp, load_path;
6915 const char *f = StringValueCStr(path);
6916 int expanded = 0;
6917
6918 if (f[0] == '~') {
6919 tmp = file_expand_path_1(path);
6920 path = copy_path_class(tmp, path);
6921 f = RSTRING_PTR(path);
6922 expanded = 1;
6923 }
6924
6925 if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) {
6926 if (!rb_file_load_ok(f)) return 0;
6927 if (!expanded)
6928 path = copy_path_class(file_expand_path_1(path), path);
6929 return path;
6930 }
6931
6932 RB_GC_GUARD(load_path) = rb_get_expanded_load_path();
6933 if (load_path) {
6934 long i;
6935
6936 tmp = rb_str_tmp_new(MAXPATHLEN + 2);
6937 rb_enc_associate_index(tmp, rb_usascii_encindex());
6938 for (i = 0; i < RARRAY_LEN(load_path); i++) {
6939 VALUE str = RARRAY_AREF(load_path, i);
6940 RB_GC_GUARD(str) = rb_get_path(str);
6941 if (RSTRING_LEN(str) > 0) {
6942 rb_file_expand_path_internal(path, str, 0, 0, tmp);
6943 f = RSTRING_PTR(tmp);
6944 if (rb_file_load_ok(f)) goto found;
6945 }
6946 }
6947 rb_str_resize(tmp, 0);
6948 return 0;
6949 }
6950 else {
6951 return 0; /* no path, no load */
6952 }
6953
6954 found:
6955 return copy_path_class(tmp, path);
6956}
6957
6958#define define_filetest_function(name, func, argc) do { \
6959 rb_define_module_function(rb_mFileTest, name, func, argc); \
6960 rb_define_singleton_method(rb_cFile, name, func, argc); \
6961} while(false)
6962
6963const char ruby_null_device[] =
6964#if defined DOSISH
6965 "NUL"
6966#elif defined AMIGA || defined __amigaos__
6967 "NIL"
6968#elif defined __VMS
6969 "NL:"
6970#else
6971 "/dev/null"
6972#endif
6973 ;
6974
6975/*
6976 * A \File object is a representation of a file in the underlying platform.
6977 *
6978 * Class \File extends module FileTest, supporting such singleton methods
6979 * as <tt>File.exist?</tt>.
6980 *
6981 * == About the Examples
6982 *
6983 * Many examples here use these variables:
6984 *
6985 * :include: doc/examples/files.rdoc
6986 *
6987 * == Access Modes
6988 *
6989 * Methods File.new and File.open each create a \File object for a given file path.
6990 *
6991 * === \String Access Modes
6992 *
6993 * Methods File.new and File.open each may take string argument +mode+, which:
6994 *
6995 * - Begins with a 1- or 2-character
6996 * {read/write mode}[rdoc-ref:File@ReadWrite+Mode].
6997 * - May also contain a 1-character {data mode}[rdoc-ref:File@Data+Mode].
6998 * - May also contain a 1-character
6999 * {file-create mode}[rdoc-ref:File@File-Create+Mode].
7000 *
7001 * ==== Read/Write Mode
7002 *
7003 * The read/write +mode+ determines:
7004 *
7005 * - Whether the file is to be initially truncated.
7006 *
7007 * - Whether reading is allowed, and if so:
7008 *
7009 * - The initial read position in the file.
7010 * - Where in the file reading can occur.
7011 *
7012 * - Whether writing is allowed, and if so:
7013 *
7014 * - The initial write position in the file.
7015 * - Where in the file writing can occur.
7016 *
7017 * These tables summarize:
7018 *
7019 * Read/Write Modes for Existing File
7020 *
7021 * |------|-----------|----------|----------|----------|-----------|
7022 * | R/W | Initial | | Initial | | Initial |
7023 * | Mode | Truncate? | Read | Read Pos | Write | Write Pos |
7024 * |------|-----------|----------|----------|----------|-----------|
7025 * | 'r' | No | Anywhere | 0 | Error | - |
7026 * | 'w' | Yes | Error | - | Anywhere | 0 |
7027 * | 'a' | No | Error | - | End only | End |
7028 * | 'r+' | No | Anywhere | 0 | Anywhere | 0 |
7029 * | 'w+' | Yes | Anywhere | 0 | Anywhere | 0 |
7030 * | 'a+' | No | Anywhere | End | End only | End |
7031 * |------|-----------|----------|----------|----------|-----------|
7032 *
7033 * Read/Write Modes for \File To Be Created
7034 *
7035 * |------|----------|----------|----------|-----------|
7036 * | R/W | | Initial | | Initial |
7037 * | Mode | Read | Read Pos | Write | Write Pos |
7038 * |------|----------|----------|----------|-----------|
7039 * | 'w' | Error | - | Anywhere | 0 |
7040 * | 'a' | Error | - | End only | 0 |
7041 * | 'w+' | Anywhere | 0 | Anywhere | 0 |
7042 * | 'a+' | Anywhere | 0 | End only | End |
7043 * |------|----------|----------|----------|-----------|
7044 *
7045 * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
7046 * for a non-existent file (exception raised).
7047 *
7048 * In the tables:
7049 *
7050 * - +Anywhere+ means that methods IO#rewind, IO#pos=, and IO#seek
7051 * may be used to change the file's position,
7052 * so that allowed reading or writing may occur anywhere in the file.
7053 * - <tt>End only</tt> means that writing can occur only at end-of-file,
7054 * and that methods IO#rewind, IO#pos=, and IO#seek do not affect writing.
7055 * - +Error+ means that an exception is raised if disallowed reading or writing
7056 * is attempted.
7057 *
7058 * ===== Read/Write Modes for Existing \File
7059 *
7060 * - <tt>'r'</tt>:
7061 *
7062 * - \File is not initially truncated:
7063 *
7064 * f = File.new('t.txt') # => #<File:t.txt>
7065 * f.size == 0 # => false
7066 *
7067 * - File's initial read position is 0:
7068 *
7069 * f.pos # => 0
7070 *
7071 * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
7072 *
7073 * f.readline # => "First line\n"
7074 * f.readline # => "Second line\n"
7075 *
7076 * f.rewind
7077 * f.readline # => "First line\n"
7078 *
7079 * f.pos = 1
7080 * f.readline # => "irst line\n"
7081 *
7082 * f.seek(1, :CUR)
7083 * f.readline # => "econd line\n"
7084 *
7085 * - Writing is not allowed:
7086 *
7087 * f.write('foo') # Raises IOError.
7088 *
7089 * - <tt>'w'</tt>:
7090 *
7091 * - \File is initially truncated:
7092 *
7093 * path = 't.tmp'
7094 * File.write(path, text)
7095 * f = File.new(path, 'w')
7096 * f.size == 0 # => true
7097 *
7098 * - File's initial write position is 0:
7099 *
7100 * f.pos # => 0
7101 *
7102 * - \File may be written anywhere (even past end-of-file);
7103 * see IO#rewind, IO#pos=, IO#seek:
7104 *
7105 * f.write('foo')
7106 * f.flush
7107 * File.read(path) # => "foo"
7108 * f.pos # => 3
7109 *
7110 * f.write('bar')
7111 * f.flush
7112 * File.read(path) # => "foobar"
7113 * f.pos # => 6
7114 *
7115 * f.rewind
7116 * f.write('baz')
7117 * f.flush
7118 * File.read(path) # => "bazbar"
7119 * f.pos # => 3
7120 *
7121 * f.pos = 3
7122 * f.write('foo')
7123 * f.flush
7124 * File.read(path) # => "bazfoo"
7125 * f.pos # => 6
7126 *
7127 * f.seek(-3, :END)
7128 * f.write('bam')
7129 * f.flush
7130 * File.read(path) # => "bazbam"
7131 * f.pos # => 6
7132 *
7133 * f.pos = 8
7134 * f.write('bah') # Zero padding as needed.
7135 * f.flush
7136 * File.read(path) # => "bazbam\u0000\u0000bah"
7137 * f.pos # => 11
7138 *
7139 * - Reading is not allowed:
7140 *
7141 * f.read # Raises IOError.
7142 *
7143 * - <tt>'a'</tt>:
7144 *
7145 * - \File is not initially truncated:
7146 *
7147 * path = 't.tmp'
7148 * File.write(path, 'foo')
7149 * f = File.new(path, 'a')
7150 * f.size == 0 # => false
7151 *
7152 * - File's initial position is 0 (but is ignored):
7153 *
7154 * f.pos # => 0
7155 *
7156 * - \File may be written only at end-of-file;
7157 * IO#rewind, IO#pos=, IO#seek do not affect writing:
7158 *
7159 * f.write('bar')
7160 * f.flush
7161 * File.read(path) # => "foobar"
7162 * f.write('baz')
7163 * f.flush
7164 * File.read(path) # => "foobarbaz"
7165 *
7166 * f.rewind
7167 * f.write('bat')
7168 * f.flush
7169 * File.read(path) # => "foobarbazbat"
7170 *
7171 * - Reading is not allowed:
7172 *
7173 * f.read # Raises IOError.
7174 *
7175 * - <tt>'r+'</tt>:
7176 *
7177 * - \File is not initially truncated:
7178 *
7179 * path = 't.tmp'
7180 * File.write(path, text)
7181 * f = File.new(path, 'r+')
7182 * f.size == 0 # => false
7183 *
7184 * - File's initial read position is 0:
7185 *
7186 * f.pos # => 0
7187 *
7188 * - \File may be read or written anywhere (even past end-of-file);
7189 * see IO#rewind, IO#pos=, IO#seek:
7190 *
7191 * f.readline # => "First line\n"
7192 * f.readline # => "Second line\n"
7193 *
7194 * f.rewind
7195 * f.readline # => "First line\n"
7196 *
7197 * f.pos = 1
7198 * f.readline # => "irst line\n"
7199 *
7200 * f.seek(1, :CUR)
7201 * f.readline # => "econd line\n"
7202 *
7203 * f.rewind
7204 * f.write('WWW')
7205 * f.flush
7206 * File.read(path)
7207 * # => "WWWst line\nSecond line\nFourth line\nFifth line\n"
7208 *
7209 * f.pos = 10
7210 * f.write('XXX')
7211 * f.flush
7212 * File.read(path)
7213 * # => "WWWst lineXXXecond line\nFourth line\nFifth line\n"
7214 *
7215 * f.seek(-6, :END)
7216 * # => 0
7217 * f.write('YYY')
7218 * # => 3
7219 * f.flush
7220 * # => #<File:t.tmp>
7221 * File.read(path)
7222 * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n"
7223 *
7224 * f.seek(2, :END)
7225 * f.write('ZZZ') # Zero padding as needed.
7226 * f.flush
7227 * File.read(path)
7228 * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n\u0000\u0000ZZZ"
7229 *
7230 *
7231 * - <tt>'a+'</tt>:
7232 *
7233 * - \File is not initially truncated:
7234 *
7235 * path = 't.tmp'
7236 * File.write(path, 'foo')
7237 * f = File.new(path, 'a+')
7238 * f.size == 0 # => false
7239 *
7240 * - File's initial read position is 0:
7241 *
7242 * f.pos # => 0
7243 *
7244 * - \File may be written only at end-of-file;
7245 * IO#rewind, IO#pos=, IO#seek do not affect writing:
7246 *
7247 * f.write('bar')
7248 * f.flush
7249 * File.read(path) # => "foobar"
7250 * f.write('baz')
7251 * f.flush
7252 * File.read(path) # => "foobarbaz"
7253 *
7254 * f.rewind
7255 * f.write('bat')
7256 * f.flush
7257 * File.read(path) # => "foobarbazbat"
7258 *
7259 * - \File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
7260 *
7261 * f.rewind
7262 * f.read # => "foobarbazbat"
7263 *
7264 * f.pos = 3
7265 * f.read # => "barbazbat"
7266 *
7267 * f.seek(-3, :END)
7268 * f.read # => "bat"
7269 *
7270 * ===== Read/Write Modes for \File To Be Created
7271 *
7272 * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
7273 * for a non-existent file (exception raised).
7274 *
7275 * - <tt>'w'</tt>:
7276 *
7277 * - File's initial write position is 0:
7278 *
7279 * path = 't.tmp'
7280 * FileUtils.rm_f(path)
7281 * f = File.new(path, 'w')
7282 * f.pos # => 0
7283 *
7284 * - \File may be written anywhere (even past end-of-file);
7285 * see IO#rewind, IO#pos=, IO#seek:
7286 *
7287 * f.write('foo')
7288 * f.flush
7289 * File.read(path) # => "foo"
7290 * f.pos # => 3
7291 *
7292 * f.write('bar')
7293 * f.flush
7294 * File.read(path) # => "foobar"
7295 * f.pos # => 6
7296 *
7297 * f.rewind
7298 * f.write('baz')
7299 * f.flush
7300 * File.read(path) # => "bazbar"
7301 * f.pos # => 3
7302 *
7303 * f.pos = 3
7304 * f.write('foo')
7305 * f.flush
7306 * File.read(path) # => "bazfoo"
7307 * f.pos # => 6
7308 *
7309 * f.seek(-3, :END)
7310 * f.write('bam')
7311 * f.flush
7312 * File.read(path) # => "bazbam"
7313 * f.pos # => 6
7314 *
7315 * f.pos = 8
7316 * f.write('bah') # Zero padding as needed.
7317 * f.flush
7318 * File.read(path) # => "bazbam\u0000\u0000bah"
7319 * f.pos # => 11
7320 *
7321 * - Reading is not allowed:
7322 *
7323 * f.read # Raises IOError.
7324 *
7325 * - <tt>'a'</tt>:
7326 *
7327 * - File's initial write position is 0:
7328 *
7329 * path = 't.tmp'
7330 * FileUtils.rm_f(path)
7331 * f = File.new(path, 'a')
7332 * f.pos # => 0
7333 *
7334 * - Writing occurs only at end-of-file:
7335 *
7336 * f.write('foo')
7337 * f.pos # => 3
7338 * f.write('bar')
7339 * f.pos # => 6
7340 * f.flush
7341 * File.read(path) # => "foobar"
7342 *
7343 * f.rewind
7344 * f.write('baz')
7345 * f.flush
7346 * File.read(path) # => "foobarbaz"
7347 *
7348 * - Reading is not allowed:
7349 *
7350 * f.read # Raises IOError.
7351 *
7352 * - <tt>'w+'</tt>:
7353 *
7354 * - File's initial position is 0:
7355 *
7356 * path = 't.tmp'
7357 * FileUtils.rm_f(path)
7358 * f = File.new(path, 'w+')
7359 * f.pos # => 0
7360 *
7361 * - \File may be written anywhere (even past end-of-file);
7362 * see IO#rewind, IO#pos=, IO#seek:
7363 *
7364 * f.write('foo')
7365 * f.flush
7366 * File.read(path) # => "foo"
7367 * f.pos # => 3
7368 *
7369 * f.write('bar')
7370 * f.flush
7371 * File.read(path) # => "foobar"
7372 * f.pos # => 6
7373 *
7374 * f.rewind
7375 * f.write('baz')
7376 * f.flush
7377 * File.read(path) # => "bazbar"
7378 * f.pos # => 3
7379 *
7380 * f.pos = 3
7381 * f.write('foo')
7382 * f.flush
7383 * File.read(path) # => "bazfoo"
7384 * f.pos # => 6
7385 *
7386 * f.seek(-3, :END)
7387 * f.write('bam')
7388 * f.flush
7389 * File.read(path) # => "bazbam"
7390 * f.pos # => 6
7391 *
7392 * f.pos = 8
7393 * f.write('bah') # Zero padding as needed.
7394 * f.flush
7395 * File.read(path) # => "bazbam\u0000\u0000bah"
7396 * f.pos # => 11
7397 *
7398 * - \File may be read anywhere (even past end-of-file);
7399 * see IO#rewind, IO#pos=, IO#seek:
7400 *
7401 * f.rewind
7402 * # => 0
7403 * f.read
7404 * # => "bazbam\u0000\u0000bah"
7405 *
7406 * f.pos = 3
7407 * # => 3
7408 * f.read
7409 * # => "bam\u0000\u0000bah"
7410 *
7411 * f.seek(-3, :END)
7412 * # => 0
7413 * f.read
7414 * # => "bah"
7415 *
7416 * - <tt>'a+'</tt>:
7417 *
7418 * - File's initial write position is 0:
7419 *
7420 * path = 't.tmp'
7421 * FileUtils.rm_f(path)
7422 * f = File.new(path, 'a+')
7423 * f.pos # => 0
7424 *
7425 * - Writing occurs only at end-of-file:
7426 *
7427 * f.write('foo')
7428 * f.pos # => 3
7429 * f.write('bar')
7430 * f.pos # => 6
7431 * f.flush
7432 * File.read(path) # => "foobar"
7433 *
7434 * f.rewind
7435 * f.write('baz')
7436 * f.flush
7437 * File.read(path) # => "foobarbaz"
7438 *
7439 * - \File may be read anywhere (even past end-of-file);
7440 * see IO#rewind, IO#pos=, IO#seek:
7441 *
7442 * f.rewind
7443 * f.read # => "foobarbaz"
7444 *
7445 * f.pos = 3
7446 * f.read # => "barbaz"
7447 *
7448 * f.seek(-3, :END)
7449 * f.read # => "baz"
7450 *
7451 * f.pos = 800
7452 * f.read # => ""
7453 *
7454 * ==== \Data Mode
7455 *
7456 * To specify whether data is to be treated as text or as binary data,
7457 * either of the following may be suffixed to any of the string read/write modes
7458 * above:
7459 *
7460 * - <tt>'t'</tt>: Text data; sets the default external encoding
7461 * to <tt>Encoding::UTF_8</tt>;
7462 * on Windows, enables conversion between EOL and CRLF
7463 * and enables interpreting <tt>0x1A</tt> as an end-of-file marker.
7464 * - <tt>'b'</tt>: Binary data; sets the default external encoding
7465 * to <tt>Encoding::ASCII_8BIT</tt>;
7466 * on Windows, suppresses conversion between EOL and CRLF
7467 * and disables interpreting <tt>0x1A</tt> as an end-of-file marker.
7468 *
7469 * If neither is given, the stream defaults to text data.
7470 *
7471 * Examples:
7472 *
7473 * File.new('t.txt', 'rt')
7474 * File.new('t.dat', 'rb')
7475 *
7476 * When the data mode is specified, the read/write mode may not be omitted,
7477 * and the data mode must precede the file-create mode, if given:
7478 *
7479 * File.new('t.dat', 'b') # Raises an exception.
7480 * File.new('t.dat', 'rxb') # Raises an exception.
7481 *
7482 * ==== \File-Create Mode
7483 *
7484 * The following may be suffixed to any writable string mode above:
7485 *
7486 * - <tt>'x'</tt>: Creates the file if it does not exist;
7487 * raises an exception if the file exists.
7488 *
7489 * Example:
7490 *
7491 * File.new('t.tmp', 'wx')
7492 *
7493 * When the file-create mode is specified, the read/write mode may not be omitted,
7494 * and the file-create mode must follow the data mode:
7495 *
7496 * File.new('t.dat', 'x') # Raises an exception.
7497 * File.new('t.dat', 'rxb') # Raises an exception.
7498 *
7499 * === \Integer Access Modes
7500 *
7501 * When mode is an integer it must be one or more of the following constants,
7502 * which may be combined by the bitwise OR operator <tt>|</tt>:
7503 *
7504 * - +File::RDONLY+: Open for reading only.
7505 * - +File::WRONLY+: Open for writing only.
7506 * - +File::RDWR+: Open for reading and writing.
7507 * - +File::APPEND+: Open for appending only.
7508 *
7509 * Examples:
7510 *
7511 * File.new('t.txt', File::RDONLY)
7512 * File.new('t.tmp', File::RDWR | File::CREAT | File::EXCL)
7513 *
7514 * Note: Method IO#set_encoding does not allow the mode to be specified as an integer.
7515 *
7516 * === File-Create Mode Specified as an \Integer
7517 *
7518 * These constants may also be ORed into the integer mode:
7519 *
7520 * - +File::CREAT+: Create file if it does not exist.
7521 * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists.
7522 *
7523 * === \Data Mode Specified as an \Integer
7524 *
7525 * \Data mode cannot be specified as an integer.
7526 * When the stream access mode is given as an integer,
7527 * the data mode is always text, never binary.
7528 *
7529 * Note that although there is a constant +File::BINARY+,
7530 * setting its value in an integer stream mode has no effect;
7531 * this is because, as documented in File::Constants,
7532 * the +File::BINARY+ value disables line code conversion,
7533 * but does not change the external encoding.
7534 *
7535 * === Encodings
7536 *
7537 * Any of the string modes above may specify encodings -
7538 * either external encoding only or both external and internal encodings -
7539 * by appending one or both encoding names, separated by colons:
7540 *
7541 * f = File.new('t.dat', 'rb')
7542 * f.external_encoding # => #<Encoding:ASCII-8BIT>
7543 * f.internal_encoding # => nil
7544 * f = File.new('t.dat', 'rb:UTF-16')
7545 * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
7546 * f.internal_encoding # => nil
7547 * f = File.new('t.dat', 'rb:UTF-16:UTF-16')
7548 * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
7549 * f.internal_encoding # => #<Encoding:UTF-16>
7550 * f.close
7551 *
7552 * The numerous encoding names are available in array Encoding.name_list:
7553 *
7554 * Encoding.name_list.take(3) # => ["ASCII-8BIT", "UTF-8", "US-ASCII"]
7555 *
7556 * When the external encoding is set, strings read are tagged by that encoding
7557 * when reading, and strings written are converted to that encoding when
7558 * writing.
7559 *
7560 * When both external and internal encodings are set,
7561 * strings read are converted from external to internal encoding,
7562 * and strings written are converted from internal to external encoding.
7563 * For further details about transcoding input and output,
7564 * see {Encodings}[rdoc-ref:encodings.rdoc@Encodings].
7565 *
7566 * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt>
7567 * or <tt>'BOM|UTF16-BE'</tt>,
7568 * Ruby checks for a Unicode BOM in the input document
7569 * to help determine the encoding.
7570 * For UTF-16 encodings the file open mode must be binary.
7571 * If the BOM is found,
7572 * it is stripped and the external encoding from the BOM is used.
7573 *
7574 * Note that the BOM-style encoding option is case insensitive,
7575 * so <tt>'bom|utf-8'</tt> is also valid.
7576 *
7577 * == \File Permissions
7578 *
7579 * A \File object has _permissions_, an octal integer representing
7580 * the permissions of an actual file in the underlying platform.
7581 *
7582 * Note that file permissions are quite different from the _mode_
7583 * of a file stream (\File object).
7584 *
7585 * In a \File object, the permissions are available thus,
7586 * where method +mode+, despite its name, returns permissions:
7587 *
7588 * f = File.new('t.txt')
7589 * f.lstat.mode.to_s(8) # => "100644"
7590 *
7591 * On a Unix-based operating system,
7592 * the three low-order octal digits represent the permissions
7593 * for owner (6), group (4), and world (4).
7594 * The triplet of bits in each octal digit represent, respectively,
7595 * read, write, and execute permissions.
7596 *
7597 * Permissions <tt>0644</tt> thus represent read-write access for owner
7598 * and read-only access for group and world.
7599 * See man pages {open(2)}[https://www.unix.com/man-page/bsd/2/open]
7600 * and {chmod(2)}[https://www.unix.com/man-page/bsd/2/chmod].
7601 *
7602 * For a directory, the meaning of the execute bit changes:
7603 * when set, the directory can be searched.
7604 *
7605 * Higher-order bits in permissions may indicate the type of file
7606 * (plain, directory, pipe, socket, etc.) and various other special features.
7607 *
7608 * On non-Posix operating systems, permissions may include only read-only or read-write,
7609 * in which case, the remaining permission will resemble typical values.
7610 * On Windows, for instance, the default permissions are <code>0644</code>;
7611 * The only change that can be made is to make the file
7612 * read-only, which is reported as <code>0444</code>.
7613 *
7614 * For a method that actually creates a file in the underlying platform
7615 * (as opposed to merely creating a \File object),
7616 * permissions may be specified:
7617 *
7618 * File.new('t.tmp', File::CREAT, 0644)
7619 * File.new('t.tmp', File::CREAT, 0444)
7620 *
7621 * Permissions may also be changed:
7622 *
7623 * f = File.new('t.tmp', File::CREAT, 0444)
7624 * f.chmod(0644)
7625 * f.chmod(0444)
7626 *
7627 * == \File \Constants
7628 *
7629 * Various constants for use in \File and IO methods
7630 * may be found in module File::Constants;
7631 * an array of their names is returned by <tt>File::Constants.constants</tt>.
7632 *
7633 * == What's Here
7634 *
7635 * First, what's elsewhere. Class \File:
7636 *
7637 * - Inherits from {class IO}[rdoc-ref:IO@Whats+Here],
7638 * in particular, methods for creating, reading, and writing files
7639 * - Includes module FileTest,
7640 * which provides dozens of additional methods.
7641 *
7642 * Here, class \File provides methods that are useful for:
7643 *
7644 * - {Creating}[rdoc-ref:File@Creating]
7645 * - {Querying}[rdoc-ref:File@Querying]
7646 * - {Settings}[rdoc-ref:File@Settings]
7647 * - {Other}[rdoc-ref:File@Other]
7648 *
7649 * === Creating
7650 *
7651 * - ::new: Opens the file at the given path; returns the file.
7652 * - ::open: Same as ::new, but when given a block will yield the file to the block,
7653 * and close the file upon exiting the block.
7654 * - ::link: Creates a new name for an existing file using a hard link.
7655 * - ::mkfifo: Returns the FIFO file created at the given path.
7656 * - ::symlink: Creates a symbolic link for the given file path.
7657 *
7658 * === Querying
7659 *
7660 * _Paths_
7661 *
7662 * - ::absolute_path: Returns the absolute file path for the given path.
7663 * - ::absolute_path?: Returns whether the given path is the absolute file path.
7664 * - ::basename: Returns the last component of the given file path.
7665 * - ::dirname: Returns all but the last component of the given file path.
7666 * - ::expand_path: Returns the absolute file path for the given path,
7667 * expanding <tt>~</tt> for a home directory.
7668 * - ::extname: Returns the file extension for the given file path.
7669 * - ::fnmatch? (aliased as ::fnmatch): Returns whether the given file path
7670 * matches the given pattern.
7671 * - ::join: Joins path components into a single path string.
7672 * - ::path: Returns the string representation of the given path.
7673 * - ::readlink: Returns the path to the file at the given symbolic link.
7674 * - ::realdirpath: Returns the real path for the given file path,
7675 * where the last component need not exist.
7676 * - ::realpath: Returns the real path for the given file path,
7677 * where all components must exist.
7678 * - ::split: Returns an array of two strings: the directory name and basename
7679 * of the file at the given path.
7680 * - #path (aliased as #to_path): Returns the string representation of the given path.
7681 *
7682 * _Times_
7683 *
7684 * - ::atime: Returns a Time for the most recent access to the given file.
7685 * - ::birthtime: Returns a Time for the creation of the given file.
7686 * - ::ctime: Returns a Time for the metadata change of the given file.
7687 * - ::mtime: Returns a Time for the most recent data modification to
7688 * the content of the given file.
7689 * - #atime: Returns a Time for the most recent access to +self+.
7690 * - #birthtime: Returns a Time the creation for +self+.
7691 * - #ctime: Returns a Time for the metadata change of +self+.
7692 * - #mtime: Returns a Time for the most recent data modification
7693 * to the content of +self+.
7694 *
7695 * _Types_
7696 *
7697 * - ::blockdev?: Returns whether the file at the given path is a block device.
7698 * - ::chardev?: Returns whether the file at the given path is a character device.
7699 * - ::directory?: Returns whether the file at the given path is a directory.
7700 * - ::executable?: Returns whether the file at the given path is executable
7701 * by the effective user and group of the current process.
7702 * - ::executable_real?: Returns whether the file at the given path is executable
7703 * by the real user and group of the current process.
7704 * - ::exist?: Returns whether the file at the given path exists.
7705 * - ::file?: Returns whether the file at the given path is a regular file.
7706 * - ::ftype: Returns a string giving the type of the file at the given path.
7707 * - ::grpowned?: Returns whether the effective group of the current process
7708 * owns the file at the given path.
7709 * - ::identical?: Returns whether the files at two given paths are identical.
7710 * - ::lstat: Returns the File::Stat object for the last symbolic link
7711 * in the given path.
7712 * - ::owned?: Returns whether the effective user of the current process
7713 * owns the file at the given path.
7714 * - ::pipe?: Returns whether the file at the given path is a pipe.
7715 * - ::readable?: Returns whether the file at the given path is readable
7716 * by the effective user and group of the current process.
7717 * - ::readable_real?: Returns whether the file at the given path is readable
7718 * by the real user and group of the current process.
7719 * - ::setgid?: Returns whether the setgid bit is set for the file at the given path.
7720 * - ::setuid?: Returns whether the setuid bit is set for the file at the given path.
7721 * - ::socket?: Returns whether the file at the given path is a socket.
7722 * - ::stat: Returns the File::Stat object for the file at the given path.
7723 * - ::sticky?: Returns whether the file at the given path has its sticky bit set.
7724 * - ::symlink?: Returns whether the file at the given path is a symbolic link.
7725 * - ::umask: Returns the umask value for the current process.
7726 * - ::world_readable?: Returns whether the file at the given path is readable
7727 * by others.
7728 * - ::world_writable?: Returns whether the file at the given path is writable
7729 * by others.
7730 * - ::writable?: Returns whether the file at the given path is writable
7731 * by the effective user and group of the current process.
7732 * - ::writable_real?: Returns whether the file at the given path is writable
7733 * by the real user and group of the current process.
7734 * - #lstat: Returns the File::Stat object for the last symbolic link
7735 * in the path for +self+.
7736 *
7737 * _Contents_
7738 *
7739 * - ::empty? (aliased as ::zero?): Returns whether the file at the given path
7740 * exists and is empty.
7741 * - ::size: Returns the size (bytes) of the file at the given path.
7742 * - ::size?: Returns +nil+ if there is no file at the given path,
7743 * or if that file is empty; otherwise returns the file size (bytes).
7744 * - #size: Returns the size (bytes) of +self+.
7745 *
7746 * === Settings
7747 *
7748 * - ::chmod: Changes permissions of the file at the given path.
7749 * - ::chown: Change ownership of the file at the given path.
7750 * - ::lchmod: Changes permissions of the last symbolic link in the given path.
7751 * - ::lchown: Change ownership of the last symbolic in the given path.
7752 * - ::lutime: For each given file path, sets the access time and modification time
7753 * of the last symbolic link in the path.
7754 * - ::rename: Moves the file at one given path to another given path.
7755 * - ::utime: Sets the access time and modification time of each file
7756 * at the given paths.
7757 * - #flock: Locks or unlocks +self+.
7758 *
7759 * === Other
7760 *
7761 * - ::truncate: Truncates the file at the given file path to the given size.
7762 * - ::unlink (aliased as ::delete): Deletes the file for each given file path.
7763 * - #truncate: Truncates +self+ to the given size.
7764 *
7765 */
7766
7767void
7768Init_File(void)
7769{
7770#if defined(__APPLE__) && defined(HAVE_WORKING_FORK)
7771 rb_CFString_class_initialize_before_fork();
7772#endif
7773
7774 VALUE separator;
7775
7776 rb_mFileTest = rb_define_module("FileTest");
7777 rb_cFile = rb_define_class("File", rb_cIO);
7778
7779 define_filetest_function("directory?", rb_file_directory_p, 1);
7780 define_filetest_function("exist?", rb_file_exist_p, 1);
7781 define_filetest_function("readable?", rb_file_readable_p, 1);
7782 define_filetest_function("readable_real?", rb_file_readable_real_p, 1);
7783 define_filetest_function("world_readable?", rb_file_world_readable_p, 1);
7784 define_filetest_function("writable?", rb_file_writable_p, 1);
7785 define_filetest_function("writable_real?", rb_file_writable_real_p, 1);
7786 define_filetest_function("world_writable?", rb_file_world_writable_p, 1);
7787 define_filetest_function("executable?", rb_file_executable_p, 1);
7788 define_filetest_function("executable_real?", rb_file_executable_real_p, 1);
7789 define_filetest_function("file?", rb_file_file_p, 1);
7790 define_filetest_function("zero?", rb_file_zero_p, 1);
7791 define_filetest_function("empty?", rb_file_zero_p, 1);
7792 define_filetest_function("size?", rb_file_size_p, 1);
7793 define_filetest_function("size", rb_file_s_size, 1);
7794 define_filetest_function("owned?", rb_file_owned_p, 1);
7795 define_filetest_function("grpowned?", rb_file_grpowned_p, 1);
7796
7797 define_filetest_function("pipe?", rb_file_pipe_p, 1);
7798 define_filetest_function("symlink?", rb_file_symlink_p, 1);
7799 define_filetest_function("socket?", rb_file_socket_p, 1);
7800
7801 define_filetest_function("blockdev?", rb_file_blockdev_p, 1);
7802 define_filetest_function("chardev?", rb_file_chardev_p, 1);
7803
7804 define_filetest_function("setuid?", rb_file_suid_p, 1);
7805 define_filetest_function("setgid?", rb_file_sgid_p, 1);
7806 define_filetest_function("sticky?", rb_file_sticky_p, 1);
7807
7808 define_filetest_function("identical?", rb_file_identical_p, 2);
7809
7810 rb_define_singleton_method(rb_cFile, "stat", rb_file_s_stat, 1);
7811 rb_define_singleton_method(rb_cFile, "lstat", rb_file_s_lstat, 1);
7812 rb_define_singleton_method(rb_cFile, "ftype", rb_file_s_ftype, 1);
7813
7814 rb_define_singleton_method(rb_cFile, "atime", rb_file_s_atime, 1);
7815 rb_define_singleton_method(rb_cFile, "mtime", rb_file_s_mtime, 1);
7816 rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1);
7817 rb_define_singleton_method(rb_cFile, "birthtime", rb_file_s_birthtime, 1);
7818
7819 rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1);
7820 rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1);
7821 rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1);
7822 rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
7823 rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1);
7824 rb_define_singleton_method(rb_cFile, "lutime", rb_file_s_lutime, -1);
7825
7826 rb_define_singleton_method(rb_cFile, "link", rb_file_s_link, 2);
7827 rb_define_singleton_method(rb_cFile, "symlink", rb_file_s_symlink, 2);
7828 rb_define_singleton_method(rb_cFile, "readlink", rb_file_s_readlink, 1);
7829
7830 rb_define_singleton_method(rb_cFile, "unlink", rb_file_s_unlink, -1);
7831 rb_define_singleton_method(rb_cFile, "delete", rb_file_s_unlink, -1);
7832 rb_define_singleton_method(rb_cFile, "rename", rb_file_s_rename, 2);
7833 rb_define_singleton_method(rb_cFile, "umask", rb_file_s_umask, -1);
7834 rb_define_singleton_method(rb_cFile, "truncate", rb_file_s_truncate, 2);
7835 rb_define_singleton_method(rb_cFile, "mkfifo", rb_file_s_mkfifo, -1);
7836 rb_define_singleton_method(rb_cFile, "expand_path", s_expand_path, -1);
7837 rb_define_singleton_method(rb_cFile, "absolute_path", s_absolute_path, -1);
7838 rb_define_singleton_method(rb_cFile, "absolute_path?", s_absolute_path_p, 1);
7839 rb_define_singleton_method(rb_cFile, "realpath", rb_file_s_realpath, -1);
7840 rb_define_singleton_method(rb_cFile, "realdirpath", rb_file_s_realdirpath, -1);
7841 rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1);
7842 rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, -1);
7843 rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1);
7844 rb_define_singleton_method(rb_cFile, "path", rb_file_s_path, 1);
7845
7846 separator = rb_fstring_lit("/");
7847 /* separates directory parts in path */
7848 rb_define_const(rb_cFile, "Separator", separator);
7849 /* separates directory parts in path */
7850 rb_define_const(rb_cFile, "SEPARATOR", separator);
7851 rb_define_singleton_method(rb_cFile, "split", rb_file_s_split, 1);
7852 rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -1);
7853
7854#ifdef DOSISH
7855 /* platform specific alternative separator */
7856 rb_define_const(rb_cFile, "ALT_SEPARATOR", rb_obj_freeze(rb_usascii_str_new2(file_alt_separator)));
7857#else
7858 rb_define_const(rb_cFile, "ALT_SEPARATOR", Qnil);
7859#endif
7860 /* path list separator */
7861 rb_define_const(rb_cFile, "PATH_SEPARATOR", rb_fstring_cstr(PATH_SEP));
7862
7863 rb_define_method(rb_cIO, "stat", rb_io_stat, 0); /* this is IO's method */
7864 rb_define_method(rb_cFile, "lstat", rb_file_lstat, 0);
7865
7866 rb_define_method(rb_cFile, "atime", rb_file_atime, 0);
7867 rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0);
7868 rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0);
7869 rb_define_method(rb_cFile, "birthtime", rb_file_birthtime, 0);
7870 rb_define_method(rb_cFile, "size", file_size, 0);
7871
7872 rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1);
7873 rb_define_method(rb_cFile, "chown", rb_file_chown, 2);
7874 rb_define_method(rb_cFile, "truncate", rb_file_truncate, 1);
7875
7876 rb_define_method(rb_cFile, "flock", rb_file_flock, 1);
7877
7878 /*
7879 * Document-module: File::Constants
7880 *
7881 * Module +File::Constants+ defines file-related constants.
7882 *
7883 * There are two families of constants here:
7884 *
7885 * - Those having to do with {file access}[rdoc-ref:File::Constants@File+Access].
7886 * - Those having to do with {filename globbing}[rdoc-ref:File::Constants@Filename+Globbing+Constants+-28File-3A-3AFNM_-2A-29].
7887 *
7888 * \File constants defined for the local process may be retrieved
7889 * with method File::Constants.constants:
7890 *
7891 * File::Constants.constants.take(5)
7892 * # => [:RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT]
7893 *
7894 * == \File Access
7895 *
7896 * \File-access constants may be used with optional argument +mode+ in calls
7897 * to the following methods:
7898 *
7899 * - File.new.
7900 * - File.open.
7901 * - IO.for_fd.
7902 * - IO.new.
7903 * - IO.open.
7904 * - IO.popen.
7905 * - IO.reopen.
7906 * - IO.sysopen.
7907 * - StringIO.new.
7908 * - StringIO.open.
7909 * - StringIO#reopen.
7910 *
7911 * === Read/Write Access
7912 *
7913 * Read-write access for a stream
7914 * may be specified by a file-access constant.
7915 *
7916 * The constant may be specified as part of a bitwise OR of other such constants.
7917 *
7918 * Any combination of the constants in this section may be specified.
7919 *
7920 * ==== File::RDONLY
7921 *
7922 * Flag File::RDONLY specifies the stream should be opened for reading only:
7923 *
7924 * filepath = '/tmp/t.tmp'
7925 * f = File.new(filepath, File::RDONLY)
7926 * f.write('Foo') # Raises IOError (not opened for writing).
7927 *
7928 * ==== File::WRONLY
7929 *
7930 * Flag File::WRONLY specifies that the stream should be opened for writing only:
7931 *
7932 * f = File.new(filepath, File::WRONLY)
7933 * f.read # Raises IOError (not opened for reading).
7934 *
7935 * ==== File::RDWR
7936 *
7937 * Flag File::RDWR specifies that the stream should be opened
7938 * for both reading and writing:
7939 *
7940 * f = File.new(filepath, File::RDWR)
7941 * f.write('Foo') # => 3
7942 * f.rewind # => 0
7943 * f.read # => "Foo"
7944 *
7945 * === \File Positioning
7946 *
7947 * ==== File::APPEND
7948 *
7949 * Flag File::APPEND specifies that the stream should be opened
7950 * in append mode.
7951 *
7952 * Before each write operation, the position is set to end-of-stream.
7953 * The modification of the position and the following write operation
7954 * are performed as a single atomic step.
7955 *
7956 * ==== File::TRUNC
7957 *
7958 * Flag File::TRUNC specifies that the stream should be truncated
7959 * at its beginning.
7960 * If the file exists and is successfully opened for writing,
7961 * it is to be truncated to position zero;
7962 * its ctime and mtime are updated.
7963 *
7964 * There is no effect on a FIFO special file or a terminal device.
7965 * The effect on other file types is implementation-defined.
7966 * The result of using File::TRUNC with File::RDONLY is undefined.
7967 *
7968 * === Creating and Preserving
7969 *
7970 * ==== File::CREAT
7971 *
7972 * Flag File::CREAT specifies that the stream should be created
7973 * if it does not already exist.
7974 *
7975 * If the file exists:
7976 *
7977 * - Raise an exception if File::EXCL is also specified.
7978 * - Otherwise, do nothing.
7979 *
7980 * If the file does not exist, then it is created.
7981 * Upon successful completion, the atime, ctime, and mtime of the file are updated,
7982 * and the ctime and mtime of the parent directory are updated.
7983 *
7984 * ==== File::EXCL
7985 *
7986 * Flag File::EXCL specifies that the stream should not already exist;
7987 * If flags File::CREAT and File::EXCL are both specified
7988 * and the stream already exists, an exception is raised.
7989 *
7990 * The check for the existence and creation of the file is performed as an
7991 * atomic operation.
7992 *
7993 * If both File::EXCL and File::CREAT are specified and the path names a symbolic link,
7994 * an exception is raised regardless of the contents of the symbolic link.
7995 *
7996 * If File::EXCL is specified and File::CREAT is not specified,
7997 * the result is undefined.
7998 *
7999 * === POSIX \File \Constants
8000 *
8001 * Some file-access constants are defined only on POSIX-compliant systems;
8002 * those are:
8003 *
8004 * - File::SYNC.
8005 * - File::DSYNC.
8006 * - File::RSYNC.
8007 * - File::DIRECT.
8008 * - File::NOATIME.
8009 * - File::NOCTTY.
8010 * - File::NOFOLLOW.
8011 * - File::TMPFILE.
8012 *
8013 * ==== File::SYNC, File::RSYNC, and File::DSYNC
8014 *
8015 * Flag File::SYNC, File::RSYNC, or File::DSYNC
8016 * specifies synchronization of I/O operations with the underlying file system.
8017 *
8018 * These flags are valid only for POSIX-compliant systems.
8019 *
8020 * - File::SYNC specifies that all write operations (both data and metadata)
8021 * are immediately to be flushed to the underlying storage device.
8022 * This means that the data is written to the storage device,
8023 * and the file's metadata (e.g., file size, timestamps, permissions)
8024 * are also synchronized.
8025 * This guarantees that data is safely stored on the storage medium
8026 * before returning control to the calling program.
8027 * This flag can have a significant impact on performance
8028 * since it requires synchronous writes, which can be slower
8029 * compared to asynchronous writes.
8030 *
8031 * - File::RSYNC specifies that any read operations on the file will not return
8032 * until all outstanding write operations
8033 * (those that have been issued but not completed) are also synchronized.
8034 * This is useful when you want to read the most up-to-date data,
8035 * which may still be in the process of being written.
8036 *
8037 * - File::DSYNC specifies that all _data_ write operations
8038 * are immediately to be flushed to the underlying storage device;
8039 * this differs from File::SYNC, which requires that _metadata_
8040 * also be synchronized.
8041 *
8042 * Note that the behavior of these flags may vary slightly
8043 * depending on the operating system and filesystem being used.
8044 * Additionally, using these flags can have an impact on performance
8045 * due to the synchronous nature of the I/O operations,
8046 * so they should be used judiciously,
8047 * especially in performance-critical applications.
8048 *
8049 * ==== File::NOCTTY
8050 *
8051 * Flag File::NOCTTY specifies that if the stream is a terminal device,
8052 * that device does not become the controlling terminal for the process.
8053 *
8054 * Defined only for POSIX-compliant systems.
8055 *
8056 * ==== File::DIRECT
8057 *
8058 * Flag File::DIRECT requests that cache effects of the I/O to and from the stream
8059 * be minimized.
8060 *
8061 * Defined only for POSIX-compliant systems.
8062 *
8063 * ==== File::NOATIME
8064 *
8065 * Flag File::NOATIME specifies that act of opening the stream
8066 * should not modify its access time (atime).
8067 *
8068 * Defined only for POSIX-compliant systems.
8069 *
8070 * ==== File::NOFOLLOW
8071 *
8072 * Flag File::NOFOLLOW specifies that if path is a symbolic link,
8073 * it should not be followed.
8074 *
8075 * Defined only for POSIX-compliant systems.
8076 *
8077 * ==== File::TMPFILE
8078 *
8079 * Flag File::TMPFILE specifies that the opened stream
8080 * should be a new temporary file.
8081 *
8082 * Defined only for POSIX-compliant systems.
8083 *
8084 * === Other File-Access \Constants
8085 *
8086 * ==== File::NONBLOCK
8087 *
8088 * When possible, the file is opened in nonblocking mode.
8089 * Neither the open operation nor any subsequent I/O operations on
8090 * the file will cause the calling process to wait.
8091 *
8092 * ==== File::BINARY
8093 *
8094 * Flag File::BINARY specifies that the stream is to be accessed in binary mode.
8095 *
8096 * ==== File::SHARE_DELETE
8097 *
8098 * Flag File::SHARE_DELETE enables other processes to open the stream
8099 * with delete access.
8100 *
8101 * Windows only.
8102 *
8103 * If the stream is opened for (local) delete access without File::SHARE_DELETE,
8104 * and another process attempts to open it with delete access,
8105 * the attempt fails and the stream is not opened for that process.
8106 *
8107 * == Locking
8108 *
8109 * Four file constants relate to stream locking;
8110 * see File#flock:
8111 *
8112 * ==== File::LOCK_EX
8113 *
8114 * Flag File::LOCK_EX specifies an exclusive lock;
8115 * only one process a a time may lock the stream.
8116 *
8117 * ==== File::LOCK_NB
8118 *
8119 * Flag File::LOCK_NB specifies non-blocking locking for the stream;
8120 * may be combined with File::LOCK_EX or File::LOCK_SH.
8121 *
8122 * ==== File::LOCK_SH
8123 *
8124 * Flag File::LOCK_SH specifies that multiple processes may lock
8125 * the stream at the same time.
8126 *
8127 * ==== File::LOCK_UN
8128 *
8129 * Flag File::LOCK_UN specifies that the stream is not to be locked.
8130 *
8131 * == Filename Globbing \Constants (File::FNM_*)
8132 *
8133 * Filename-globbing constants may be used with optional argument +flags+
8134 * in calls to the following methods:
8135 *
8136 * - Dir.glob.
8137 * - File.fnmatch.
8138 * - Pathname#fnmatch.
8139 * - Pathname.glob.
8140 * - Pathname#glob.
8141 *
8142 * The constants are:
8143 *
8144 * ==== File::FNM_CASEFOLD
8145 *
8146 * Flag File::FNM_CASEFOLD makes patterns case insensitive
8147 * for File.fnmatch (but not Dir.glob).
8148 *
8149 * ==== File::FNM_DOTMATCH
8150 *
8151 * Flag File::FNM_DOTMATCH makes the <tt>'*'</tt> pattern
8152 * match a filename starting with <tt>'.'</tt>.
8153 *
8154 * ==== File::FNM_EXTGLOB
8155 *
8156 * Flag File::FNM_EXTGLOB enables pattern <tt>'{a,b}'</tt>,
8157 * which matches pattern '_a_' and pattern '_b_';
8158 * behaves like
8159 * a {regexp union}[rdoc-ref:Regexp.union]
8160 * (e.g., <tt>'(?:a|b)'</tt>):
8161 *
8162 * pattern = '{LEGAL,BSDL}'
8163 * Dir.glob(pattern) # => ["LEGAL", "BSDL"]
8164 * Pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>]
8165 * pathname.glob(pattern) # => [#<Pathname:LEGAL>, #<Pathname:BSDL>]
8166 *
8167 * ==== File::FNM_NOESCAPE
8168 *
8169 * Flag File::FNM_NOESCAPE disables <tt>'\'</tt> escaping.
8170 *
8171 * ==== File::FNM_PATHNAME
8172 *
8173 * Flag File::FNM_PATHNAME specifies that patterns <tt>'*'</tt> and <tt>'?'</tt>
8174 * do not match the directory separator
8175 * (the value of constant File::SEPARATOR).
8176 *
8177 * ==== File::FNM_SHORTNAME
8178 *
8179 * Flag File::FNM_SHORTNAME allows patterns to match short names if they exist.
8180 *
8181 * Windows only.
8182 *
8183 * ==== File::FNM_SYSCASE
8184 *
8185 * Flag File::FNM_SYSCASE specifies that case sensitivity
8186 * is the same as in the underlying operating system;
8187 * effective for File.fnmatch, but not Dir.glob.
8188 *
8189 * == Other \Constants
8190 *
8191 * ==== File::NULL
8192 *
8193 * Flag File::NULL contains the string value of the null device:
8194 *
8195 * - On a Unix-like OS, <tt>'/dev/null'</tt>.
8196 * - On Windows, <tt>'NUL'</tt>.
8197 *
8198 */
8199 rb_mFConst = rb_define_module_under(rb_cFile, "Constants");
8200 rb_include_module(rb_cIO, rb_mFConst);
8201 /* {File::RDONLY}[rdoc-ref:File::Constants@File-3A-3ARDONLY] */
8202 rb_define_const(rb_mFConst, "RDONLY", INT2FIX(O_RDONLY));
8203 /* {File::WRONLY}[rdoc-ref:File::Constants@File-3A-3AWRONLY] */
8204 rb_define_const(rb_mFConst, "WRONLY", INT2FIX(O_WRONLY));
8205 /* {File::RDWR}[rdoc-ref:File::Constants@File-3A-3ARDWR] */
8206 rb_define_const(rb_mFConst, "RDWR", INT2FIX(O_RDWR));
8207 /* {File::APPEND}[rdoc-ref:File::Constants@File-3A-3AAPPEND] */
8208 rb_define_const(rb_mFConst, "APPEND", INT2FIX(O_APPEND));
8209 /* {File::CREAT}[rdoc-ref:File::Constants@File-3A-3ACREAT] */
8210 rb_define_const(rb_mFConst, "CREAT", INT2FIX(O_CREAT));
8211 /* {File::EXCL}[rdoc-ref:File::Constants@File-3A-3AEXCL] */
8212 rb_define_const(rb_mFConst, "EXCL", INT2FIX(O_EXCL));
8213#if defined(O_NDELAY) || defined(O_NONBLOCK)
8214# ifndef O_NONBLOCK
8215# define O_NONBLOCK O_NDELAY
8216# endif
8217 /* {File::NONBLOCK}[rdoc-ref:File::Constants@File-3A-3ANONBLOCK] */
8218 rb_define_const(rb_mFConst, "NONBLOCK", INT2FIX(O_NONBLOCK));
8219#endif
8220 /* {File::TRUNC}[rdoc-ref:File::Constants@File-3A-3ATRUNC] */
8221 rb_define_const(rb_mFConst, "TRUNC", INT2FIX(O_TRUNC));
8222#ifdef O_NOCTTY
8223 /* {File::NOCTTY}[rdoc-ref:File::Constants@File-3A-3ANOCTTY] */
8224 rb_define_const(rb_mFConst, "NOCTTY", INT2FIX(O_NOCTTY));
8225#endif
8226#ifndef O_BINARY
8227# define O_BINARY 0
8228#endif
8229 /* {File::BINARY}[rdoc-ref:File::Constants@File-3A-3ABINARY] */
8230 rb_define_const(rb_mFConst, "BINARY", INT2FIX(O_BINARY));
8231#ifndef O_SHARE_DELETE
8232# define O_SHARE_DELETE 0
8233#endif
8234 /* {File::SHARE_DELETE}[rdoc-ref:File::Constants@File-3A-3ASHARE_DELETE] */
8235 rb_define_const(rb_mFConst, "SHARE_DELETE", INT2FIX(O_SHARE_DELETE));
8236#ifdef O_SYNC
8237 /* {File::SYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
8238 rb_define_const(rb_mFConst, "SYNC", INT2FIX(O_SYNC));
8239#endif
8240#ifdef O_DSYNC
8241 /* {File::DSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
8242 rb_define_const(rb_mFConst, "DSYNC", INT2FIX(O_DSYNC));
8243#endif
8244#ifdef O_RSYNC
8245 /* {File::RSYNC}[rdoc-ref:File::Constants@File-3A-3ASYNC-2C+File-3A-3ARSYNC-2C+and+File-3A-3ADSYNC] */
8246 rb_define_const(rb_mFConst, "RSYNC", INT2FIX(O_RSYNC));
8247#endif
8248#ifdef O_NOFOLLOW
8249 /* {File::NOFOLLOW}[rdoc-ref:File::Constants@File-3A-3ANOFOLLOW] */
8250 rb_define_const(rb_mFConst, "NOFOLLOW", INT2FIX(O_NOFOLLOW)); /* FreeBSD, Linux */
8251#endif
8252#ifdef O_NOATIME
8253 /* {File::NOATIME}[rdoc-ref:File::Constants@File-3A-3ANOATIME] */
8254 rb_define_const(rb_mFConst, "NOATIME", INT2FIX(O_NOATIME)); /* Linux */
8255#endif
8256#ifdef O_DIRECT
8257 /* {File::DIRECT}[rdoc-ref:File::Constants@File-3A-3ADIRECT] */
8258 rb_define_const(rb_mFConst, "DIRECT", INT2FIX(O_DIRECT));
8259#endif
8260#ifdef O_TMPFILE
8261 /* {File::TMPFILE}[rdoc-ref:File::Constants@File-3A-3ATMPFILE] */
8262 rb_define_const(rb_mFConst, "TMPFILE", INT2FIX(O_TMPFILE));
8263#endif
8264
8265 /* {File::LOCK_SH}[rdoc-ref:File::Constants@File-3A-3ALOCK_SH] */
8266 rb_define_const(rb_mFConst, "LOCK_SH", INT2FIX(LOCK_SH));
8267 /* {File::LOCK_EX}[rdoc-ref:File::Constants@File-3A-3ALOCK_EX] */
8268 rb_define_const(rb_mFConst, "LOCK_EX", INT2FIX(LOCK_EX));
8269 /* {File::LOCK_UN}[rdoc-ref:File::Constants@File-3A-3ALOCK_UN] */
8270 rb_define_const(rb_mFConst, "LOCK_UN", INT2FIX(LOCK_UN));
8271 /* {File::LOCK_NB}[rdoc-ref:File::Constants@File-3A-3ALOCK_NB] */
8272 rb_define_const(rb_mFConst, "LOCK_NB", INT2FIX(LOCK_NB));
8273
8274 /* {File::NULL}[rdoc-ref:File::Constants@File-3A-3ANULL] */
8275 rb_define_const(rb_mFConst, "NULL", rb_fstring_cstr(ruby_null_device));
8276
8277 rb_define_global_function("test", rb_f_test, -1);
8278
8280 rb_define_alloc_func(rb_cStat, rb_stat_s_alloc);
8281 rb_define_method(rb_cStat, "initialize", rb_stat_init, 1);
8282 rb_define_method(rb_cStat, "initialize_copy", rb_stat_init_copy, 1);
8283
8285
8286 rb_define_method(rb_cStat, "<=>", rb_stat_cmp, 1);
8287
8288 rb_define_method(rb_cStat, "dev", rb_stat_dev, 0);
8289 rb_define_method(rb_cStat, "dev_major", rb_stat_dev_major, 0);
8290 rb_define_method(rb_cStat, "dev_minor", rb_stat_dev_minor, 0);
8291 rb_define_method(rb_cStat, "ino", rb_stat_ino, 0);
8292 rb_define_method(rb_cStat, "mode", rb_stat_mode, 0);
8293 rb_define_method(rb_cStat, "nlink", rb_stat_nlink, 0);
8294 rb_define_method(rb_cStat, "uid", rb_stat_uid, 0);
8295 rb_define_method(rb_cStat, "gid", rb_stat_gid, 0);
8296 rb_define_method(rb_cStat, "rdev", rb_stat_rdev, 0);
8297 rb_define_method(rb_cStat, "rdev_major", rb_stat_rdev_major, 0);
8298 rb_define_method(rb_cStat, "rdev_minor", rb_stat_rdev_minor, 0);
8299 rb_define_method(rb_cStat, "size", rb_stat_size, 0);
8300 rb_define_method(rb_cStat, "blksize", rb_stat_blksize, 0);
8301 rb_define_method(rb_cStat, "blocks", rb_stat_blocks, 0);
8302 rb_define_method(rb_cStat, "atime", rb_stat_atime, 0);
8303 rb_define_method(rb_cStat, "mtime", rb_stat_mtime, 0);
8304 rb_define_method(rb_cStat, "ctime", rb_stat_ctime, 0);
8305 rb_define_method(rb_cStat, "birthtime", rb_stat_birthtime, 0);
8306
8307 rb_define_method(rb_cStat, "inspect", rb_stat_inspect, 0);
8308
8309 rb_define_method(rb_cStat, "ftype", rb_stat_ftype, 0);
8310
8311 rb_define_method(rb_cStat, "directory?", rb_stat_d, 0);
8312 rb_define_method(rb_cStat, "readable?", rb_stat_r, 0);
8313 rb_define_method(rb_cStat, "readable_real?", rb_stat_R, 0);
8314 rb_define_method(rb_cStat, "world_readable?", rb_stat_wr, 0);
8315 rb_define_method(rb_cStat, "writable?", rb_stat_w, 0);
8316 rb_define_method(rb_cStat, "writable_real?", rb_stat_W, 0);
8317 rb_define_method(rb_cStat, "world_writable?", rb_stat_ww, 0);
8318 rb_define_method(rb_cStat, "executable?", rb_stat_x, 0);
8319 rb_define_method(rb_cStat, "executable_real?", rb_stat_X, 0);
8320 rb_define_method(rb_cStat, "file?", rb_stat_f, 0);
8321 rb_define_method(rb_cStat, "zero?", rb_stat_z, 0);
8322 rb_define_method(rb_cStat, "size?", rb_stat_s, 0);
8323 rb_define_method(rb_cStat, "owned?", rb_stat_owned, 0);
8324 rb_define_method(rb_cStat, "grpowned?", rb_stat_grpowned, 0);
8325
8326 rb_define_method(rb_cStat, "pipe?", rb_stat_p, 0);
8327 rb_define_method(rb_cStat, "symlink?", rb_stat_l, 0);
8328 rb_define_method(rb_cStat, "socket?", rb_stat_S, 0);
8329
8330 rb_define_method(rb_cStat, "blockdev?", rb_stat_b, 0);
8331 rb_define_method(rb_cStat, "chardev?", rb_stat_c, 0);
8332
8333 rb_define_method(rb_cStat, "setuid?", rb_stat_suid, 0);
8334 rb_define_method(rb_cStat, "setgid?", rb_stat_sgid, 0);
8335 rb_define_method(rb_cStat, "sticky?", rb_stat_sticky, 0);
8336}
#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:1603
VALUE rb_define_class(const char *name, VALUE super)
Defines a top-level class.
Definition class.c:1396
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition class.c:1427
VALUE rb_define_module(const char *name)
Defines a top-level module.
Definition class.c:1509
VALUE rb_define_module_under(VALUE outer, const char *name)
Defines a module under the namespace of outer.
Definition class.c:1532
#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:1437
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:661
VALUE rb_eIOError
IOError exception.
Definition io.c:189
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1427
VALUE rb_eEncCompatError
Encoding::CompatibilityError exception.
Definition error.c:1434
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:3835
VALUE rb_eSystemCallError
SystemCallError exception.
Definition error.c:1447
VALUE rb_cObject
Object class.
Definition object.c:61
VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass)
Allocates, then initialises an instance of the given class.
Definition object.c:2296
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:235
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:141
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:1313
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:81
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:1344
int rb_enc_str_asciionly_p(VALUE str)
Queries if the passed string is "ASCII only".
Definition string.c:970
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1121
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:248
int rb_cloexec_open(const char *pathname, int flags, mode_t mode)
Opens a file that closes on exec.
Definition io.c:328
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:1514
VALUE rb_str_plus(VALUE lhs, VALUE rhs)
Generates a new string, concatenating the former to the latter.
Definition string.c:2487
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:3836
VALUE rb_str_tmp_new(long len)
Allocates a "temporary" string.
Definition string.c:1748
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:3189
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:11699
#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
size_t rb_str_capacity(VALUE str)
Queries the capacity of the given string.
Definition string.c:1005
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1520
VALUE rb_str_dup(VALUE str)
Duplicates a string.
Definition string.c:1996
VALUE rb_str_cat(VALUE dst, const char *src, long srclen)
Destructively appends the passed contents to the string.
Definition string.c:3604
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:6588
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:3802
void rb_str_set_len(VALUE str, long len)
Overwrites the length of the string.
Definition string.c:3423
VALUE rb_str_inspect(VALUE str)
Generates a "readable" version of the receiver.
Definition string.c:7267
int rb_str_cmp(VALUE lhs, VALUE rhs)
Compares two strings, as in strcmp(3).
Definition string.c:4253
#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:2746
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1720
#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:1445
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:2801
struct timespec rb_time_timespec(VALUE time)
Identical to rb_time_timeval(), except for return type.
Definition time.c:2971
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:797
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 strdup(s)
Just another name of ruby_strdup.
Definition util.h:187
#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:51
#define RARRAY_AREF(a, i)
Definition rarray.h:403
#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 RUBY_TYPED_FREE_IMMEDIATELY
Macros to see if each corresponding flag is defined.
Definition rtypeddata.h:122
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:769
#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:578
const char * rb_obj_classname(VALUE obj)
Queries the name of the class of the passed object.
Definition variable.c:515
#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:229
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.