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