Created
December 21, 2011 05:26
-
-
Save shirosaki/1504711 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CPU: Intel Core 2 Duo 6600 2.40GHz | |
RAM: 4 GB | |
HDD: On Ramdisk 1.1GB using ImDisk | |
AV: Off | |
OS: Windows XP SP3 | |
# This was run before replacing rb_file_expand_path() to funcall() in rb_expand_load_paths() of load.c. | |
V:\test_rails>set RUBY_USE_ATTRIBUTE=1 | |
V:\test_rails>set RUBY_CACHED_LOAD_PATH=0 | |
# cached_load_path: off, fenix: off | |
V:\test_rails>timer ruby script\rails r 'p $LOAD_PATH.size, $LOADED_FEATURES.size' | |
62 | |
726 | |
real 6.453 | |
system 3.671 | |
user 2.765 | |
# cached_load_path: off, fenix: on | |
V:\test_rails>timer ruby -rfenix/replace script\rails r 'p $LOAD_PATH.size, $LOADED_FEATURES.size' | |
62 | |
728 | |
real 5.296 | |
system 2.421 | |
user 2.859 | |
V:\test_rails>set RUBY_CACHED_LOAD_PATH= | |
# cached_load_path: on, fenix: off | |
V:\test_rails>timer ruby script\rails r 'p $LOAD_PATH.size, $LOADED_FEATURES.size' | |
62 | |
726 | |
real 3.359 | |
system 1.531 | |
user 1.812 | |
# cached_load_path + fenix | |
V:\test_rails>timer ruby -rfenix/replace script\rails r 'p $LOAD_PATH.size, $LOADED_FEATURES.size' | |
62 | |
728 | |
real 2.312 | |
system 0.531 | |
user 1.750 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************** | |
file.c - | |
$Author$ | |
created at: Mon Nov 15 12:24:34 JST 1993 | |
Copyright (C) 1993-2007 Yukihiro Matsumoto | |
Copyright (C) 2000 Network Applied Communication Laboratory, Inc. | |
Copyright (C) 2000 Information-technology Promotion Agency, Japan | |
**********************************************************************/ | |
#ifdef _WIN32 | |
#include "missing/file.h" | |
#endif | |
#ifdef __CYGWIN__ | |
#include <windows.h> | |
#include <sys/cygwin.h> | |
#endif | |
#include "ruby/ruby.h" | |
#include "ruby/io.h" | |
#include "ruby/util.h" | |
#include "dln.h" | |
#include "internal.h" | |
#ifdef HAVE_UNISTD_H | |
#include <unistd.h> | |
#endif | |
#ifdef HAVE_SYS_FILE_H | |
# include <sys/file.h> | |
#else | |
int flock(int, int); | |
#endif | |
#ifdef HAVE_SYS_PARAM_H | |
# include <sys/param.h> | |
#endif | |
#ifndef MAXPATHLEN | |
# define MAXPATHLEN 1024 | |
#endif | |
#include <ctype.h> | |
#include <time.h> | |
#ifdef HAVE_UTIME_H | |
#include <utime.h> | |
#elif defined HAVE_SYS_UTIME_H | |
#include <sys/utime.h> | |
#endif | |
#ifdef HAVE_PWD_H | |
#include <pwd.h> | |
#endif | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#ifdef HAVE_SYS_MKDEV_H | |
#include <sys/mkdev.h> | |
#endif | |
#if defined(HAVE_FCNTL_H) | |
#include <fcntl.h> | |
#endif | |
#if !defined HAVE_LSTAT && !defined lstat | |
#define lstat stat | |
#endif | |
/* define system APIs */ | |
#ifdef _WIN32 | |
#define STAT(p, s) rb_w32_ustati64((p), (s)) | |
#undef lstat | |
#define lstat(p, s) rb_w32_ustati64((p), (s)) | |
#undef access | |
#define access(p, m) rb_w32_uaccess((p), (m)) | |
#undef chmod | |
#define chmod(p, m) rb_w32_uchmod((p), (m)) | |
#undef chown | |
#define chown(p, o, g) rb_w32_uchown((p), (o), (g)) | |
#undef utime | |
#define utime(p, t) rb_w32_uutime((p), (t)) | |
#undef link | |
#define link(f, t) rb_w32_ulink((f), (t)) | |
#undef unlink | |
#define unlink(p) rb_w32_uunlink(p) | |
#undef rename | |
#define rename(f, t) rb_w32_urename((f), (t)) | |
#else | |
#define STAT(p, s) stat((p), (s)) | |
#endif | |
#if defined(__BEOS__) || defined(__HAIKU__) /* should not change ID if -1 */ | |
static int | |
be_chown(const char *path, uid_t owner, gid_t group) | |
{ | |
if (owner == (uid_t)-1 || group == (gid_t)-1) { | |
struct stat st; | |
if (STAT(path, &st) < 0) return -1; | |
if (owner == (uid_t)-1) owner = st.st_uid; | |
if (group == (gid_t)-1) group = st.st_gid; | |
} | |
return chown(path, owner, group); | |
} | |
#define chown be_chown | |
static int | |
be_fchown(int fd, uid_t owner, gid_t group) | |
{ | |
if (owner == (uid_t)-1 || group == (gid_t)-1) { | |
struct stat st; | |
if (fstat(fd, &st) < 0) return -1; | |
if (owner == (uid_t)-1) owner = st.st_uid; | |
if (group == (gid_t)-1) group = st.st_gid; | |
} | |
return fchown(fd, owner, group); | |
} | |
#define fchown be_fchown | |
#endif /* __BEOS__ || __HAIKU__ */ | |
VALUE rb_cFile; | |
VALUE rb_mFileTest; | |
VALUE rb_cStat; | |
#define insecure_obj_p(obj, level) ((level) >= 4 || ((level) > 0 && OBJ_TAINTED(obj))) | |
static VALUE | |
file_path_convert(VALUE name) | |
{ | |
#ifndef _WIN32 /* non Windows == Unix */ | |
rb_encoding *fname_encoding = rb_enc_from_index(ENCODING_GET(name)); | |
rb_encoding *fs_encoding; | |
if (rb_default_internal_encoding() != NULL | |
&& rb_usascii_encoding() != fname_encoding | |
&& rb_ascii8bit_encoding() != fname_encoding | |
&& (fs_encoding = rb_filesystem_encoding()) != fname_encoding) { | |
/* Don't call rb_filesystem_encoding() before US-ASCII and ASCII-8BIT */ | |
name = rb_str_conv_enc(name, fname_encoding, fs_encoding); | |
} | |
#endif | |
return name; | |
} | |
static VALUE | |
rb_get_path_check(VALUE obj, int level) | |
{ | |
VALUE tmp; | |
ID to_path; | |
rb_encoding *enc; | |
if (insecure_obj_p(obj, level)) { | |
rb_insecure_operation(); | |
} | |
CONST_ID(to_path, "to_path"); | |
tmp = rb_check_funcall(obj, to_path, 0, 0); | |
if (tmp == Qundef) { | |
tmp = obj; | |
} | |
StringValue(tmp); | |
tmp = file_path_convert(tmp); | |
if (obj != tmp && insecure_obj_p(tmp, level)) { | |
rb_insecure_operation(); | |
} | |
enc = rb_enc_get(tmp); | |
if (!rb_enc_asciicompat(enc)) { | |
tmp = rb_str_inspect(tmp); | |
rb_raise(rb_eEncCompatError, "path name must be ASCII-compatible (%s): %s", | |
rb_enc_name(enc), RSTRING_PTR(tmp)); | |
} | |
return rb_str_new4(tmp); | |
} | |
VALUE | |
rb_get_path_no_checksafe(VALUE obj) | |
{ | |
return rb_get_path_check(obj, 0); | |
} | |
VALUE | |
rb_get_path(VALUE obj) | |
{ | |
return rb_get_path_check(obj, rb_safe_level()); | |
} | |
VALUE | |
rb_str_encode_ospath(VALUE path) | |
{ | |
#ifdef _WIN32 | |
rb_encoding *enc = rb_enc_get(path); | |
if (enc != rb_ascii8bit_encoding()) { | |
rb_encoding *utf8 = rb_utf8_encoding(); | |
if (enc != utf8) | |
path = rb_str_encode(path, rb_enc_from_encoding(utf8), 0, Qnil); | |
} | |
else if (RSTRING_LEN(path) > 0) { | |
path = rb_str_dup(path); | |
rb_enc_associate(path, rb_filesystem_encoding()); | |
path = rb_str_encode(path, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil); | |
} | |
#endif | |
return path; | |
} | |
static long | |
apply2files(void (*func)(const char *, void *), VALUE vargs, void *arg) | |
{ | |
long i; | |
volatile VALUE path; | |
rb_secure(4); | |
for (i=0; i<RARRAY_LEN(vargs); i++) { | |
path = rb_get_path(RARRAY_PTR(vargs)[i]); | |
path = rb_str_encode_ospath(path); | |
(*func)(StringValueCStr(path), arg); | |
} | |
return RARRAY_LEN(vargs); | |
} | |
/* | |
* call-seq: | |
* file.path -> filename | |
* | |
* Returns the pathname used to create <i>file</i> as a string. Does | |
* not normalize the name. | |
* | |
* File.new("testfile").path #=> "testfile" | |
* File.new("/tmp/../tmp/xxx", "w").path #=> "/tmp/../tmp/xxx" | |
* | |
*/ | |
static VALUE | |
rb_file_path(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
fptr = RFILE(rb_io_taint_check(obj))->fptr; | |
rb_io_check_initialized(fptr); | |
if (NIL_P(fptr->pathv)) return Qnil; | |
return rb_obj_taint(rb_str_dup(fptr->pathv)); | |
} | |
static size_t | |
stat_memsize(const void *p) | |
{ | |
return p ? sizeof(struct stat) : 0; | |
} | |
static const rb_data_type_t stat_data_type = { | |
"stat", | |
{NULL, RUBY_TYPED_DEFAULT_FREE, stat_memsize,}, | |
}; | |
static VALUE | |
stat_new_0(VALUE klass, struct stat *st) | |
{ | |
struct stat *nst = 0; | |
if (st) { | |
nst = ALLOC(struct stat); | |
*nst = *st; | |
} | |
return TypedData_Wrap_Struct(klass, &stat_data_type, nst); | |
} | |
static VALUE | |
stat_new(struct stat *st) | |
{ | |
return stat_new_0(rb_cStat, st); | |
} | |
static struct stat* | |
get_stat(VALUE self) | |
{ | |
struct stat* st; | |
TypedData_Get_Struct(self, struct stat, &stat_data_type, st); | |
if (!st) rb_raise(rb_eTypeError, "uninitialized File::Stat"); | |
return st; | |
} | |
static struct timespec stat_mtimespec(struct stat *st); | |
/* | |
* call-seq: | |
* stat <=> other_stat -> -1, 0, 1, nil | |
* | |
* Compares <code>File::Stat</code> objects by comparing their | |
* respective modification times. | |
* | |
* f1 = File.new("f1", "w") | |
* sleep 1 | |
* f2 = File.new("f2", "w") | |
* f1.stat <=> f2.stat #=> -1 | |
*/ | |
static VALUE | |
rb_stat_cmp(VALUE self, VALUE other) | |
{ | |
if (rb_obj_is_kind_of(other, rb_obj_class(self))) { | |
struct timespec ts1 = stat_mtimespec(get_stat(self)); | |
struct timespec ts2 = stat_mtimespec(get_stat(other)); | |
if (ts1.tv_sec == ts2.tv_sec) { | |
if (ts1.tv_nsec == ts2.tv_nsec) return INT2FIX(0); | |
if (ts1.tv_nsec < ts2.tv_nsec) return INT2FIX(-1); | |
return INT2FIX(1); | |
} | |
if (ts1.tv_sec < ts2.tv_sec) return INT2FIX(-1); | |
return INT2FIX(1); | |
} | |
return Qnil; | |
} | |
#define ST2UINT(val) ((val) & ~(~1UL << (sizeof(val) * CHAR_BIT - 1))) | |
#ifndef NUM2DEVT | |
# define NUM2DEVT(v) NUM2UINT(v) | |
#endif | |
#ifndef DEVT2NUM | |
# define DEVT2NUM(v) UINT2NUM(v) | |
#endif | |
#ifndef PRI_DEVT_PREFIX | |
# define PRI_DEVT_PREFIX "" | |
#endif | |
/* | |
* call-seq: | |
* stat.dev -> fixnum | |
* | |
* Returns an integer representing the device on which <i>stat</i> | |
* resides. | |
* | |
* File.stat("testfile").dev #=> 774 | |
*/ | |
static VALUE | |
rb_stat_dev(VALUE self) | |
{ | |
return DEVT2NUM(get_stat(self)->st_dev); | |
} | |
/* | |
* call-seq: | |
* stat.dev_major -> fixnum | |
* | |
* Returns the major part of <code>File_Stat#dev</code> or | |
* <code>nil</code>. | |
* | |
* File.stat("/dev/fd1").dev_major #=> 2 | |
* File.stat("/dev/tty").dev_major #=> 5 | |
*/ | |
static VALUE | |
rb_stat_dev_major(VALUE self) | |
{ | |
#if defined(major) | |
return INT2NUM(major(get_stat(self)->st_dev)); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.dev_minor -> fixnum | |
* | |
* Returns the minor part of <code>File_Stat#dev</code> or | |
* <code>nil</code>. | |
* | |
* File.stat("/dev/fd1").dev_minor #=> 1 | |
* File.stat("/dev/tty").dev_minor #=> 0 | |
*/ | |
static VALUE | |
rb_stat_dev_minor(VALUE self) | |
{ | |
#if defined(minor) | |
return INT2NUM(minor(get_stat(self)->st_dev)); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.ino -> fixnum | |
* | |
* Returns the inode number for <i>stat</i>. | |
* | |
* File.stat("testfile").ino #=> 1083669 | |
* | |
*/ | |
static VALUE | |
rb_stat_ino(VALUE self) | |
{ | |
#if SIZEOF_STRUCT_STAT_ST_INO > SIZEOF_LONG | |
return ULL2NUM(get_stat(self)->st_ino); | |
#else | |
return ULONG2NUM(get_stat(self)->st_ino); | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.mode -> fixnum | |
* | |
* Returns an integer representing the permission bits of | |
* <i>stat</i>. The meaning of the bits is platform dependent; on | |
* Unix systems, see <code>stat(2)</code>. | |
* | |
* File.chmod(0644, "testfile") #=> 1 | |
* s = File.stat("testfile") | |
* sprintf("%o", s.mode) #=> "100644" | |
*/ | |
static VALUE | |
rb_stat_mode(VALUE self) | |
{ | |
return UINT2NUM(ST2UINT(get_stat(self)->st_mode)); | |
} | |
/* | |
* call-seq: | |
* stat.nlink -> fixnum | |
* | |
* Returns the number of hard links to <i>stat</i>. | |
* | |
* File.stat("testfile").nlink #=> 1 | |
* File.link("testfile", "testfile.bak") #=> 0 | |
* File.stat("testfile").nlink #=> 2 | |
* | |
*/ | |
static VALUE | |
rb_stat_nlink(VALUE self) | |
{ | |
return UINT2NUM(get_stat(self)->st_nlink); | |
} | |
/* | |
* call-seq: | |
* stat.uid -> fixnum | |
* | |
* Returns the numeric user id of the owner of <i>stat</i>. | |
* | |
* File.stat("testfile").uid #=> 501 | |
* | |
*/ | |
static VALUE | |
rb_stat_uid(VALUE self) | |
{ | |
return UIDT2NUM(get_stat(self)->st_uid); | |
} | |
/* | |
* call-seq: | |
* stat.gid -> fixnum | |
* | |
* Returns the numeric group id of the owner of <i>stat</i>. | |
* | |
* File.stat("testfile").gid #=> 500 | |
* | |
*/ | |
static VALUE | |
rb_stat_gid(VALUE self) | |
{ | |
return GIDT2NUM(get_stat(self)->st_gid); | |
} | |
/* | |
* call-seq: | |
* stat.rdev -> fixnum or nil | |
* | |
* Returns an integer representing the device type on which | |
* <i>stat</i> resides. Returns <code>nil</code> if the operating | |
* system doesn't support this feature. | |
* | |
* File.stat("/dev/fd1").rdev #=> 513 | |
* File.stat("/dev/tty").rdev #=> 1280 | |
*/ | |
static VALUE | |
rb_stat_rdev(VALUE self) | |
{ | |
#ifdef HAVE_ST_RDEV | |
return DEVT2NUM(get_stat(self)->st_rdev); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.rdev_major -> fixnum | |
* | |
* Returns the major part of <code>File_Stat#rdev</code> or | |
* <code>nil</code>. | |
* | |
* File.stat("/dev/fd1").rdev_major #=> 2 | |
* File.stat("/dev/tty").rdev_major #=> 5 | |
*/ | |
static VALUE | |
rb_stat_rdev_major(VALUE self) | |
{ | |
#if defined(HAVE_ST_RDEV) && defined(major) | |
return DEVT2NUM(major(get_stat(self)->st_rdev)); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.rdev_minor -> fixnum | |
* | |
* Returns the minor part of <code>File_Stat#rdev</code> or | |
* <code>nil</code>. | |
* | |
* File.stat("/dev/fd1").rdev_minor #=> 1 | |
* File.stat("/dev/tty").rdev_minor #=> 0 | |
*/ | |
static VALUE | |
rb_stat_rdev_minor(VALUE self) | |
{ | |
#if defined(HAVE_ST_RDEV) && defined(minor) | |
return DEVT2NUM(minor(get_stat(self)->st_rdev)); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.size -> fixnum | |
* | |
* Returns the size of <i>stat</i> in bytes. | |
* | |
* File.stat("testfile").size #=> 66 | |
*/ | |
static VALUE | |
rb_stat_size(VALUE self) | |
{ | |
return OFFT2NUM(get_stat(self)->st_size); | |
} | |
/* | |
* call-seq: | |
* stat.blksize -> integer or nil | |
* | |
* Returns the native file system's block size. Will return <code>nil</code> | |
* on platforms that don't support this information. | |
* | |
* File.stat("testfile").blksize #=> 4096 | |
* | |
*/ | |
static VALUE | |
rb_stat_blksize(VALUE self) | |
{ | |
#ifdef HAVE_ST_BLKSIZE | |
return ULONG2NUM(get_stat(self)->st_blksize); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.blocks -> integer or nil | |
* | |
* Returns the number of native file system blocks allocated for this | |
* file, or <code>nil</code> if the operating system doesn't | |
* support this feature. | |
* | |
* File.stat("testfile").blocks #=> 2 | |
*/ | |
static VALUE | |
rb_stat_blocks(VALUE self) | |
{ | |
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS | |
# if SIZEOF_STRUCT_STAT_ST_BLOCKS > SIZEOF_LONG | |
return ULL2NUM(get_stat(self)->st_blocks); | |
# else | |
return ULONG2NUM(get_stat(self)->st_blocks); | |
# endif | |
#else | |
return Qnil; | |
#endif | |
} | |
static struct timespec | |
stat_atimespec(struct stat *st) | |
{ | |
struct timespec ts; | |
ts.tv_sec = st->st_atime; | |
#if defined(HAVE_STRUCT_STAT_ST_ATIM) | |
ts.tv_nsec = st->st_atim.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) | |
ts.tv_nsec = st->st_atimespec.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) | |
ts.tv_nsec = st->st_atimensec; | |
#else | |
ts.tv_nsec = 0; | |
#endif | |
return ts; | |
} | |
static VALUE | |
stat_atime(struct stat *st) | |
{ | |
struct timespec ts = stat_atimespec(st); | |
return rb_time_nano_new(ts.tv_sec, ts.tv_nsec); | |
} | |
static struct timespec | |
stat_mtimespec(struct stat *st) | |
{ | |
struct timespec ts; | |
ts.tv_sec = st->st_mtime; | |
#if defined(HAVE_STRUCT_STAT_ST_MTIM) | |
ts.tv_nsec = st->st_mtim.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC) | |
ts.tv_nsec = st->st_mtimespec.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC) | |
ts.tv_nsec = st->st_mtimensec; | |
#else | |
ts.tv_nsec = 0; | |
#endif | |
return ts; | |
} | |
static VALUE | |
stat_mtime(struct stat *st) | |
{ | |
struct timespec ts = stat_mtimespec(st); | |
return rb_time_nano_new(ts.tv_sec, ts.tv_nsec); | |
} | |
static struct timespec | |
stat_ctimespec(struct stat *st) | |
{ | |
struct timespec ts; | |
ts.tv_sec = st->st_ctime; | |
#if defined(HAVE_STRUCT_STAT_ST_CTIM) | |
ts.tv_nsec = st->st_ctim.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC) | |
ts.tv_nsec = st->st_ctimespec.tv_nsec; | |
#elif defined(HAVE_STRUCT_STAT_ST_CTIMENSEC) | |
ts.tv_nsec = st->st_ctimensec; | |
#else | |
ts.tv_nsec = 0; | |
#endif | |
return ts; | |
} | |
static VALUE | |
stat_ctime(struct stat *st) | |
{ | |
struct timespec ts = stat_ctimespec(st); | |
return rb_time_nano_new(ts.tv_sec, ts.tv_nsec); | |
} | |
/* | |
* call-seq: | |
* stat.atime -> time | |
* | |
* Returns the last access time for this file as an object of class | |
* <code>Time</code>. | |
* | |
* File.stat("testfile").atime #=> Wed Dec 31 18:00:00 CST 1969 | |
* | |
*/ | |
static VALUE | |
rb_stat_atime(VALUE self) | |
{ | |
return stat_atime(get_stat(self)); | |
} | |
/* | |
* call-seq: | |
* stat.mtime -> aTime | |
* | |
* Returns the modification time of <i>stat</i>. | |
* | |
* File.stat("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_stat_mtime(VALUE self) | |
{ | |
return stat_mtime(get_stat(self)); | |
} | |
/* | |
* call-seq: | |
* stat.ctime -> aTime | |
* | |
* Returns the change time for <i>stat</i> (that is, the time | |
* directory information about the file was changed, not the file | |
* itself). | |
* | |
* Note that on Windows (NTFS), returns creation time (birth time). | |
* | |
* File.stat("testfile").ctime #=> Wed Apr 09 08:53:14 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_stat_ctime(VALUE self) | |
{ | |
return stat_ctime(get_stat(self)); | |
} | |
/* | |
* call-seq: | |
* stat.inspect -> string | |
* | |
* Produce a nicely formatted description of <i>stat</i>. | |
* | |
* File.stat("/etc/passwd").inspect | |
* #=> "#<File::Stat dev=0xe000005, ino=1078078, mode=0100644, | |
* # nlink=1, uid=0, gid=0, rdev=0x0, size=1374, blksize=4096, | |
* # blocks=8, atime=Wed Dec 10 10:16:12 CST 2003, | |
* # mtime=Fri Sep 12 15:41:41 CDT 2003, | |
* # ctime=Mon Oct 27 11:20:27 CST 2003>" | |
*/ | |
static VALUE | |
rb_stat_inspect(VALUE self) | |
{ | |
VALUE str; | |
size_t i; | |
static const struct { | |
const char *name; | |
VALUE (*func)(VALUE); | |
} member[] = { | |
{"dev", rb_stat_dev}, | |
{"ino", rb_stat_ino}, | |
{"mode", rb_stat_mode}, | |
{"nlink", rb_stat_nlink}, | |
{"uid", rb_stat_uid}, | |
{"gid", rb_stat_gid}, | |
{"rdev", rb_stat_rdev}, | |
{"size", rb_stat_size}, | |
{"blksize", rb_stat_blksize}, | |
{"blocks", rb_stat_blocks}, | |
{"atime", rb_stat_atime}, | |
{"mtime", rb_stat_mtime}, | |
{"ctime", rb_stat_ctime}, | |
}; | |
struct stat* st; | |
TypedData_Get_Struct(self, struct stat, &stat_data_type, st); | |
if (!st) { | |
return rb_sprintf("#<%s: uninitialized>", rb_obj_classname(self)); | |
} | |
str = rb_str_buf_new2("#<"); | |
rb_str_buf_cat2(str, rb_obj_classname(self)); | |
rb_str_buf_cat2(str, " "); | |
for (i = 0; i < sizeof(member)/sizeof(member[0]); i++) { | |
VALUE v; | |
if (i > 0) { | |
rb_str_buf_cat2(str, ", "); | |
} | |
rb_str_buf_cat2(str, member[i].name); | |
rb_str_buf_cat2(str, "="); | |
v = (*member[i].func)(self); | |
if (i == 2) { /* mode */ | |
rb_str_catf(str, "0%lo", (unsigned long)NUM2ULONG(v)); | |
} | |
else if (i == 0 || i == 6) { /* dev/rdev */ | |
rb_str_catf(str, "0x%"PRI_DEVT_PREFIX"x", NUM2DEVT(v)); | |
} | |
else { | |
rb_str_append(str, rb_inspect(v)); | |
} | |
} | |
rb_str_buf_cat2(str, ">"); | |
OBJ_INFECT(str, self); | |
return str; | |
} | |
static int | |
rb_stat(VALUE file, struct stat *st) | |
{ | |
VALUE tmp; | |
rb_secure(2); | |
tmp = rb_check_convert_type(file, T_FILE, "IO", "to_io"); | |
if (!NIL_P(tmp)) { | |
rb_io_t *fptr; | |
GetOpenFile(tmp, fptr); | |
return fstat(fptr->fd, st); | |
} | |
FilePathValue(file); | |
file = rb_str_encode_ospath(file); | |
return STAT(StringValueCStr(file), st); | |
} | |
#ifdef _WIN32 | |
static HANDLE | |
w32_io_info(VALUE *file, BY_HANDLE_FILE_INFORMATION *st) | |
{ | |
VALUE tmp; | |
HANDLE f, ret = 0; | |
tmp = rb_check_convert_type(*file, T_FILE, "IO", "to_io"); | |
if (!NIL_P(tmp)) { | |
rb_io_t *fptr; | |
GetOpenFile(tmp, fptr); | |
f = (HANDLE)rb_w32_get_osfhandle(fptr->fd); | |
if (f == (HANDLE)-1) return INVALID_HANDLE_VALUE; | |
} | |
else { | |
VALUE tmp; | |
WCHAR *ptr; | |
int len; | |
VALUE v; | |
FilePathValue(*file); | |
tmp = rb_str_encode_ospath(*file); | |
len = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, NULL, 0); | |
ptr = ALLOCV_N(WCHAR, v, len); | |
MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, ptr, len); | |
f = CreateFileW(ptr, 0, | |
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, | |
rb_w32_iswin95() ? 0 : FILE_FLAG_BACKUP_SEMANTICS, | |
NULL); | |
ALLOCV_END(v); | |
if (f == INVALID_HANDLE_VALUE) return f; | |
ret = f; | |
} | |
if (GetFileType(f) == FILE_TYPE_DISK) { | |
ZeroMemory(st, sizeof(*st)); | |
if (GetFileInformationByHandle(f, st)) return ret; | |
} | |
if (ret) CloseHandle(ret); | |
return INVALID_HANDLE_VALUE; | |
} | |
#endif | |
/* | |
* call-seq: | |
* File.stat(file_name) -> stat | |
* | |
* Returns a <code>File::Stat</code> object for the named file (see | |
* <code>File::Stat</code>). | |
* | |
* File.stat("testfile").mtime #=> Tue Apr 08 12:58:04 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_s_stat(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
rb_secure(4); | |
FilePathValue(fname); | |
if (rb_stat(fname, &st) < 0) { | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return stat_new(&st); | |
} | |
/* | |
* call-seq: | |
* ios.stat -> stat | |
* | |
* Returns status information for <em>ios</em> as an object of type | |
* <code>File::Stat</code>. | |
* | |
* f = File.new("testfile") | |
* s = f.stat | |
* "%o" % s.mode #=> "100644" | |
* s.blksize #=> 4096 | |
* s.atime #=> Wed Apr 09 08:53:54 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_io_stat(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
struct stat st; | |
#define rb_sys_fail_path(path) rb_sys_fail(NIL_P(path) ? 0 : RSTRING_PTR(path)) | |
GetOpenFile(obj, fptr); | |
if (fstat(fptr->fd, &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return stat_new(&st); | |
} | |
/* | |
* call-seq: | |
* File.lstat(file_name) -> stat | |
* | |
* Same as <code>File::stat</code>, but does not follow the last symbolic | |
* link. Instead, reports on the link itself. | |
* | |
* File.symlink("testfile", "link2test") #=> 0 | |
* File.stat("testfile").size #=> 66 | |
* File.lstat("link2test").size #=> 8 | |
* File.stat("link2test").size #=> 66 | |
* | |
*/ | |
static VALUE | |
rb_file_s_lstat(VALUE klass, VALUE fname) | |
{ | |
#ifdef HAVE_LSTAT | |
struct stat st; | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (lstat(StringValueCStr(fname), &st) == -1) { | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return stat_new(&st); | |
#else | |
return rb_file_s_stat(klass, fname); | |
#endif | |
} | |
/* | |
* call-seq: | |
* file.lstat -> stat | |
* | |
* Same as <code>IO#stat</code>, but does not follow the last symbolic | |
* link. Instead, reports on the link itself. | |
* | |
* File.symlink("testfile", "link2test") #=> 0 | |
* File.stat("testfile").size #=> 66 | |
* f = File.new("link2test") | |
* f.lstat.size #=> 8 | |
* f.stat.size #=> 66 | |
*/ | |
static VALUE | |
rb_file_lstat(VALUE obj) | |
{ | |
#ifdef HAVE_LSTAT | |
rb_io_t *fptr; | |
struct stat st; | |
VALUE path; | |
rb_secure(2); | |
GetOpenFile(obj, fptr); | |
if (NIL_P(fptr->pathv)) return Qnil; | |
path = rb_str_encode_ospath(fptr->pathv); | |
if (lstat(RSTRING_PTR(path), &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return stat_new(&st); | |
#else | |
return rb_io_stat(obj); | |
#endif | |
} | |
static int | |
rb_group_member(GETGROUPS_T gid) | |
{ | |
int rv = FALSE; | |
#ifndef _WIN32 | |
if (getgid() == gid || getegid() == gid) | |
return TRUE; | |
# ifdef HAVE_GETGROUPS | |
# ifndef NGROUPS | |
# ifdef NGROUPS_MAX | |
# define NGROUPS NGROUPS_MAX | |
# else | |
# define NGROUPS 32 | |
# endif | |
# endif | |
{ | |
GETGROUPS_T *gary; | |
int anum; | |
gary = xmalloc(NGROUPS * sizeof(GETGROUPS_T)); | |
anum = getgroups(NGROUPS, gary); | |
while (--anum >= 0) { | |
if (gary[anum] == gid) { | |
rv = TRUE; | |
break; | |
} | |
} | |
xfree(gary); | |
} | |
# endif | |
#endif | |
return rv; | |
} | |
#ifndef S_IXUGO | |
# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) | |
#endif | |
#if defined(S_IXGRP) && !defined(_WIN32) && !defined(__CYGWIN__) | |
#define USE_GETEUID 1 | |
#endif | |
#ifndef HAVE_EACCESS | |
int | |
eaccess(const char *path, int mode) | |
{ | |
#ifdef USE_GETEUID | |
struct stat st; | |
rb_uid_t euid; | |
if (STAT(path, &st) < 0) | |
return -1; | |
euid = geteuid(); | |
if (euid == 0) { | |
/* Root can read or write any file. */ | |
if (!(mode & X_OK)) | |
return 0; | |
/* Root can execute any file that has any one of the execute | |
bits set. */ | |
if (st.st_mode & S_IXUGO) | |
return 0; | |
return -1; | |
} | |
if (st.st_uid == euid) /* owner */ | |
mode <<= 6; | |
else if (rb_group_member(st.st_gid)) | |
mode <<= 3; | |
if ((int)(st.st_mode & mode) == mode) return 0; | |
return -1; | |
#else | |
return access(path, mode); | |
#endif | |
} | |
#endif | |
static inline int | |
access_internal(const char *path, int mode) | |
{ | |
return access(path, mode); | |
} | |
/* | |
* Document-class: FileTest | |
* | |
* <code>FileTest</code> implements file test operations similar to | |
* those used in <code>File::Stat</code>. It exists as a standalone | |
* module, and its methods are also insinuated into the <code>File</code> | |
* class. (Note that this is not done by inclusion: the interpreter cheats). | |
* | |
*/ | |
/* | |
* Document-method: exist? | |
* | |
* call-seq: | |
* Dir.exist?(file_name) -> true or false | |
* Dir.exists?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a directory, | |
* <code>false</code> otherwise. | |
* | |
*/ | |
/* | |
* Document-method: directory? | |
* | |
* call-seq: | |
* File.directory?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a directory, | |
* or a symlink that points at a directory, and <code>false</code> | |
* otherwise. | |
* | |
* File.directory?(".") | |
*/ | |
VALUE | |
rb_file_directory_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef S_ISDIR | |
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) | |
#endif | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISDIR(st.st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.pipe?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a pipe. | |
*/ | |
static VALUE | |
rb_file_pipe_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_IFIFO | |
# ifndef S_ISFIFO | |
# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) | |
# endif | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISFIFO(st.st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.symlink?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a symbolic link. | |
*/ | |
static VALUE | |
rb_file_symlink_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef S_ISLNK | |
# ifdef _S_ISLNK | |
# define S_ISLNK(m) _S_ISLNK(m) | |
# else | |
# ifdef _S_IFLNK | |
# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK) | |
# else | |
# ifdef S_IFLNK | |
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) | |
# endif | |
# endif | |
# endif | |
#endif | |
#ifdef S_ISLNK | |
struct stat st; | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (lstat(StringValueCStr(fname), &st) < 0) return Qfalse; | |
if (S_ISLNK(st.st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.socket?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a socket. | |
*/ | |
static VALUE | |
rb_file_socket_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef S_ISSOCK | |
# ifdef _S_ISSOCK | |
# define S_ISSOCK(m) _S_ISSOCK(m) | |
# else | |
# ifdef _S_IFSOCK | |
# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK) | |
# else | |
# ifdef S_IFSOCK | |
# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) | |
# endif | |
# endif | |
# endif | |
#endif | |
#ifdef S_ISSOCK | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISSOCK(st.st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.blockdev?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a block device. | |
*/ | |
static VALUE | |
rb_file_blockdev_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef S_ISBLK | |
# ifdef S_IFBLK | |
# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) | |
# else | |
# define S_ISBLK(m) (0) /* anytime false */ | |
# endif | |
#endif | |
#ifdef S_ISBLK | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISBLK(st.st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.chardev?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is a character device. | |
*/ | |
static VALUE | |
rb_file_chardev_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef S_ISCHR | |
# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) | |
#endif | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISCHR(st.st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.exist?(file_name) -> true or false | |
* File.exists?(file_name) -> true or false | |
* | |
* Return <code>true</code> if the named file exists. | |
*/ | |
static VALUE | |
rb_file_exist_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.readable?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is readable by the effective | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_readable_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (eaccess(StringValueCStr(fname), R_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.readable_real?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is readable by the real | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_readable_real_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (access_internal(StringValueCStr(fname), R_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
#ifndef S_IRUGO | |
# define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) | |
#endif | |
#ifndef S_IWUGO | |
# define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) | |
#endif | |
/* | |
* call-seq: | |
* File.world_readable?(file_name) -> fixnum or nil | |
* | |
* If <i>file_name</i> is readable by others, returns an integer | |
* representing the file permission bits of <i>file_name</i>. Returns | |
* <code>nil</code> otherwise. The meaning of the bits is platform | |
* dependent; on Unix systems, see <code>stat(2)</code>. | |
* | |
* File.world_readable?("/etc/passwd") #=> 420 | |
* m = File.world_readable?("/etc/passwd") | |
* sprintf("%o", m) #=> "644" | |
*/ | |
static VALUE | |
rb_file_world_readable_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_IROTH | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qnil; | |
if ((st.st_mode & (S_IROTH)) == S_IROTH) { | |
return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); | |
} | |
#endif | |
return Qnil; | |
} | |
/* | |
* call-seq: | |
* File.writable?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is writable by the effective | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_writable_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (eaccess(StringValueCStr(fname), W_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.writable_real?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is writable by the real | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_writable_real_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (access_internal(StringValueCStr(fname), W_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.world_writable?(file_name) -> fixnum or nil | |
* | |
* If <i>file_name</i> is writable by others, returns an integer | |
* representing the file permission bits of <i>file_name</i>. Returns | |
* <code>nil</code> otherwise. The meaning of the bits is platform | |
* dependent; on Unix systems, see <code>stat(2)</code>. | |
* | |
* File.world_writable?("/tmp") #=> 511 | |
* m = File.world_writable?("/tmp") | |
* sprintf("%o", m) #=> "777" | |
*/ | |
static VALUE | |
rb_file_world_writable_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_IWOTH | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qnil; | |
if ((st.st_mode & (S_IWOTH)) == S_IWOTH) { | |
return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); | |
} | |
#endif | |
return Qnil; | |
} | |
/* | |
* call-seq: | |
* File.executable?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is executable by the effective | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_executable_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (eaccess(StringValueCStr(fname), X_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.executable_real?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file is executable by the real | |
* user id of this process. | |
*/ | |
static VALUE | |
rb_file_executable_real_p(VALUE obj, VALUE fname) | |
{ | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (access_internal(StringValueCStr(fname), X_OK) < 0) return Qfalse; | |
return Qtrue; | |
} | |
#ifndef S_ISREG | |
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | |
#endif | |
/* | |
* call-seq: | |
* File.file?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file exists and is a | |
* regular file. | |
*/ | |
static VALUE | |
rb_file_file_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (S_ISREG(st.st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.zero?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file exists and has | |
* a zero size. | |
*/ | |
static VALUE | |
rb_file_zero_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (st.st_size == 0) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.size?(file_name) -> Integer or nil | |
* | |
* Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the | |
* file otherwise. | |
*/ | |
static VALUE | |
rb_file_size_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qnil; | |
if (st.st_size == 0) return Qnil; | |
return OFFT2NUM(st.st_size); | |
} | |
/* | |
* call-seq: | |
* File.owned?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file exists and the | |
* effective used id of the calling process is the owner of | |
* the file. | |
*/ | |
static VALUE | |
rb_file_owned_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (st.st_uid == geteuid()) return Qtrue; | |
return Qfalse; | |
} | |
static VALUE | |
rb_file_rowned_p(VALUE obj, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (st.st_uid == getuid()) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* File.grpowned?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file exists and the | |
* effective group id of the calling process is the owner of | |
* the file. Returns <code>false</code> on Windows. | |
*/ | |
static VALUE | |
rb_file_grpowned_p(VALUE obj, VALUE fname) | |
{ | |
#ifndef _WIN32 | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) return Qfalse; | |
if (rb_group_member(st.st_gid)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
#if defined(S_ISUID) || defined(S_ISGID) || defined(S_ISVTX) | |
static VALUE | |
check3rdbyte(VALUE fname, int mode) | |
{ | |
struct stat st; | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (STAT(StringValueCStr(fname), &st) < 0) return Qfalse; | |
if (st.st_mode & mode) return Qtrue; | |
return Qfalse; | |
} | |
#endif | |
/* | |
* call-seq: | |
* File.setuid?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file has the setuid bit set. | |
*/ | |
static VALUE | |
rb_file_suid_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_ISUID | |
return check3rdbyte(fname, S_ISUID); | |
#else | |
return Qfalse; | |
#endif | |
} | |
/* | |
* call-seq: | |
* File.setgid?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file has the setgid bit set. | |
*/ | |
static VALUE | |
rb_file_sgid_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_ISGID | |
return check3rdbyte(fname, S_ISGID); | |
#else | |
return Qfalse; | |
#endif | |
} | |
/* | |
* call-seq: | |
* File.sticky?(file_name) -> true or false | |
* | |
* Returns <code>true</code> if the named file has the sticky bit set. | |
*/ | |
static VALUE | |
rb_file_sticky_p(VALUE obj, VALUE fname) | |
{ | |
#ifdef S_ISVTX | |
return check3rdbyte(fname, S_ISVTX); | |
#else | |
return Qnil; | |
#endif | |
} | |
/* | |
* call-seq: | |
* File.identical?(file_1, file_2) -> true or false | |
* | |
* Returns <code>true</code> if the named files are identical. | |
* | |
* open("a", "w") {} | |
* p File.identical?("a", "a") #=> true | |
* p File.identical?("a", "./a") #=> true | |
* File.link("a", "b") | |
* p File.identical?("a", "b") #=> true | |
* File.symlink("a", "c") | |
* p File.identical?("a", "c") #=> true | |
* open("d", "w") {} | |
* p File.identical?("a", "d") #=> false | |
*/ | |
static VALUE | |
rb_file_identical_p(VALUE obj, VALUE fname1, VALUE fname2) | |
{ | |
#ifndef DOSISH | |
struct stat st1, st2; | |
if (rb_stat(fname1, &st1) < 0) return Qfalse; | |
if (rb_stat(fname2, &st2) < 0) return Qfalse; | |
if (st1.st_dev != st2.st_dev) return Qfalse; | |
if (st1.st_ino != st2.st_ino) return Qfalse; | |
#else | |
# ifdef _WIN32 | |
BY_HANDLE_FILE_INFORMATION st1, st2; | |
HANDLE f1 = 0, f2 = 0; | |
# endif | |
rb_secure(2); | |
# ifdef _WIN32 | |
f1 = w32_io_info(&fname1, &st1); | |
if (f1 == INVALID_HANDLE_VALUE) return Qfalse; | |
f2 = w32_io_info(&fname2, &st2); | |
if (f1) CloseHandle(f1); | |
if (f2 == INVALID_HANDLE_VALUE) return Qfalse; | |
if (f2) CloseHandle(f2); | |
if (st1.dwVolumeSerialNumber == st2.dwVolumeSerialNumber && | |
st1.nFileIndexHigh == st2.nFileIndexHigh && | |
st1.nFileIndexLow == st2.nFileIndexLow) | |
return Qtrue; | |
if (!f1 || !f2) return Qfalse; | |
if (rb_w32_iswin95()) return Qfalse; | |
# else | |
FilePathValue(fname1); | |
fname1 = rb_str_new4(fname1); | |
fname1 = rb_str_encode_ospath(fname1); | |
FilePathValue(fname2); | |
fname2 = rb_str_encode_ospath(fname2); | |
if (access(RSTRING_PTR(fname1), 0)) return Qfalse; | |
if (access(RSTRING_PTR(fname2), 0)) return Qfalse; | |
# endif | |
fname1 = rb_file_expand_path(fname1, Qnil); | |
fname2 = rb_file_expand_path(fname2, Qnil); | |
if (RSTRING_LEN(fname1) != RSTRING_LEN(fname2)) return Qfalse; | |
if (rb_memcicmp(RSTRING_PTR(fname1), RSTRING_PTR(fname2), RSTRING_LEN(fname1))) | |
return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* File.size(file_name) -> integer | |
* | |
* Returns the size of <code>file_name</code>. | |
*/ | |
static VALUE | |
rb_file_s_size(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) { | |
FilePathValue(fname); | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return OFFT2NUM(st.st_size); | |
} | |
static VALUE | |
rb_file_ftype(const struct stat *st) | |
{ | |
const char *t; | |
if (S_ISREG(st->st_mode)) { | |
t = "file"; | |
} | |
else if (S_ISDIR(st->st_mode)) { | |
t = "directory"; | |
} | |
else if (S_ISCHR(st->st_mode)) { | |
t = "characterSpecial"; | |
} | |
#ifdef S_ISBLK | |
else if (S_ISBLK(st->st_mode)) { | |
t = "blockSpecial"; | |
} | |
#endif | |
#ifdef S_ISFIFO | |
else if (S_ISFIFO(st->st_mode)) { | |
t = "fifo"; | |
} | |
#endif | |
#ifdef S_ISLNK | |
else if (S_ISLNK(st->st_mode)) { | |
t = "link"; | |
} | |
#endif | |
#ifdef S_ISSOCK | |
else if (S_ISSOCK(st->st_mode)) { | |
t = "socket"; | |
} | |
#endif | |
else { | |
t = "unknown"; | |
} | |
return rb_usascii_str_new2(t); | |
} | |
/* | |
* call-seq: | |
* File.ftype(file_name) -> string | |
* | |
* Identifies the type of the named file; the return string is one of | |
* ``<code>file</code>'', ``<code>directory</code>'', | |
* ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'', | |
* ``<code>fifo</code>'', ``<code>link</code>'', | |
* ``<code>socket</code>'', or ``<code>unknown</code>''. | |
* | |
* File.ftype("testfile") #=> "file" | |
* File.ftype("/dev/tty") #=> "characterSpecial" | |
* File.ftype("/tmp/.X11-unix/X0") #=> "socket" | |
*/ | |
static VALUE | |
rb_file_s_ftype(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (lstat(StringValueCStr(fname), &st) == -1) { | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return rb_file_ftype(&st); | |
} | |
/* | |
* call-seq: | |
* File.atime(file_name) -> time | |
* | |
* Returns the last access time for the named file as a Time object). | |
* | |
* File.atime("testfile") #=> Wed Apr 09 08:51:48 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_s_atime(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) { | |
FilePathValue(fname); | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return stat_atime(&st); | |
} | |
/* | |
* call-seq: | |
* file.atime -> time | |
* | |
* Returns the last access time (a <code>Time</code> object) | |
* for <i>file</i>, or epoch if <i>file</i> has not been accessed. | |
* | |
* File.new("testfile").atime #=> Wed Dec 31 18:00:00 CST 1969 | |
* | |
*/ | |
static VALUE | |
rb_file_atime(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
struct stat st; | |
GetOpenFile(obj, fptr); | |
if (fstat(fptr->fd, &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return stat_atime(&st); | |
} | |
/* | |
* call-seq: | |
* File.mtime(file_name) -> time | |
* | |
* Returns the modification time for the named file as a Time object. | |
* | |
* File.mtime("testfile") #=> Tue Apr 08 12:58:04 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_s_mtime(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) { | |
FilePathValue(fname); | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return stat_mtime(&st); | |
} | |
/* | |
* call-seq: | |
* file.mtime -> time | |
* | |
* Returns the modification time for <i>file</i>. | |
* | |
* File.new("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_mtime(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
struct stat st; | |
GetOpenFile(obj, fptr); | |
if (fstat(fptr->fd, &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return stat_mtime(&st); | |
} | |
/* | |
* call-seq: | |
* File.ctime(file_name) -> time | |
* | |
* Returns the change time for the named file (the time at which | |
* directory information about the file was changed, not the file | |
* itself). | |
* | |
* Note that on Windows (NTFS), returns creation time (birth time). | |
* | |
* File.ctime("testfile") #=> Wed Apr 09 08:53:13 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_s_ctime(VALUE klass, VALUE fname) | |
{ | |
struct stat st; | |
if (rb_stat(fname, &st) < 0) { | |
FilePathValue(fname); | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
return stat_ctime(&st); | |
} | |
/* | |
* call-seq: | |
* file.ctime -> time | |
* | |
* Returns the change time for <i>file</i> (that is, the time directory | |
* information about the file was changed, not the file itself). | |
* | |
* Note that on Windows (NTFS), returns creation time (birth time). | |
* | |
* File.new("testfile").ctime #=> Wed Apr 09 08:53:14 CDT 2003 | |
* | |
*/ | |
static VALUE | |
rb_file_ctime(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
struct stat st; | |
GetOpenFile(obj, fptr); | |
if (fstat(fptr->fd, &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return stat_ctime(&st); | |
} | |
/* | |
* call-seq: | |
* file.size -> integer | |
* | |
* Returns the size of <i>file</i> in bytes. | |
* | |
* File.new("testfile").size #=> 66 | |
* | |
*/ | |
static VALUE | |
rb_file_size(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
struct stat st; | |
GetOpenFile(obj, fptr); | |
if (fptr->mode & FMODE_WRITABLE) { | |
rb_io_flush(obj); | |
} | |
if (fstat(fptr->fd, &st) == -1) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
return OFFT2NUM(st.st_size); | |
} | |
static void | |
chmod_internal(const char *path, void *mode) | |
{ | |
if (chmod(path, *(int *)mode) < 0) | |
rb_sys_fail(path); | |
} | |
/* | |
* call-seq: | |
* File.chmod(mode_int, file_name, ... ) -> integer | |
* | |
* Changes permission bits on the named file(s) to the bit pattern | |
* represented by <i>mode_int</i>. Actual effects are operating system | |
* dependent (see the beginning of this section). On Unix systems, see | |
* <code>chmod(2)</code> for details. Returns the number of files | |
* processed. | |
* | |
* File.chmod(0644, "testfile", "out") #=> 2 | |
*/ | |
static VALUE | |
rb_file_s_chmod(int argc, VALUE *argv) | |
{ | |
VALUE vmode; | |
VALUE rest; | |
int mode; | |
long n; | |
rb_secure(2); | |
rb_scan_args(argc, argv, "1*", &vmode, &rest); | |
mode = NUM2INT(vmode); | |
n = apply2files(chmod_internal, rest, &mode); | |
return LONG2FIX(n); | |
} | |
/* | |
* call-seq: | |
* file.chmod(mode_int) -> 0 | |
* | |
* Changes permission bits on <i>file</i> to the bit pattern | |
* represented by <i>mode_int</i>. Actual effects are platform | |
* dependent; on Unix systems, see <code>chmod(2)</code> for details. | |
* Follows symbolic links. Also see <code>File#lchmod</code>. | |
* | |
* f = File.new("out", "w"); | |
* f.chmod(0644) #=> 0 | |
*/ | |
static VALUE | |
rb_file_chmod(VALUE obj, VALUE vmode) | |
{ | |
rb_io_t *fptr; | |
int mode; | |
#ifndef HAVE_FCHMOD | |
VALUE path; | |
#endif | |
rb_secure(2); | |
mode = NUM2INT(vmode); | |
GetOpenFile(obj, fptr); | |
#ifdef HAVE_FCHMOD | |
if (fchmod(fptr->fd, mode) == -1) | |
rb_sys_fail_path(fptr->pathv); | |
#else | |
if (NIL_P(fptr->pathv)) return Qnil; | |
path = rb_str_encode_ospath(fptr->pathv); | |
if (chmod(RSTRING_PTR(path), mode) == -1) | |
rb_sys_fail_path(fptr->pathv); | |
#endif | |
return INT2FIX(0); | |
} | |
#if defined(HAVE_LCHMOD) | |
static void | |
lchmod_internal(const char *path, void *mode) | |
{ | |
if (lchmod(path, (int)(VALUE)mode) < 0) | |
rb_sys_fail(path); | |
} | |
/* | |
* call-seq: | |
* File.lchmod(mode_int, file_name, ...) -> integer | |
* | |
* Equivalent to <code>File::chmod</code>, but does not follow symbolic | |
* links (so it will change the permissions associated with the link, | |
* not the file referenced by the link). Often not available. | |
* | |
*/ | |
static VALUE | |
rb_file_s_lchmod(int argc, VALUE *argv) | |
{ | |
VALUE vmode; | |
VALUE rest; | |
long mode, n; | |
rb_secure(2); | |
rb_scan_args(argc, argv, "1*", &vmode, &rest); | |
mode = NUM2INT(vmode); | |
n = apply2files(lchmod_internal, rest, (void *)(long)mode); | |
return LONG2FIX(n); | |
} | |
#else | |
#define rb_file_s_lchmod rb_f_notimplement | |
#endif | |
struct chown_args { | |
rb_uid_t owner; | |
rb_gid_t group; | |
}; | |
static void | |
chown_internal(const char *path, void *arg) | |
{ | |
struct chown_args *args = arg; | |
if (chown(path, args->owner, args->group) < 0) | |
rb_sys_fail(path); | |
} | |
/* | |
* call-seq: | |
* File.chown(owner_int, group_int, file_name,... ) -> integer | |
* | |
* Changes the owner and group of the named file(s) to the given | |
* numeric owner and group id's. Only a process with superuser | |
* privileges may change the owner of a file. The current owner of a | |
* file may change the file's group to any group to which the owner | |
* belongs. A <code>nil</code> or -1 owner or group id is ignored. | |
* Returns the number of files processed. | |
* | |
* File.chown(nil, 100, "testfile") | |
* | |
*/ | |
static VALUE | |
rb_file_s_chown(int argc, VALUE *argv) | |
{ | |
VALUE o, g, rest; | |
struct chown_args arg; | |
long n; | |
rb_secure(2); | |
rb_scan_args(argc, argv, "2*", &o, &g, &rest); | |
if (NIL_P(o)) { | |
arg.owner = -1; | |
} | |
else { | |
arg.owner = NUM2UIDT(o); | |
} | |
if (NIL_P(g)) { | |
arg.group = -1; | |
} | |
else { | |
arg.group = NUM2GIDT(g); | |
} | |
n = apply2files(chown_internal, rest, &arg); | |
return LONG2FIX(n); | |
} | |
/* | |
* call-seq: | |
* file.chown(owner_int, group_int ) -> 0 | |
* | |
* Changes the owner and group of <i>file</i> to the given numeric | |
* owner and group id's. Only a process with superuser privileges may | |
* change the owner of a file. The current owner of a file may change | |
* the file's group to any group to which the owner belongs. A | |
* <code>nil</code> or -1 owner or group id is ignored. Follows | |
* symbolic links. See also <code>File#lchown</code>. | |
* | |
* File.new("testfile").chown(502, 1000) | |
* | |
*/ | |
static VALUE | |
rb_file_chown(VALUE obj, VALUE owner, VALUE group) | |
{ | |
rb_io_t *fptr; | |
int o, g; | |
#ifndef HAVE_FCHOWN | |
VALUE path; | |
#endif | |
rb_secure(2); | |
o = NIL_P(owner) ? -1 : NUM2INT(owner); | |
g = NIL_P(group) ? -1 : NUM2INT(group); | |
GetOpenFile(obj, fptr); | |
#ifndef HAVE_FCHOWN | |
if (NIL_P(fptr->pathv)) return Qnil; | |
path = rb_str_encode_ospath(fptr->pathv); | |
if (chown(RSTRING_PTR(path), o, g) == -1) | |
rb_sys_fail_path(fptr->pathv); | |
#else | |
if (fchown(fptr->fd, o, g) == -1) | |
rb_sys_fail_path(fptr->pathv); | |
#endif | |
return INT2FIX(0); | |
} | |
#if defined(HAVE_LCHOWN) | |
static void | |
lchown_internal(const char *path, void *arg) | |
{ | |
struct chown_args *args = arg; | |
if (lchown(path, args->owner, args->group) < 0) | |
rb_sys_fail(path); | |
} | |
/* | |
* call-seq: | |
* file.lchown(owner_int, group_int, file_name,..) -> integer | |
* | |
* Equivalent to <code>File::chown</code>, but does not follow symbolic | |
* links (so it will change the owner associated with the link, not the | |
* file referenced by the link). Often not available. Returns number | |
* of files in the argument list. | |
* | |
*/ | |
static VALUE | |
rb_file_s_lchown(int argc, VALUE *argv) | |
{ | |
VALUE o, g, rest; | |
struct chown_args arg; | |
long n; | |
rb_secure(2); | |
rb_scan_args(argc, argv, "2*", &o, &g, &rest); | |
if (NIL_P(o)) { | |
arg.owner = -1; | |
} | |
else { | |
arg.owner = NUM2UIDT(o); | |
} | |
if (NIL_P(g)) { | |
arg.group = -1; | |
} | |
else { | |
arg.group = NUM2GIDT(g); | |
} | |
n = apply2files(lchown_internal, rest, &arg); | |
return LONG2FIX(n); | |
} | |
#else | |
#define rb_file_s_lchown rb_f_notimplement | |
#endif | |
struct utime_args { | |
const struct timespec* tsp; | |
VALUE atime, mtime; | |
}; | |
#if defined DOSISH || defined __CYGWIN__ | |
NORETURN(static void utime_failed(const char *, const struct timespec *, VALUE, VALUE)); | |
static void | |
utime_failed(const char *path, const struct timespec *tsp, VALUE atime, VALUE mtime) | |
{ | |
if (tsp && errno == EINVAL) { | |
VALUE e[2], a = Qnil, m = Qnil; | |
int d = 0; | |
if (!NIL_P(atime)) { | |
a = rb_inspect(atime); | |
} | |
if (!NIL_P(mtime) && mtime != atime && !rb_equal(atime, mtime)) { | |
m = rb_inspect(mtime); | |
} | |
if (NIL_P(a)) e[0] = m; | |
else if (NIL_P(m) || rb_str_cmp(a, m) == 0) e[0] = a; | |
else { | |
e[0] = rb_str_plus(a, rb_str_new_cstr(" or ")); | |
rb_str_append(e[0], m); | |
d = 1; | |
} | |
if (!NIL_P(e[0])) { | |
if (path) { | |
if (!d) e[0] = rb_str_dup(e[0]); | |
rb_str_cat2(rb_str_cat2(e[0], " for "), path); | |
} | |
e[1] = INT2FIX(EINVAL); | |
rb_exc_raise(rb_class_new_instance(2, e, rb_eSystemCallError)); | |
} | |
errno = EINVAL; | |
} | |
rb_sys_fail(path); | |
} | |
#else | |
#define utime_failed(path, tsp, atime, mtime) rb_sys_fail(path) | |
#endif | |
#if defined(HAVE_UTIMES) | |
static void | |
utime_internal(const char *path, void *arg) | |
{ | |
struct utime_args *v = arg; | |
const struct timespec *tsp = v->tsp; | |
struct timeval tvbuf[2], *tvp = NULL; | |
#ifdef HAVE_UTIMENSAT | |
static int try_utimensat = 1; | |
if (try_utimensat) { | |
if (utimensat(AT_FDCWD, path, tsp, 0) < 0) { | |
if (errno == ENOSYS) { | |
try_utimensat = 0; | |
goto no_utimensat; | |
} | |
utime_failed(path, tsp, v->atime, v->mtime); | |
} | |
return; | |
} | |
no_utimensat: | |
#endif | |
if (tsp) { | |
tvbuf[0].tv_sec = tsp[0].tv_sec; | |
tvbuf[0].tv_usec = (int)(tsp[0].tv_nsec / 1000); | |
tvbuf[1].tv_sec = tsp[1].tv_sec; | |
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000); | |
tvp = tvbuf; | |
} | |
if (utimes(path, tvp) < 0) | |
utime_failed(path, tsp, v->atime, v->mtime); | |
} | |
#else | |
#if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H | |
struct utimbuf { | |
long actime; | |
long modtime; | |
}; | |
#endif | |
static void | |
utime_internal(const char *path, void *arg) | |
{ | |
struct utime_args *v = arg; | |
const struct timespec *tsp = v->tsp; | |
struct utimbuf utbuf, *utp = NULL; | |
if (tsp) { | |
utbuf.actime = tsp[0].tv_sec; | |
utbuf.modtime = tsp[1].tv_sec; | |
utp = &utbuf; | |
} | |
if (utime(path, utp) < 0) | |
utime_failed(path, tsp, v->atime, v->mtime); | |
} | |
#endif | |
/* | |
* call-seq: | |
* File.utime(atime, mtime, file_name,...) -> integer | |
* | |
* Sets the access and modification times of each | |
* named file to the first two arguments. Returns | |
* the number of file names in the argument list. | |
*/ | |
static VALUE | |
rb_file_s_utime(int argc, VALUE *argv) | |
{ | |
VALUE rest; | |
struct utime_args args; | |
struct timespec tss[2], *tsp = NULL; | |
long n; | |
rb_secure(2); | |
rb_scan_args(argc, argv, "2*", &args.atime, &args.mtime, &rest); | |
if (!NIL_P(args.atime) || !NIL_P(args.mtime)) { | |
tsp = tss; | |
tsp[0] = rb_time_timespec(args.atime); | |
tsp[1] = rb_time_timespec(args.mtime); | |
} | |
args.tsp = tsp; | |
n = apply2files(utime_internal, rest, &args); | |
return LONG2FIX(n); | |
} | |
NORETURN(static void sys_fail2(VALUE,VALUE)); | |
static void | |
sys_fail2(VALUE s1, VALUE s2) | |
{ | |
char *buf; | |
#ifdef MAX_PATH | |
const int max_pathlen = MAX_PATH; | |
#else | |
const int max_pathlen = MAXPATHLEN; | |
#endif | |
const char *e1, *e2; | |
int len = 5; | |
long l1 = RSTRING_LEN(s1), l2 = RSTRING_LEN(s2); | |
e1 = e2 = ""; | |
if (l1 > max_pathlen) { | |
l1 = max_pathlen - 3; | |
e1 = "..."; | |
len += 3; | |
} | |
if (l2 > max_pathlen) { | |
l2 = max_pathlen - 3; | |
e2 = "..."; | |
len += 3; | |
} | |
len += (int)l1 + (int)l2; | |
buf = ALLOCA_N(char, len); | |
snprintf(buf, len, "(%.*s%s, %.*s%s)", | |
(int)l1, RSTRING_PTR(s1), e1, | |
(int)l2, RSTRING_PTR(s2), e2); | |
rb_sys_fail(buf); | |
} | |
#ifdef HAVE_LINK | |
/* | |
* call-seq: | |
* File.link(old_name, new_name) -> 0 | |
* | |
* Creates a new name for an existing file using a hard link. Will not | |
* overwrite <i>new_name</i> if it already exists (raising a subclass | |
* of <code>SystemCallError</code>). Not available on all platforms. | |
* | |
* File.link("testfile", ".testfile") #=> 0 | |
* IO.readlines(".testfile")[0] #=> "This is line one\n" | |
*/ | |
static VALUE | |
rb_file_s_link(VALUE klass, VALUE from, VALUE to) | |
{ | |
rb_secure(2); | |
FilePathValue(from); | |
FilePathValue(to); | |
from = rb_str_encode_ospath(from); | |
to = rb_str_encode_ospath(to); | |
if (link(StringValueCStr(from), StringValueCStr(to)) < 0) { | |
sys_fail2(from, to); | |
} | |
return INT2FIX(0); | |
} | |
#else | |
#define rb_file_s_link rb_f_notimplement | |
#endif | |
#ifdef HAVE_SYMLINK | |
/* | |
* call-seq: | |
* File.symlink(old_name, new_name) -> 0 | |
* | |
* Creates a symbolic link called <i>new_name</i> for the existing file | |
* <i>old_name</i>. Raises a <code>NotImplemented</code> exception on | |
* platforms that do not support symbolic links. | |
* | |
* File.symlink("testfile", "link2test") #=> 0 | |
* | |
*/ | |
static VALUE | |
rb_file_s_symlink(VALUE klass, VALUE from, VALUE to) | |
{ | |
rb_secure(2); | |
FilePathValue(from); | |
FilePathValue(to); | |
from = rb_str_encode_ospath(from); | |
to = rb_str_encode_ospath(to); | |
if (symlink(StringValueCStr(from), StringValueCStr(to)) < 0) { | |
sys_fail2(from, to); | |
} | |
return INT2FIX(0); | |
} | |
#else | |
#define rb_file_s_symlink rb_f_notimplement | |
#endif | |
#ifdef HAVE_READLINK | |
/* | |
* call-seq: | |
* File.readlink(link_name) -> file_name | |
* | |
* Returns the name of the file referenced by the given link. | |
* Not available on all platforms. | |
* | |
* File.symlink("testfile", "link2test") #=> 0 | |
* File.readlink("link2test") #=> "testfile" | |
*/ | |
static VALUE | |
rb_file_s_readlink(VALUE klass, VALUE path) | |
{ | |
char *buf; | |
int size = 100; | |
ssize_t rv; | |
VALUE v; | |
rb_secure(2); | |
FilePathValue(path); | |
path = rb_str_encode_ospath(path); | |
buf = xmalloc(size); | |
while ((rv = readlink(RSTRING_PTR(path), buf, size)) == size | |
#ifdef _AIX | |
|| (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */ | |
#endif | |
) { | |
size *= 2; | |
buf = xrealloc(buf, size); | |
} | |
if (rv < 0) { | |
xfree(buf); | |
rb_sys_fail_path(path); | |
} | |
v = rb_filesystem_str_new(buf, rv); | |
xfree(buf); | |
return v; | |
} | |
#else | |
#define rb_file_s_readlink rb_f_notimplement | |
#endif | |
static void | |
unlink_internal(const char *path, void *arg) | |
{ | |
if (unlink(path) < 0) | |
rb_sys_fail(path); | |
} | |
/* | |
* call-seq: | |
* File.delete(file_name, ...) -> integer | |
* File.unlink(file_name, ...) -> integer | |
* | |
* Deletes the named files, returning the number of names | |
* passed as arguments. Raises an exception on any error. | |
* See also <code>Dir::rmdir</code>. | |
*/ | |
static VALUE | |
rb_file_s_unlink(VALUE klass, VALUE args) | |
{ | |
long n; | |
rb_secure(2); | |
n = apply2files(unlink_internal, args, 0); | |
return LONG2FIX(n); | |
} | |
/* | |
* call-seq: | |
* File.rename(old_name, new_name) -> 0 | |
* | |
* Renames the given file to the new name. Raises a | |
* <code>SystemCallError</code> if the file cannot be renamed. | |
* | |
* File.rename("afile", "afile.bak") #=> 0 | |
*/ | |
static VALUE | |
rb_file_s_rename(VALUE klass, VALUE from, VALUE to) | |
{ | |
const char *src, *dst; | |
VALUE f, t; | |
rb_secure(2); | |
FilePathValue(from); | |
FilePathValue(to); | |
f = rb_str_encode_ospath(from); | |
t = rb_str_encode_ospath(to); | |
src = StringValueCStr(f); | |
dst = StringValueCStr(t); | |
#if defined __CYGWIN__ | |
errno = 0; | |
#endif | |
if (rename(src, dst) < 0) { | |
#if defined DOSISH | |
switch (errno) { | |
case EEXIST: | |
#if defined (__EMX__) | |
case EACCES: | |
#endif | |
if (chmod(dst, 0666) == 0 && | |
unlink(dst) == 0 && | |
rename(src, dst) == 0) | |
return INT2FIX(0); | |
} | |
#endif | |
sys_fail2(from, to); | |
} | |
return INT2FIX(0); | |
} | |
/* | |
* call-seq: | |
* File.umask() -> integer | |
* File.umask(integer) -> integer | |
* | |
* Returns the current umask value for this process. If the optional | |
* argument is given, set the umask to that value and return the | |
* previous value. Umask values are <em>subtracted</em> from the | |
* default permissions, so a umask of <code>0222</code> would make a | |
* file read-only for everyone. | |
* | |
* File.umask(0006) #=> 18 | |
* File.umask #=> 6 | |
*/ | |
static VALUE | |
rb_file_s_umask(int argc, VALUE *argv) | |
{ | |
int omask = 0; | |
rb_secure(2); | |
if (argc == 0) { | |
omask = umask(0); | |
umask(omask); | |
} | |
else if (argc == 1) { | |
omask = umask(NUM2INT(argv[0])); | |
} | |
else { | |
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0..1)", argc); | |
} | |
return INT2FIX(omask); | |
} | |
#ifdef __CYGWIN__ | |
#undef DOSISH | |
#endif | |
#if defined __CYGWIN__ || defined DOSISH | |
#define DOSISH_UNC | |
#define DOSISH_DRIVE_LETTER | |
#define FILE_ALT_SEPARATOR '\\' | |
#endif | |
#ifdef FILE_ALT_SEPARATOR | |
#define isdirsep(x) ((x) == '/' || (x) == FILE_ALT_SEPARATOR) | |
static const char file_alt_separator[] = {FILE_ALT_SEPARATOR, '\0'}; | |
#else | |
#define isdirsep(x) ((x) == '/') | |
#endif | |
#ifndef USE_NTFS | |
#if defined _WIN32 || defined __CYGWIN__ | |
#define USE_NTFS 1 | |
#else | |
#define USE_NTFS 0 | |
#endif | |
#endif | |
#if USE_NTFS | |
#define istrailinggarbage(x) ((x) == '.' || (x) == ' ') | |
#else | |
#define istrailinggarbage(x) 0 | |
#endif | |
#ifndef CharNext /* defined as CharNext[AW] on Windows. */ | |
# define CharNext(p) ((p) + 1) | |
#endif | |
#if defined(DOSISH_UNC) | |
#define has_unc(buf) (isdirsep((buf)[0]) && isdirsep((buf)[1])) | |
#else | |
#define has_unc(buf) 0 | |
#endif | |
#ifdef DOSISH_DRIVE_LETTER | |
static inline int | |
has_drive_letter(const char *buf) | |
{ | |
if (ISALPHA(buf[0]) && buf[1] == ':') { | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} | |
static char* | |
getcwdofdrv(int drv) | |
{ | |
char drive[4]; | |
char *drvcwd, *oldcwd; | |
drive[0] = drv; | |
drive[1] = ':'; | |
drive[2] = '\0'; | |
/* the only way that I know to get the current directory | |
of a particular drive is to change chdir() to that drive, | |
so save the old cwd before chdir() | |
*/ | |
oldcwd = my_getcwd(); | |
if (chdir(drive) == 0) { | |
drvcwd = my_getcwd(); | |
chdir(oldcwd); | |
xfree(oldcwd); | |
} | |
else { | |
/* perhaps the drive is not exist. we return only drive letter */ | |
drvcwd = strdup(drive); | |
} | |
return drvcwd; | |
} | |
static inline int | |
not_same_drive(VALUE path, int drive) | |
{ | |
const char *p = RSTRING_PTR(path); | |
if (RSTRING_LEN(path) < 2) return 0; | |
if (has_drive_letter(p)) { | |
return TOLOWER(p[0]) != TOLOWER(drive); | |
} | |
else { | |
return has_unc(p); | |
} | |
} | |
#endif | |
static inline char * | |
skiproot(const char *path) | |
{ | |
#ifdef DOSISH_DRIVE_LETTER | |
if (has_drive_letter(path)) path += 2; | |
#endif | |
while (isdirsep(*path)) path++; | |
return (char *)path; | |
} | |
#define nextdirsep rb_path_next | |
char * | |
rb_path_next(const char *s) | |
{ | |
while (*s && !isdirsep(*s)) { | |
s = CharNext(s); | |
} | |
return (char *)s; | |
} | |
#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) | |
#define skipprefix rb_path_skip_prefix | |
#else | |
#define skipprefix(path) (path) | |
#endif | |
char * | |
rb_path_skip_prefix(const char *path) | |
{ | |
#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) | |
#ifdef DOSISH_UNC | |
if (isdirsep(path[0]) && isdirsep(path[1])) { | |
path += 2; | |
while (isdirsep(*path)) path++; | |
if (*(path = nextdirsep(path)) && path[1] && !isdirsep(path[1])) | |
path = nextdirsep(path + 1); | |
return (char *)path; | |
} | |
#endif | |
#ifdef DOSISH_DRIVE_LETTER | |
if (has_drive_letter(path)) | |
return (char *)(path + 2); | |
#endif | |
#endif | |
return (char *)path; | |
} | |
static inline char * | |
skipprefixroot(const char *path) | |
{ | |
#if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) | |
char *p = skipprefix(path); | |
while (isdirsep(*p)) p++; | |
return p; | |
#else | |
return skiproot(path); | |
#endif | |
} | |
#define strrdirsep rb_path_last_separator | |
char * | |
rb_path_last_separator(const char *path) | |
{ | |
char *last = NULL; | |
while (*path) { | |
if (isdirsep(*path)) { | |
const char *tmp = path++; | |
while (isdirsep(*path)) path++; | |
if (!*path) break; | |
last = (char *)tmp; | |
} | |
else { | |
path = CharNext(path); | |
} | |
} | |
return last; | |
} | |
static char * | |
chompdirsep(const char *path) | |
{ | |
while (*path) { | |
if (isdirsep(*path)) { | |
const char *last = path++; | |
while (isdirsep(*path)) path++; | |
if (!*path) return (char *)last; | |
} | |
else { | |
path = CharNext(path); | |
} | |
} | |
return (char *)path; | |
} | |
char * | |
rb_path_end(const char *path) | |
{ | |
if (isdirsep(*path)) path++; | |
return chompdirsep(path); | |
} | |
#if USE_NTFS | |
static char * | |
ntfs_tail(const char *path) | |
{ | |
while (*path == '.') path++; | |
while (*path && *path != ':') { | |
if (istrailinggarbage(*path)) { | |
const char *last = path++; | |
while (istrailinggarbage(*path)) path++; | |
if (!*path || *path == ':') return (char *)last; | |
} | |
else if (isdirsep(*path)) { | |
const char *last = path++; | |
while (isdirsep(*path)) path++; | |
if (!*path) return (char *)last; | |
if (*path == ':') path++; | |
} | |
else { | |
path = CharNext(path); | |
} | |
} | |
return (char *)path; | |
} | |
#endif | |
#define BUFCHECK(cond) do {\ | |
bdiff = p - buf;\ | |
if (cond) {\ | |
do {buflen *= 2;} while (cond);\ | |
rb_str_resize(result, buflen);\ | |
buf = RSTRING_PTR(result);\ | |
p = buf + bdiff;\ | |
pend = buf + buflen;\ | |
}\ | |
} while (0) | |
#define BUFINIT() (\ | |
p = buf = RSTRING_PTR(result),\ | |
buflen = RSTRING_LEN(result),\ | |
pend = p + buflen) | |
#define EXPAND_PATH()\ | |
if ( !(abs_mode & FEP_DIR_EXPANDED) ) { \ | |
file_expand_path(dname, Qnil, abs_mode, result); \ | |
} \ | |
else { \ | |
size_t dlen = RSTRING_LEN(dname); \ | |
BUFCHECK(dlen > buflen); \ | |
strncpy(buf, RSTRING_PTR(dname), dlen + 1); \ | |
rb_str_set_len(result, dlen); \ | |
rb_enc_associate(result, rb_enc_check(result, dname)); \ | |
ENC_CODERANGE_CLEAR(result); \ | |
} | |
VALUE | |
rb_home_dir(const char *user, VALUE result) | |
{ | |
const char *dir; | |
char *buf; | |
#if defined DOSISH || defined __CYGWIN__ | |
char *p; | |
#endif | |
long dirlen; | |
if (!user || !*user) { | |
if (!(dir = getenv("HOME"))) { | |
rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding `~'"); | |
} | |
dirlen = strlen(dir); | |
rb_str_resize(result, dirlen); | |
memcpy(buf = RSTRING_PTR(result), dir, dirlen); | |
} | |
else { | |
#ifdef HAVE_PWD_H | |
struct passwd *pwPtr = getpwnam(user); | |
if (!pwPtr) { | |
endpwent(); | |
rb_raise(rb_eArgError, "user %s doesn't exist", user); | |
} | |
dirlen = strlen(pwPtr->pw_dir); | |
rb_str_resize(result, dirlen); | |
strcpy(buf = RSTRING_PTR(result), pwPtr->pw_dir); | |
endpwent(); | |
#else | |
return Qnil; | |
#endif | |
} | |
#if defined DOSISH || defined __CYGWIN__ | |
for (p = buf; *p; p = CharNext(p)) { | |
if (*p == '\\') { | |
*p = '/'; | |
} | |
} | |
#endif | |
rb_enc_associate_index(result, rb_filesystem_encindex()); | |
return result; | |
} | |
#define FEP_FILE_ABSOLUTE 1 | |
#define FEP_DIR_EXPANDED 2 | |
static VALUE | |
file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result) | |
{ | |
const char *s, *b; | |
char *buf, *p, *pend, *root; | |
size_t buflen, dirlen, bdiff; | |
int tainted; | |
s = StringValuePtr(fname); | |
BUFINIT(); | |
tainted = OBJ_TAINTED(fname); | |
if (s[0] == '~' && !(abs_mode & FEP_FILE_ABSOLUTE)) { /* execute only if NOT absolute_path() */ | |
long userlen = 0; | |
tainted = 1; | |
if (isdirsep(s[1]) || s[1] == '\0') { | |
buf = 0; | |
b = 0; | |
rb_str_set_len(result, 0); | |
if (*++s) ++s; | |
} | |
else { | |
s = nextdirsep(b = s); | |
userlen = s - b; | |
BUFCHECK(bdiff + userlen >= buflen); | |
memcpy(p, b, userlen); | |
rb_str_set_len(result, userlen); | |
buf = p + 1; | |
p += userlen; | |
} | |
if (NIL_P(rb_home_dir(buf, result))) { | |
rb_raise(rb_eArgError, "can't find user %s", buf); | |
} | |
if (!rb_is_absolute_path(RSTRING_PTR(result))) { | |
if (userlen) { | |
rb_raise(rb_eArgError, "non-absolute home of %.*s", (int)userlen, b); | |
} | |
else { | |
rb_raise(rb_eArgError, "non-absolute home"); | |
} | |
} | |
BUFINIT(); | |
p = pend; | |
} | |
#ifdef DOSISH_DRIVE_LETTER | |
/* skip drive letter */ | |
else if (has_drive_letter(s)) { | |
if (isdirsep(s[2])) { | |
/* specified drive letter, and full path */ | |
/* skip drive letter */ | |
BUFCHECK(bdiff + 2 >= buflen); | |
memcpy(p, s, 2); | |
p += 2; | |
s += 2; | |
rb_enc_copy(result, fname); | |
} | |
else { | |
/* specified drive, but not full path */ | |
int same = 0; | |
if (!NIL_P(dname) && !not_same_drive(dname, s[0])) { | |
EXPAND_PATH(); | |
BUFINIT(); | |
if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) { | |
/* ok, same drive */ | |
same = 1; | |
} | |
} | |
if (!same) { | |
char *dir = getcwdofdrv(*s); | |
tainted = 1; | |
dirlen = strlen(dir); | |
BUFCHECK(dirlen > buflen); | |
strcpy(buf, dir); | |
xfree(dir); | |
rb_enc_associate_index(result, rb_filesystem_encindex()); | |
} | |
else | |
rb_enc_associate(result, rb_enc_check(result, fname)); | |
p = chompdirsep(skiproot(buf)); | |
s += 2; | |
} | |
} | |
#endif | |
else if (!rb_is_absolute_path(s)) { | |
if (!NIL_P(dname)) { | |
EXPAND_PATH(); | |
BUFINIT(); | |
rb_enc_associate(result, rb_enc_check(result, fname)); | |
} | |
else { | |
char *dir = my_getcwd(); | |
tainted = 1; | |
dirlen = strlen(dir); | |
BUFCHECK(dirlen > buflen); | |
strcpy(buf, dir); | |
xfree(dir); | |
rb_enc_associate_index(result, rb_filesystem_encindex()); | |
} | |
#if defined DOSISH || defined __CYGWIN__ | |
if (isdirsep(*s)) { | |
/* specified full path, but not drive letter nor UNC */ | |
/* we need to get the drive letter or UNC share name */ | |
p = skipprefix(buf); | |
} | |
else | |
#endif | |
p = chompdirsep(skiproot(buf)); | |
} | |
else { | |
size_t len; | |
b = s; | |
do s++; while (isdirsep(*s)); | |
len = s - b; | |
p = buf + len; | |
BUFCHECK(bdiff >= buflen); | |
memset(buf, '/', len); | |
rb_str_set_len(result, len); | |
rb_enc_associate(result, rb_enc_check(result, fname)); | |
} | |
if (p > buf && p[-1] == '/') | |
--p; | |
else { | |
rb_str_set_len(result, p-buf); | |
BUFCHECK(bdiff + 1 >= buflen); | |
*p = '/'; | |
} | |
rb_str_set_len(result, p-buf+1); | |
BUFCHECK(bdiff + 1 >= buflen); | |
p[1] = 0; | |
root = skipprefix(buf); | |
b = s; | |
while (*s) { | |
switch (*s) { | |
case '.': | |
if (b == s++) { /* beginning of path element */ | |
switch (*s) { | |
case '\0': | |
b = s; | |
break; | |
case '.': | |
if (*(s+1) == '\0' || isdirsep(*(s+1))) { | |
/* We must go back to the parent */ | |
char *n; | |
*p = '\0'; | |
if (!(n = strrdirsep(root))) { | |
*p = '/'; | |
} | |
else { | |
p = n; | |
} | |
b = ++s; | |
} | |
#if USE_NTFS | |
else { | |
do ++s; while (istrailinggarbage(*s)); | |
} | |
#endif | |
break; | |
case '/': | |
#if defined DOSISH || defined __CYGWIN__ | |
case '\\': | |
#endif | |
b = ++s; | |
break; | |
default: | |
/* ordinary path element, beginning don't move */ | |
break; | |
} | |
} | |
#if USE_NTFS | |
else { | |
--s; | |
case ' ': { | |
const char *e = s; | |
while (istrailinggarbage(*s)) s++; | |
if (!*s) { | |
s = e; | |
goto endpath; | |
} | |
} | |
} | |
#endif | |
break; | |
case '/': | |
#if defined DOSISH || defined __CYGWIN__ | |
case '\\': | |
#endif | |
if (s > b) { | |
long rootdiff = root - buf; | |
rb_str_set_len(result, p-buf+1); | |
BUFCHECK(bdiff + (s-b+1) >= buflen); | |
root = buf + rootdiff; | |
memcpy(++p, b, s-b); | |
p += s-b; | |
*p = '/'; | |
} | |
b = ++s; | |
break; | |
default: | |
s = CharNext(s); | |
break; | |
} | |
} | |
if (s > b) { | |
#if USE_NTFS | |
static const char prime[] = ":$DATA"; | |
enum {prime_len = sizeof(prime) -1}; | |
endpath: | |
if (s > b + prime_len && strncasecmp(s - prime_len, prime, prime_len) == 0) { | |
/* alias of stream */ | |
/* get rid of a bug of x64 VC++ */ | |
if (*(s - (prime_len+1)) == ':') { | |
s -= prime_len + 1; /* prime */ | |
} | |
else if (memchr(b, ':', s - prime_len - b)) { | |
s -= prime_len; /* alternative */ | |
} | |
} | |
#endif | |
rb_str_set_len(result, p-buf+1); | |
BUFCHECK(bdiff + (s-b) >= buflen); | |
memcpy(++p, b, s-b); | |
p += s-b; | |
} | |
if (p == skiproot(buf) - 1) p++; | |
#if USE_NTFS | |
*p = '\0'; | |
if ((s = strrdirsep(b = buf)) != 0 && !strpbrk(s, "*?")) { | |
size_t len; | |
WIN32_FIND_DATA wfd; | |
HANDLE h; | |
#ifdef __CYGWIN__ | |
#ifdef HAVE_CYGWIN_CONV_PATH | |
char *w32buf = NULL; | |
const int flags = CCP_POSIX_TO_WIN_A | CCP_RELATIVE; | |
#else | |
char w32buf[MAXPATHLEN]; | |
#endif | |
const char *path; | |
ssize_t bufsize; | |
int lnk_added = 0, is_symlink = 0; | |
struct stat st; | |
p = (char *)s; | |
len = strlen(p); | |
if (lstat(buf, &st) == 0 && S_ISLNK(st.st_mode)) { | |
is_symlink = 1; | |
if (len > 4 && STRCASECMP(p + len - 4, ".lnk") != 0) { | |
lnk_added = 1; | |
} | |
} | |
path = *buf ? buf : "/"; | |
#ifdef HAVE_CYGWIN_CONV_PATH | |
bufsize = cygwin_conv_path(flags, path, NULL, 0); | |
if (bufsize > 0) { | |
bufsize += len; | |
if (lnk_added) bufsize += 4; | |
w32buf = ALLOCA_N(char, bufsize); | |
if (cygwin_conv_path(flags, path, w32buf, bufsize) == 0) { | |
b = w32buf; | |
} | |
} | |
#else | |
bufsize = MAXPATHLEN; | |
if (cygwin_conv_to_win32_path(path, w32buf) == 0) { | |
b = w32buf; | |
} | |
#endif | |
if (is_symlink && b == w32buf) { | |
*p = '\\'; | |
strlcat(w32buf, p, bufsize); | |
if (lnk_added) { | |
strlcat(w32buf, ".lnk", bufsize); | |
} | |
} | |
else { | |
lnk_added = 0; | |
} | |
*p = '/'; | |
#endif | |
h = FindFirstFile(b, &wfd); | |
if (h != INVALID_HANDLE_VALUE) { | |
FindClose(h); | |
len = strlen(wfd.cFileName); | |
#ifdef __CYGWIN__ | |
if (lnk_added && len > 4 && | |
STRCASECMP(wfd.cFileName + len - 4, ".lnk") == 0) { | |
wfd.cFileName[len -= 4] = '\0'; | |
} | |
#else | |
p = (char *)s; | |
#endif | |
++p; | |
BUFCHECK(bdiff + len >= buflen); | |
memcpy(p, wfd.cFileName, len + 1); | |
p += len; | |
} | |
#ifdef __CYGWIN__ | |
else { | |
p += strlen(p); | |
} | |
#endif | |
} | |
#endif | |
if (tainted) OBJ_TAINT(result); | |
rb_str_set_len(result, p - buf); | |
rb_enc_check(fname, result); | |
return result; | |
} | |
#define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, MAXPATHLEN + 2) | |
#define check_expand_path_args(fname, dname) \ | |
(((fname) = rb_get_path(fname)), \ | |
(void)(NIL_P(dname) ? (dname) : ((dname) = rb_get_path(dname)))) | |
static VALUE | |
file_expand_path_1(VALUE fname) | |
{ | |
return file_expand_path(fname, Qnil, 0, EXPAND_PATH_BUFFER()); | |
} | |
VALUE | |
rb_file_expand_path(VALUE fname, VALUE dname) | |
{ | |
check_expand_path_args(fname, dname); | |
return file_expand_path(fname, dname, 0, EXPAND_PATH_BUFFER()); | |
} | |
/* | |
* call-seq: | |
* File.expand_path(file_name [, dir_string] ) -> abs_file_name | |
* | |
* Converts a pathname to an absolute pathname. Relative paths are | |
* referenced from the current working directory of the process unless | |
* <i>dir_string</i> is given, in which case it will be used as the | |
* starting point. The given pathname may start with a | |
* ``<code>~</code>'', which expands to the process owner's home | |
* directory (the environment variable <code>HOME</code> must be set | |
* correctly). ``<code>~</code><i>user</i>'' expands to the named | |
* user's home directory. | |
* | |
* File.expand_path("~oracle/bin") #=> "/home/oracle/bin" | |
* File.expand_path("../../bin", "/tmp/x") #=> "/bin" | |
*/ | |
VALUE | |
rb_file_s_expand_path(int argc, VALUE *argv) | |
{ | |
VALUE fname, dname; | |
if (argc == 1) { | |
return rb_file_expand_path(argv[0], Qnil); | |
} | |
rb_scan_args(argc, argv, "11", &fname, &dname); | |
return rb_file_expand_path(fname, dname); | |
} | |
VALUE | |
rb_file_absolute_path(VALUE fname, VALUE dname) | |
{ | |
check_expand_path_args(fname, dname); | |
return file_expand_path(fname, dname, FEP_FILE_ABSOLUTE, EXPAND_PATH_BUFFER()); | |
} | |
/* | |
* call-seq: | |
* File.absolute_path(file_name [, dir_string] ) -> abs_file_name | |
* | |
* Converts a pathname to an absolute pathname. Relative paths are | |
* referenced from the current working directory of the process unless | |
* <i>dir_string</i> is given, in which case it will be used as the | |
* starting point. If the given pathname starts with a ``<code>~</code>'' | |
* it is NOT expanded, it is treated as a normal directory name. | |
* | |
* File.absolute_path("~oracle/bin") #=> "<relative_path>/~oracle/bin" | |
*/ | |
VALUE | |
rb_file_s_absolute_path(int argc, VALUE *argv) | |
{ | |
VALUE fname, dname; | |
if (argc == 1) { | |
return rb_file_absolute_path(argv[0], Qnil); | |
} | |
rb_scan_args(argc, argv, "11", &fname, &dname); | |
return rb_file_absolute_path(fname, dname); | |
} | |
static void | |
realpath_rec(long *prefixlenp, VALUE *resolvedp, char *unresolved, VALUE loopcheck, int strict, int last) | |
{ | |
ID resolving; | |
CONST_ID(resolving, "resolving"); | |
while (*unresolved) { | |
char *testname = unresolved; | |
char *unresolved_firstsep = rb_path_next(unresolved); | |
long testnamelen = unresolved_firstsep - unresolved; | |
char *unresolved_nextname = unresolved_firstsep; | |
while (isdirsep(*unresolved_nextname)) unresolved_nextname++; | |
unresolved = unresolved_nextname; | |
if (testnamelen == 1 && testname[0] == '.') { | |
} | |
else if (testnamelen == 2 && testname[0] == '.' && testname[1] == '.') { | |
if (*prefixlenp < RSTRING_LEN(*resolvedp)) { | |
char *resolved_names = RSTRING_PTR(*resolvedp) + *prefixlenp; | |
char *lastsep = rb_path_last_separator(resolved_names); | |
long len = lastsep ? lastsep - resolved_names : 0; | |
rb_str_resize(*resolvedp, *prefixlenp + len); | |
} | |
} | |
else { | |
VALUE checkval; | |
VALUE testpath = rb_str_dup(*resolvedp); | |
if (*prefixlenp < RSTRING_LEN(testpath)) | |
rb_str_cat2(testpath, "/"); | |
rb_str_cat(testpath, testname, testnamelen); | |
checkval = rb_hash_aref(loopcheck, testpath); | |
if (!NIL_P(checkval)) { | |
if (checkval == ID2SYM(resolving)) { | |
errno = ELOOP; | |
rb_sys_fail(RSTRING_PTR(testpath)); | |
} | |
else { | |
*resolvedp = rb_str_dup(checkval); | |
} | |
} | |
else { | |
struct stat sbuf; | |
int ret; | |
VALUE testpath2 = rb_str_encode_ospath(testpath); | |
ret = lstat(RSTRING_PTR(testpath2), &sbuf); | |
if (ret == -1) { | |
if (errno == ENOENT) { | |
if (strict || !last || *unresolved_firstsep) | |
rb_sys_fail(RSTRING_PTR(testpath)); | |
*resolvedp = testpath; | |
break; | |
} | |
else { | |
rb_sys_fail(RSTRING_PTR(testpath)); | |
} | |
} | |
#ifdef HAVE_READLINK | |
if (S_ISLNK(sbuf.st_mode)) { | |
volatile VALUE link; | |
char *link_prefix, *link_names; | |
long link_prefixlen; | |
rb_hash_aset(loopcheck, testpath, ID2SYM(resolving)); | |
link = rb_file_s_readlink(rb_cFile, testpath); | |
link_prefix = RSTRING_PTR(link); | |
link_names = skipprefixroot(link_prefix); | |
link_prefixlen = link_names - link_prefix; | |
if (link_prefixlen == 0) { | |
realpath_rec(prefixlenp, resolvedp, link_names, loopcheck, strict, *unresolved_firstsep == '\0'); | |
} | |
else { | |
*resolvedp = rb_str_new(link_prefix, link_prefixlen); | |
*prefixlenp = link_prefixlen; | |
realpath_rec(prefixlenp, resolvedp, link_names, loopcheck, strict, *unresolved_firstsep == '\0'); | |
} | |
rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp)); | |
} | |
else | |
#endif | |
{ | |
VALUE s = rb_str_dup_frozen(testpath); | |
rb_hash_aset(loopcheck, s, s); | |
*resolvedp = testpath; | |
} | |
} | |
} | |
} | |
} | |
VALUE | |
rb_realpath_internal(VALUE basedir, VALUE path, int strict) | |
{ | |
long prefixlen; | |
VALUE resolved; | |
volatile VALUE unresolved_path; | |
VALUE loopcheck; | |
volatile VALUE curdir = Qnil; | |
char *path_names = NULL, *basedir_names = NULL, *curdir_names = NULL; | |
char *ptr, *prefixptr = NULL; | |
rb_secure(2); | |
FilePathValue(path); | |
unresolved_path = rb_str_dup_frozen(path); | |
if (!NIL_P(basedir)) { | |
FilePathValue(basedir); | |
basedir = rb_str_dup_frozen(basedir); | |
} | |
ptr = RSTRING_PTR(unresolved_path); | |
path_names = skipprefixroot(ptr); | |
if (ptr != path_names) { | |
resolved = rb_enc_str_new(ptr, path_names - ptr, | |
rb_enc_get(unresolved_path)); | |
goto root_found; | |
} | |
if (!NIL_P(basedir)) { | |
ptr = RSTRING_PTR(basedir); | |
basedir_names = skipprefixroot(ptr); | |
if (ptr != basedir_names) { | |
resolved = rb_enc_str_new(ptr, basedir_names - ptr, | |
rb_enc_get(basedir)); | |
goto root_found; | |
} | |
} | |
curdir = rb_dir_getwd(); | |
ptr = RSTRING_PTR(curdir); | |
curdir_names = skipprefixroot(ptr); | |
resolved = rb_enc_str_new(ptr, curdir_names - ptr, rb_enc_get(curdir)); | |
root_found: | |
prefixptr = RSTRING_PTR(resolved); | |
prefixlen = RSTRING_LEN(resolved); | |
ptr = chompdirsep(prefixptr); | |
if (*ptr) { | |
prefixlen = ++ptr - prefixptr; | |
rb_str_set_len(resolved, prefixlen); | |
} | |
#ifdef FILE_ALT_SEPARATOR | |
while (prefixptr < ptr) { | |
if (*prefixptr == FILE_ALT_SEPARATOR) { | |
*prefixptr = '/'; | |
} | |
prefixptr = CharNext(prefixptr); | |
} | |
#endif | |
loopcheck = rb_hash_new(); | |
if (curdir_names) | |
realpath_rec(&prefixlen, &resolved, curdir_names, loopcheck, 1, 0); | |
if (basedir_names) | |
realpath_rec(&prefixlen, &resolved, basedir_names, loopcheck, 1, 0); | |
realpath_rec(&prefixlen, &resolved, path_names, loopcheck, strict, 1); | |
OBJ_TAINT(resolved); | |
return resolved; | |
} | |
/* | |
* call-seq: | |
* File.realpath(pathname [, dir_string]) -> real_pathname | |
* | |
* Returns the real (absolute) pathname of _pathname_ in the actual | |
* filesystem not containing symlinks or useless dots. | |
* | |
* If _dir_string_ is given, it is used as a base directory | |
* for interpreting relative pathname instead of the current directory. | |
* | |
* All components of the pathname must exist when this method is | |
* called. | |
*/ | |
static VALUE | |
rb_file_s_realpath(int argc, VALUE *argv, VALUE klass) | |
{ | |
VALUE path, basedir; | |
rb_scan_args(argc, argv, "11", &path, &basedir); | |
return rb_realpath_internal(basedir, path, 1); | |
} | |
/* | |
* call-seq: | |
* File.realdirpath(pathname [, dir_string]) -> real_pathname | |
* | |
* Returns the real (absolute) pathname of _pathname_ in the actual filesystem. | |
* The real pathname doesn't contain symlinks or useless dots. | |
* | |
* If _dir_string_ is given, it is used as a base directory | |
* for interpreting relative pathname instead of the current directory. | |
* | |
* The last component of the real pathname can be nonexistent. | |
*/ | |
static VALUE | |
rb_file_s_realdirpath(int argc, VALUE *argv, VALUE klass) | |
{ | |
VALUE path, basedir; | |
rb_scan_args(argc, argv, "11", &path, &basedir); | |
return rb_realpath_internal(basedir, path, 0); | |
} | |
static size_t | |
rmext(const char *p, long l0, long l1, const char *e) | |
{ | |
long l2; | |
if (!e) return 0; | |
l2 = strlen(e); | |
if (l2 == 2 && e[1] == '*') { | |
unsigned char c = *e; | |
e = p + l1; | |
do { | |
if (e <= p + l0) return 0; | |
} while (*--e != c); | |
return e - p; | |
} | |
if (l1 < l2) return l1; | |
#if CASEFOLD_FILESYSTEM | |
#define fncomp strncasecmp | |
#else | |
#define fncomp strncmp | |
#endif | |
if (fncomp(p+l1-l2, e, l2) == 0) { | |
return l1-l2; | |
} | |
return 0; | |
} | |
const char * | |
ruby_find_basename(const char *name, long *baselen, long *alllen) | |
{ | |
const char *p, *q, *e; | |
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC | |
const char *root; | |
#endif | |
long f = 0, n = -1; | |
name = skipprefix(name); | |
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC | |
root = name; | |
#endif | |
while (isdirsep(*name)) | |
name++; | |
if (!*name) { | |
p = name - 1; | |
f = 1; | |
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC | |
if (name != root) { | |
/* has slashes */ | |
} | |
#ifdef DOSISH_DRIVE_LETTER | |
else if (*p == ':') { | |
p++; | |
f = 0; | |
} | |
#endif | |
#ifdef DOSISH_UNC | |
else { | |
p = "/"; | |
} | |
#endif | |
#endif | |
} | |
else { | |
if (!(p = strrdirsep(name))) { | |
p = name; | |
} | |
else { | |
while (isdirsep(*p)) p++; /* skip last / */ | |
} | |
#if USE_NTFS | |
n = ntfs_tail(p) - p; | |
#else | |
n = chompdirsep(p) - p; | |
#endif | |
for (q = p; q - p < n && *q == '.'; q++); | |
for (e = 0; q - p < n; q = CharNext(q)) { | |
if (*q == '.') e = q; | |
} | |
if (e) f = e - p; | |
else f = n; | |
} | |
if (baselen) | |
*baselen = f; | |
if (alllen) | |
*alllen = n; | |
return p; | |
} | |
/* | |
* call-seq: | |
* File.basename(file_name [, suffix] ) -> base_name | |
* | |
* Returns the last component of the filename given in <i>file_name</i>, | |
* which must be formed using forward slashes (``<code>/</code>'') | |
* regardless of the separator used on the local file system. If | |
* <i>suffix</i> is given and present at the end of <i>file_name</i>, | |
* it is removed. | |
* | |
* File.basename("/home/gumby/work/ruby.rb") #=> "ruby.rb" | |
* File.basename("/home/gumby/work/ruby.rb", ".rb") #=> "ruby" | |
*/ | |
static VALUE | |
rb_file_s_basename(int argc, VALUE *argv) | |
{ | |
VALUE fname, fext, basename; | |
const char *name, *p; | |
long f, n; | |
if (rb_scan_args(argc, argv, "11", &fname, &fext) == 2) { | |
rb_encoding *enc; | |
StringValue(fext); | |
if (!rb_enc_asciicompat(enc = rb_enc_get(fext))) { | |
rb_raise(rb_eEncCompatError, "ascii incompatible character encodings: %s", | |
rb_enc_name(enc)); | |
} | |
} | |
FilePathStringValue(fname); | |
if (!NIL_P(fext)) rb_enc_check(fname, fext); | |
if (RSTRING_LEN(fname) == 0 || !*(name = RSTRING_PTR(fname))) | |
return rb_str_new_shared(fname); | |
p = ruby_find_basename(name, &f, &n); | |
if (n >= 0) { | |
if (NIL_P(fext) || !(f = rmext(p, f, n, StringValueCStr(fext)))) { | |
f = n; | |
} | |
if (f == RSTRING_LEN(fname)) return rb_str_new_shared(fname); | |
} | |
basename = rb_str_new(p, f); | |
rb_enc_copy(basename, fname); | |
OBJ_INFECT(basename, fname); | |
return basename; | |
} | |
/* | |
* call-seq: | |
* File.dirname(file_name ) -> dir_name | |
* | |
* Returns all components of the filename given in <i>file_name</i> | |
* except the last one. The filename must be formed using forward | |
* slashes (``<code>/</code>'') regardless of the separator used on the | |
* local file system. | |
* | |
* File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work" | |
*/ | |
static VALUE | |
rb_file_s_dirname(VALUE klass, VALUE fname) | |
{ | |
return rb_file_dirname(fname); | |
} | |
VALUE | |
rb_file_dirname(VALUE fname) | |
{ | |
const char *name, *root, *p; | |
VALUE dirname; | |
FilePathStringValue(fname); | |
name = StringValueCStr(fname); | |
root = skiproot(name); | |
#ifdef DOSISH_UNC | |
if (root > name + 1 && isdirsep(*name)) | |
root = skipprefix(name = root - 2); | |
#else | |
if (root > name + 1) | |
name = root - 1; | |
#endif | |
p = strrdirsep(root); | |
if (!p) { | |
p = root; | |
} | |
if (p == name) | |
return rb_usascii_str_new2("."); | |
#ifdef DOSISH_DRIVE_LETTER | |
if (has_drive_letter(name) && isdirsep(*(name + 2))) { | |
const char *top = skiproot(name + 2); | |
dirname = rb_str_new(name, 3); | |
rb_str_cat(dirname, top, p - top); | |
} | |
else | |
#endif | |
dirname = rb_str_new(name, p - name); | |
#ifdef DOSISH_DRIVE_LETTER | |
if (has_drive_letter(name) && root == name + 2 && p - name == 2) | |
rb_str_cat(dirname, ".", 1); | |
#endif | |
rb_enc_copy(dirname, fname); | |
OBJ_INFECT(dirname, fname); | |
return dirname; | |
} | |
/* | |
* accept a String, and return the pointer of the extension. | |
* if len is passed, set the length of extension to it. | |
* returned pointer is in ``name'' or NULL. | |
* returns *len | |
* no dot NULL 0 | |
* dotfile top 0 | |
* end with dot dot 1 | |
* .ext dot len of .ext | |
* .ext:stream dot len of .ext without :stream (NT only) | |
* | |
*/ | |
const char * | |
ruby_find_extname(const char *name, long *len) | |
{ | |
const char *p, *e; | |
p = strrdirsep(name); /* get the last path component */ | |
if (!p) | |
p = name; | |
else | |
do name = ++p; while (isdirsep(*p)); | |
e = 0; | |
while (*p && *p == '.') p++; | |
while (*p) { | |
if (*p == '.' || istrailinggarbage(*p)) { | |
#if USE_NTFS | |
const char *last = p++, *dot = last; | |
while (istrailinggarbage(*p)) { | |
if (*p == '.') dot = p; | |
p++; | |
} | |
if (!*p || *p == ':') { | |
p = last; | |
break; | |
} | |
if (*last == '.' || dot > last) e = dot; | |
continue; | |
#else | |
e = p; /* get the last dot of the last component */ | |
#endif | |
} | |
#if USE_NTFS | |
else if (*p == ':') { | |
break; | |
} | |
#endif | |
else if (isdirsep(*p)) | |
break; | |
p = CharNext(p); | |
} | |
if (len) { | |
/* no dot, or the only dot is first or end? */ | |
if (!e || e == name) | |
*len = 0; | |
else if (e+1 == p) | |
*len = 1; | |
else | |
*len = p - e; | |
} | |
return e; | |
} | |
/* | |
* call-seq: | |
* File.extname(path) -> string | |
* | |
* Returns the extension (the portion of file name in <i>path</i> | |
* after the period). | |
* | |
* File.extname("test.rb") #=> ".rb" | |
* File.extname("a/b/d/test.rb") #=> ".rb" | |
* File.extname("test") #=> "" | |
* File.extname(".profile") #=> "" | |
* | |
*/ | |
static VALUE | |
rb_file_s_extname(VALUE klass, VALUE fname) | |
{ | |
const char *name, *e; | |
long len; | |
VALUE extname; | |
FilePathStringValue(fname); | |
name = StringValueCStr(fname); | |
e = ruby_find_extname(name, &len); | |
if (len <= 1) | |
return rb_str_new(0, 0); | |
extname = rb_str_new(e, len); /* keep the dot, too! */ | |
rb_enc_copy(extname, fname); | |
OBJ_INFECT(extname, fname); | |
return extname; | |
} | |
/* | |
* call-seq: | |
* File.path(path) -> string | |
* | |
* Returns the string representation of the path | |
* | |
* File.path("/dev/null") #=> "/dev/null" | |
* File.path(Pathname.new("/tmp")) #=> "/tmp" | |
* | |
*/ | |
static VALUE | |
rb_file_s_path(VALUE klass, VALUE fname) | |
{ | |
return rb_get_path(fname); | |
} | |
/* | |
* call-seq: | |
* File.split(file_name) -> array | |
* | |
* Splits the given string into a directory and a file component and | |
* returns them in a two-element array. See also | |
* <code>File::dirname</code> and <code>File::basename</code>. | |
* | |
* File.split("/home/gumby/.profile") #=> ["/home/gumby", ".profile"] | |
*/ | |
static VALUE | |
rb_file_s_split(VALUE klass, VALUE path) | |
{ | |
FilePathStringValue(path); /* get rid of converting twice */ | |
return rb_assoc_new(rb_file_s_dirname(Qnil, path), rb_file_s_basename(1,&path)); | |
} | |
static VALUE separator; | |
static VALUE rb_file_join(VALUE ary, VALUE sep); | |
static VALUE | |
file_inspect_join(VALUE ary, VALUE argp, int recur) | |
{ | |
VALUE *arg = (VALUE *)argp; | |
if (recur || ary == arg[0]) rb_raise(rb_eArgError, "recursive array"); | |
return rb_file_join(arg[0], arg[1]); | |
} | |
static VALUE | |
rb_file_join(VALUE ary, VALUE sep) | |
{ | |
long len, i; | |
VALUE result, tmp; | |
const char *name, *tail; | |
if (RARRAY_LEN(ary) == 0) return rb_str_new(0, 0); | |
len = 1; | |
for (i=0; i<RARRAY_LEN(ary); i++) { | |
if (TYPE(RARRAY_PTR(ary)[i]) == T_STRING) { | |
len += RSTRING_LEN(RARRAY_PTR(ary)[i]); | |
} | |
else { | |
len += 10; | |
} | |
} | |
if (!NIL_P(sep)) { | |
StringValue(sep); | |
len += RSTRING_LEN(sep) * RARRAY_LEN(ary) - 1; | |
} | |
result = rb_str_buf_new(len); | |
OBJ_INFECT(result, ary); | |
for (i=0; i<RARRAY_LEN(ary); i++) { | |
tmp = RARRAY_PTR(ary)[i]; | |
switch (TYPE(tmp)) { | |
case T_STRING: | |
break; | |
case T_ARRAY: | |
if (ary == tmp) { | |
rb_raise(rb_eArgError, "recursive array"); | |
} | |
else { | |
VALUE args[2]; | |
args[0] = tmp; | |
args[1] = sep; | |
tmp = rb_exec_recursive(file_inspect_join, ary, (VALUE)args); | |
} | |
break; | |
default: | |
FilePathStringValue(tmp); | |
} | |
name = StringValueCStr(result); | |
if (i > 0 && !NIL_P(sep)) { | |
tail = chompdirsep(name); | |
if (RSTRING_PTR(tmp) && isdirsep(RSTRING_PTR(tmp)[0])) { | |
rb_str_set_len(result, tail - name); | |
} | |
else if (!*tail) { | |
rb_str_buf_append(result, sep); | |
} | |
} | |
rb_str_buf_append(result, tmp); | |
} | |
return result; | |
} | |
/* | |
* call-seq: | |
* File.join(string, ...) -> path | |
* | |
* Returns a new string formed by joining the strings using | |
* <code>File::SEPARATOR</code>. | |
* | |
* File.join("usr", "mail", "gumby") #=> "usr/mail/gumby" | |
* | |
*/ | |
static VALUE | |
rb_file_s_join(VALUE klass, VALUE args) | |
{ | |
return rb_file_join(args, separator); | |
} | |
#if defined(HAVE_TRUNCATE) || defined(HAVE_CHSIZE) | |
/* | |
* call-seq: | |
* File.truncate(file_name, integer) -> 0 | |
* | |
* Truncates the file <i>file_name</i> to be at most <i>integer</i> | |
* bytes long. Not available on all platforms. | |
* | |
* f = File.new("out", "w") | |
* f.write("1234567890") #=> 10 | |
* f.close #=> nil | |
* File.truncate("out", 5) #=> 0 | |
* File.size("out") #=> 5 | |
* | |
*/ | |
static VALUE | |
rb_file_s_truncate(VALUE klass, VALUE path, VALUE len) | |
{ | |
off_t pos; | |
rb_secure(2); | |
pos = NUM2OFFT(len); | |
FilePathValue(path); | |
path = rb_str_encode_ospath(path); | |
#ifdef HAVE_TRUNCATE | |
if (truncate(StringValueCStr(path), pos) < 0) | |
rb_sys_fail(RSTRING_PTR(path)); | |
#else /* defined(HAVE_CHSIZE) */ | |
{ | |
int tmpfd; | |
if ((tmpfd = open(StringValueCStr(path), 0)) < 0) { | |
rb_sys_fail(RSTRING_PTR(path)); | |
} | |
rb_update_max_fd(tmpfd); | |
if (chsize(tmpfd, pos) < 0) { | |
close(tmpfd); | |
rb_sys_fail(RSTRING_PTR(path)); | |
} | |
close(tmpfd); | |
} | |
#endif | |
return INT2FIX(0); | |
} | |
#else | |
#define rb_file_s_truncate rb_f_notimplement | |
#endif | |
#if defined(HAVE_FTRUNCATE) || defined(HAVE_CHSIZE) | |
/* | |
* call-seq: | |
* file.truncate(integer) -> 0 | |
* | |
* Truncates <i>file</i> to at most <i>integer</i> bytes. The file | |
* must be opened for writing. Not available on all platforms. | |
* | |
* f = File.new("out", "w") | |
* f.syswrite("1234567890") #=> 10 | |
* f.truncate(5) #=> 0 | |
* f.close() #=> nil | |
* File.size("out") #=> 5 | |
*/ | |
static VALUE | |
rb_file_truncate(VALUE obj, VALUE len) | |
{ | |
rb_io_t *fptr; | |
off_t pos; | |
rb_secure(2); | |
pos = NUM2OFFT(len); | |
GetOpenFile(obj, fptr); | |
if (!(fptr->mode & FMODE_WRITABLE)) { | |
rb_raise(rb_eIOError, "not opened for writing"); | |
} | |
rb_io_flush(obj); | |
#ifdef HAVE_FTRUNCATE | |
if (ftruncate(fptr->fd, pos) < 0) | |
rb_sys_fail_path(fptr->pathv); | |
#else /* defined(HAVE_CHSIZE) */ | |
if (chsize(fptr->fd, pos) < 0) | |
rb_sys_fail_path(fptr->pathv); | |
#endif | |
return INT2FIX(0); | |
} | |
#else | |
#define rb_file_truncate rb_f_notimplement | |
#endif | |
# ifndef LOCK_SH | |
# define LOCK_SH 1 | |
# endif | |
# ifndef LOCK_EX | |
# define LOCK_EX 2 | |
# endif | |
# ifndef LOCK_NB | |
# define LOCK_NB 4 | |
# endif | |
# ifndef LOCK_UN | |
# define LOCK_UN 8 | |
# endif | |
#ifdef __CYGWIN__ | |
#include <winerror.h> | |
extern unsigned long __attribute__((stdcall)) GetLastError(void); | |
#endif | |
static VALUE | |
rb_thread_flock(void *data) | |
{ | |
#ifdef __CYGWIN__ | |
int old_errno = errno; | |
#endif | |
int *op = data, ret = flock(op[0], op[1]); | |
#ifdef __CYGWIN__ | |
if (GetLastError() == ERROR_NOT_LOCKED) { | |
ret = 0; | |
errno = old_errno; | |
} | |
#endif | |
return (VALUE)ret; | |
} | |
/* | |
* call-seq: | |
* file.flock (locking_constant )-> 0 or false | |
* | |
* Locks or unlocks a file according to <i>locking_constant</i> (a | |
* logical <em>or</em> of the values in the table below). | |
* Returns <code>false</code> if <code>File::LOCK_NB</code> is | |
* specified and the operation would otherwise have blocked. Not | |
* available on all platforms. | |
* | |
* Locking constants (in class File): | |
* | |
* LOCK_EX | Exclusive lock. Only one process may hold an | |
* | exclusive lock for a given file at a time. | |
* ----------+------------------------------------------------ | |
* LOCK_NB | Don't block when locking. May be combined | |
* | with other lock options using logical or. | |
* ----------+------------------------------------------------ | |
* LOCK_SH | Shared lock. Multiple processes may each hold a | |
* | shared lock for a given file at the same time. | |
* ----------+------------------------------------------------ | |
* LOCK_UN | Unlock. | |
* | |
* Example: | |
* | |
* # update a counter using write lock | |
* # don't use "w" because it truncates the file before lock. | |
* File.open("counter", File::RDWR|File::CREAT, 0644) {|f| | |
* f.flock(File::LOCK_EX) | |
* value = f.read.to_i + 1 | |
* f.rewind | |
* f.write("#{value}\n") | |
* f.flush | |
* f.truncate(f.pos) | |
* } | |
* | |
* # read the counter using read lock | |
* File.open("counter", "r") {|f| | |
* f.flock(File::LOCK_SH) | |
* p f.read | |
* } | |
* | |
*/ | |
static VALUE | |
rb_file_flock(VALUE obj, VALUE operation) | |
{ | |
rb_io_t *fptr; | |
int op[2], op1; | |
rb_secure(2); | |
op[1] = op1 = NUM2INT(operation); | |
GetOpenFile(obj, fptr); | |
op[0] = fptr->fd; | |
if (fptr->mode & FMODE_WRITABLE) { | |
rb_io_flush(obj); | |
} | |
while ((int)rb_thread_io_blocking_region(rb_thread_flock, op, fptr->fd) < 0) { | |
switch (errno) { | |
case EAGAIN: | |
case EACCES: | |
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN | |
case EWOULDBLOCK: | |
#endif | |
if (op1 & LOCK_NB) return Qfalse; | |
rb_thread_polling(); | |
rb_io_check_closed(fptr); | |
continue; | |
case EINTR: | |
#if defined(ERESTART) | |
case ERESTART: | |
#endif | |
break; | |
default: | |
rb_sys_fail_path(fptr->pathv); | |
} | |
} | |
return INT2FIX(0); | |
} | |
#undef flock | |
static void | |
test_check(int n, int argc, VALUE *argv) | |
{ | |
int i; | |
rb_secure(2); | |
n+=1; | |
if (n != argc) rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, n); | |
for (i=1; i<n; i++) { | |
switch (TYPE(argv[i])) { | |
case T_STRING: | |
default: | |
FilePathValue(argv[i]); | |
break; | |
case T_FILE: | |
break; | |
} | |
} | |
} | |
#define CHECK(n) test_check((n), argc, argv) | |
/* | |
* call-seq: | |
* test(int_cmd, file1 [, file2] ) -> obj | |
* | |
* Uses the integer <i>aCmd</i> to perform various tests on | |
* <i>file1</i> (first table below) or on <i>file1</i> and | |
* <i>file2</i> (second table). | |
* | |
* File tests on a single file: | |
* | |
* Test Returns Meaning | |
* "A" | Time | Last access time for file1 | |
* "b" | boolean | True if file1 is a block device | |
* "c" | boolean | True if file1 is a character device | |
* "C" | Time | Last change time for file1 | |
* "d" | boolean | True if file1 exists and is a directory | |
* "e" | boolean | True if file1 exists | |
* "f" | boolean | True if file1 exists and is a regular file | |
* "g" | boolean | True if file1 has the \CF{setgid} bit | |
* | | set (false under NT) | |
* "G" | boolean | True if file1 exists and has a group | |
* | | ownership equal to the caller's group | |
* "k" | boolean | True if file1 exists and has the sticky bit set | |
* "l" | boolean | True if file1 exists and is a symbolic link | |
* "M" | Time | Last modification time for file1 | |
* "o" | boolean | True if file1 exists and is owned by | |
* | | the caller's effective uid | |
* "O" | boolean | True if file1 exists and is owned by | |
* | | the caller's real uid | |
* "p" | boolean | True if file1 exists and is a fifo | |
* "r" | boolean | True if file1 is readable by the effective | |
* | | uid/gid of the caller | |
* "R" | boolean | True if file is readable by the real | |
* | | uid/gid of the caller | |
* "s" | int/nil | If file1 has nonzero size, return the size, | |
* | | otherwise return nil | |
* "S" | boolean | True if file1 exists and is a socket | |
* "u" | boolean | True if file1 has the setuid bit set | |
* "w" | boolean | True if file1 exists and is writable by | |
* | | the effective uid/gid | |
* "W" | boolean | True if file1 exists and is writable by | |
* | | the real uid/gid | |
* "x" | boolean | True if file1 exists and is executable by | |
* | | the effective uid/gid | |
* "X" | boolean | True if file1 exists and is executable by | |
* | | the real uid/gid | |
* "z" | boolean | True if file1 exists and has a zero length | |
* | |
* Tests that take two files: | |
* | |
* "-" | boolean | True if file1 and file2 are identical | |
* "=" | boolean | True if the modification times of file1 | |
* | | and file2 are equal | |
* "<" | boolean | True if the modification time of file1 | |
* | | is prior to that of file2 | |
* ">" | boolean | True if the modification time of file1 | |
* | | is after that of file2 | |
*/ | |
static VALUE | |
rb_f_test(int argc, VALUE *argv) | |
{ | |
int cmd; | |
if (argc == 0) rb_raise(rb_eArgError, "wrong number of arguments (0 for 2..3)"); | |
cmd = NUM2CHR(argv[0]); | |
if (cmd == 0) goto unknown; | |
if (strchr("bcdefgGkloOprRsSuwWxXz", cmd)) { | |
CHECK(1); | |
switch (cmd) { | |
case 'b': | |
return rb_file_blockdev_p(0, argv[1]); | |
case 'c': | |
return rb_file_chardev_p(0, argv[1]); | |
case 'd': | |
return rb_file_directory_p(0, argv[1]); | |
case 'a': | |
case 'e': | |
return rb_file_exist_p(0, argv[1]); | |
case 'f': | |
return rb_file_file_p(0, argv[1]); | |
case 'g': | |
return rb_file_sgid_p(0, argv[1]); | |
case 'G': | |
return rb_file_grpowned_p(0, argv[1]); | |
case 'k': | |
return rb_file_sticky_p(0, argv[1]); | |
case 'l': | |
return rb_file_symlink_p(0, argv[1]); | |
case 'o': | |
return rb_file_owned_p(0, argv[1]); | |
case 'O': | |
return rb_file_rowned_p(0, argv[1]); | |
case 'p': | |
return rb_file_pipe_p(0, argv[1]); | |
case 'r': | |
return rb_file_readable_p(0, argv[1]); | |
case 'R': | |
return rb_file_readable_real_p(0, argv[1]); | |
case 's': | |
return rb_file_size_p(0, argv[1]); | |
case 'S': | |
return rb_file_socket_p(0, argv[1]); | |
case 'u': | |
return rb_file_suid_p(0, argv[1]); | |
case 'w': | |
return rb_file_writable_p(0, argv[1]); | |
case 'W': | |
return rb_file_writable_real_p(0, argv[1]); | |
case 'x': | |
return rb_file_executable_p(0, argv[1]); | |
case 'X': | |
return rb_file_executable_real_p(0, argv[1]); | |
case 'z': | |
return rb_file_zero_p(0, argv[1]); | |
} | |
} | |
if (strchr("MAC", cmd)) { | |
struct stat st; | |
VALUE fname = argv[1]; | |
CHECK(1); | |
if (rb_stat(fname, &st) == -1) { | |
FilePathValue(fname); | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
switch (cmd) { | |
case 'A': | |
return stat_atime(&st); | |
case 'M': | |
return stat_mtime(&st); | |
case 'C': | |
return stat_ctime(&st); | |
} | |
} | |
if (cmd == '-') { | |
CHECK(2); | |
return rb_file_identical_p(0, argv[1], argv[2]); | |
} | |
if (strchr("=<>", cmd)) { | |
struct stat st1, st2; | |
CHECK(2); | |
if (rb_stat(argv[1], &st1) < 0) return Qfalse; | |
if (rb_stat(argv[2], &st2) < 0) return Qfalse; | |
switch (cmd) { | |
case '=': | |
if (st1.st_mtime == st2.st_mtime) return Qtrue; | |
return Qfalse; | |
case '>': | |
if (st1.st_mtime > st2.st_mtime) return Qtrue; | |
return Qfalse; | |
case '<': | |
if (st1.st_mtime < st2.st_mtime) return Qtrue; | |
return Qfalse; | |
} | |
} | |
unknown: | |
/* unknown command */ | |
if (ISPRINT(cmd)) { | |
rb_raise(rb_eArgError, "unknown command '%s%c'", cmd == '\'' || cmd == '\\' ? "\\" : "", cmd); | |
} | |
else { | |
rb_raise(rb_eArgError, "unknown command \"\\x%02X\"", cmd); | |
} | |
return Qnil; /* not reached */ | |
} | |
/* | |
* Document-class: File::Stat | |
* | |
* Objects of class <code>File::Stat</code> encapsulate common status | |
* information for <code>File</code> objects. The information is | |
* recorded at the moment the <code>File::Stat</code> object is | |
* created; changes made to the file after that point will not be | |
* reflected. <code>File::Stat</code> objects are returned by | |
* <code>IO#stat</code>, <code>File::stat</code>, | |
* <code>File#lstat</code>, and <code>File::lstat</code>. Many of these | |
* methods return platform-specific values, and not all values are | |
* meaningful on all systems. See also <code>Kernel#test</code>. | |
*/ | |
static VALUE | |
rb_stat_s_alloc(VALUE klass) | |
{ | |
return stat_new_0(klass, 0); | |
} | |
/* | |
* call-seq: | |
* | |
* File::Stat.new(file_name) -> stat | |
* | |
* Create a File::Stat object for the given file name (raising an | |
* exception if the file doesn't exist). | |
*/ | |
static VALUE | |
rb_stat_init(VALUE obj, VALUE fname) | |
{ | |
struct stat st, *nst; | |
rb_secure(2); | |
FilePathValue(fname); | |
fname = rb_str_encode_ospath(fname); | |
if (STAT(StringValueCStr(fname), &st) == -1) { | |
rb_sys_fail(RSTRING_PTR(fname)); | |
} | |
if (DATA_PTR(obj)) { | |
xfree(DATA_PTR(obj)); | |
DATA_PTR(obj) = NULL; | |
} | |
nst = ALLOC(struct stat); | |
*nst = st; | |
DATA_PTR(obj) = nst; | |
return Qnil; | |
} | |
/* :nodoc: */ | |
static VALUE | |
rb_stat_init_copy(VALUE copy, VALUE orig) | |
{ | |
struct stat *nst; | |
if (copy == orig) return orig; | |
rb_check_frozen(copy); | |
/* need better argument type check */ | |
if (!rb_obj_is_instance_of(orig, rb_obj_class(copy))) { | |
rb_raise(rb_eTypeError, "wrong argument class"); | |
} | |
if (DATA_PTR(copy)) { | |
xfree(DATA_PTR(copy)); | |
DATA_PTR(copy) = 0; | |
} | |
if (DATA_PTR(orig)) { | |
nst = ALLOC(struct stat); | |
*nst = *(struct stat*)DATA_PTR(orig); | |
DATA_PTR(copy) = nst; | |
} | |
return copy; | |
} | |
/* | |
* call-seq: | |
* stat.ftype -> string | |
* | |
* Identifies the type of <i>stat</i>. The return string is one of: | |
* ``<code>file</code>'', ``<code>directory</code>'', | |
* ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'', | |
* ``<code>fifo</code>'', ``<code>link</code>'', | |
* ``<code>socket</code>'', or ``<code>unknown</code>''. | |
* | |
* File.stat("/dev/tty").ftype #=> "characterSpecial" | |
* | |
*/ | |
static VALUE | |
rb_stat_ftype(VALUE obj) | |
{ | |
return rb_file_ftype(get_stat(obj)); | |
} | |
/* | |
* call-seq: | |
* stat.directory? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is a directory, | |
* <code>false</code> otherwise. | |
* | |
* File.stat("testfile").directory? #=> false | |
* File.stat(".").directory? #=> true | |
*/ | |
static VALUE | |
rb_stat_d(VALUE obj) | |
{ | |
if (S_ISDIR(get_stat(obj)->st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.pipe? -> true or false | |
* | |
* Returns <code>true</code> if the operating system supports pipes and | |
* <i>stat</i> is a pipe; <code>false</code> otherwise. | |
*/ | |
static VALUE | |
rb_stat_p(VALUE obj) | |
{ | |
#ifdef S_IFIFO | |
if (S_ISFIFO(get_stat(obj)->st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.symlink? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is a symbolic link, | |
* <code>false</code> if it isn't or if the operating system doesn't | |
* support this feature. As <code>File::stat</code> automatically | |
* follows symbolic links, <code>symlink?</code> will always be | |
* <code>false</code> for an object returned by | |
* <code>File::stat</code>. | |
* | |
* File.symlink("testfile", "alink") #=> 0 | |
* File.stat("alink").symlink? #=> false | |
* File.lstat("alink").symlink? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_l(VALUE obj) | |
{ | |
#ifdef S_ISLNK | |
if (S_ISLNK(get_stat(obj)->st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.socket? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is a socket, | |
* <code>false</code> if it isn't or if the operating system doesn't | |
* support this feature. | |
* | |
* File.stat("testfile").socket? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_S(VALUE obj) | |
{ | |
#ifdef S_ISSOCK | |
if (S_ISSOCK(get_stat(obj)->st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.blockdev? -> true or false | |
* | |
* Returns <code>true</code> if the file is a block device, | |
* <code>false</code> if it isn't or if the operating system doesn't | |
* support this feature. | |
* | |
* File.stat("testfile").blockdev? #=> false | |
* File.stat("/dev/hda1").blockdev? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_b(VALUE obj) | |
{ | |
#ifdef S_ISBLK | |
if (S_ISBLK(get_stat(obj)->st_mode)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.chardev? -> true or false | |
* | |
* Returns <code>true</code> if the file is a character device, | |
* <code>false</code> if it isn't or if the operating system doesn't | |
* support this feature. | |
* | |
* File.stat("/dev/tty").chardev? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_c(VALUE obj) | |
{ | |
if (S_ISCHR(get_stat(obj)->st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.owned? -> true or false | |
* | |
* Returns <code>true</code> if the effective user id of the process is | |
* the same as the owner of <i>stat</i>. | |
* | |
* File.stat("testfile").owned? #=> true | |
* File.stat("/etc/passwd").owned? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_owned(VALUE obj) | |
{ | |
if (get_stat(obj)->st_uid == geteuid()) return Qtrue; | |
return Qfalse; | |
} | |
static VALUE | |
rb_stat_rowned(VALUE obj) | |
{ | |
if (get_stat(obj)->st_uid == getuid()) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.grpowned? -> true or false | |
* | |
* Returns true if the effective group id of the process is the same as | |
* the group id of <i>stat</i>. On Windows NT, returns <code>false</code>. | |
* | |
* File.stat("testfile").grpowned? #=> true | |
* File.stat("/etc/passwd").grpowned? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_grpowned(VALUE obj) | |
{ | |
#ifndef _WIN32 | |
if (rb_group_member(get_stat(obj)->st_gid)) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.readable? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is readable by the | |
* effective user id of this process. | |
* | |
* File.stat("testfile").readable? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_r(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (geteuid() == 0) return Qtrue; | |
#endif | |
#ifdef S_IRUSR | |
if (rb_stat_owned(obj)) | |
return st->st_mode & S_IRUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IRGRP | |
if (rb_stat_grpowned(obj)) | |
return st->st_mode & S_IRGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IROTH | |
if (!(st->st_mode & S_IROTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.readable_real? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is readable by the real | |
* user id of this process. | |
* | |
* File.stat("testfile").readable_real? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_R(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (getuid() == 0) return Qtrue; | |
#endif | |
#ifdef S_IRUSR | |
if (rb_stat_rowned(obj)) | |
return st->st_mode & S_IRUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IRGRP | |
if (rb_group_member(get_stat(obj)->st_gid)) | |
return st->st_mode & S_IRGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IROTH | |
if (!(st->st_mode & S_IROTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.world_readable? -> fixnum or nil | |
* | |
* If <i>stat</i> is readable by others, returns an integer | |
* representing the file permission bits of <i>stat</i>. Returns | |
* <code>nil</code> otherwise. The meaning of the bits is platform | |
* dependent; on Unix systems, see <code>stat(2)</code>. | |
* | |
* m = File.stat("/etc/passwd").world_readable? #=> 420 | |
* sprintf("%o", m) #=> "644" | |
*/ | |
static VALUE | |
rb_stat_wr(VALUE obj) | |
{ | |
#ifdef S_IROTH | |
if ((get_stat(obj)->st_mode & (S_IROTH)) == S_IROTH) { | |
return UINT2NUM(get_stat(obj)->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); | |
} | |
else { | |
return Qnil; | |
} | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.writable? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is writable by the | |
* effective user id of this process. | |
* | |
* File.stat("testfile").writable? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_w(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (geteuid() == 0) return Qtrue; | |
#endif | |
#ifdef S_IWUSR | |
if (rb_stat_owned(obj)) | |
return st->st_mode & S_IWUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IWGRP | |
if (rb_stat_grpowned(obj)) | |
return st->st_mode & S_IWGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IWOTH | |
if (!(st->st_mode & S_IWOTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.writable_real? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is writable by the real | |
* user id of this process. | |
* | |
* File.stat("testfile").writable_real? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_W(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (getuid() == 0) return Qtrue; | |
#endif | |
#ifdef S_IWUSR | |
if (rb_stat_rowned(obj)) | |
return st->st_mode & S_IWUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IWGRP | |
if (rb_group_member(get_stat(obj)->st_gid)) | |
return st->st_mode & S_IWGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IWOTH | |
if (!(st->st_mode & S_IWOTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.world_writable? -> fixnum or nil | |
* | |
* If <i>stat</i> is writable by others, returns an integer | |
* representing the file permission bits of <i>stat</i>. Returns | |
* <code>nil</code> otherwise. The meaning of the bits is platform | |
* dependent; on Unix systems, see <code>stat(2)</code>. | |
* | |
* m = File.stat("/tmp").world_writable? #=> 511 | |
* sprintf("%o", m) #=> "777" | |
*/ | |
static VALUE | |
rb_stat_ww(VALUE obj) | |
{ | |
#ifdef S_IROTH | |
if ((get_stat(obj)->st_mode & (S_IWOTH)) == S_IWOTH) { | |
return UINT2NUM(get_stat(obj)->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); | |
} | |
else { | |
return Qnil; | |
} | |
#endif | |
} | |
/* | |
* call-seq: | |
* stat.executable? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is executable or if the | |
* operating system doesn't distinguish executable files from | |
* nonexecutable files. The tests are made using the effective owner of | |
* the process. | |
* | |
* File.stat("testfile").executable? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_x(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (geteuid() == 0) { | |
return st->st_mode & S_IXUGO ? Qtrue : Qfalse; | |
} | |
#endif | |
#ifdef S_IXUSR | |
if (rb_stat_owned(obj)) | |
return st->st_mode & S_IXUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IXGRP | |
if (rb_stat_grpowned(obj)) | |
return st->st_mode & S_IXGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IXOTH | |
if (!(st->st_mode & S_IXOTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.executable_real? -> true or false | |
* | |
* Same as <code>executable?</code>, but tests using the real owner of | |
* the process. | |
*/ | |
static VALUE | |
rb_stat_X(VALUE obj) | |
{ | |
struct stat *st = get_stat(obj); | |
#ifdef USE_GETEUID | |
if (getuid() == 0) { | |
return st->st_mode & S_IXUGO ? Qtrue : Qfalse; | |
} | |
#endif | |
#ifdef S_IXUSR | |
if (rb_stat_rowned(obj)) | |
return st->st_mode & S_IXUSR ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IXGRP | |
if (rb_group_member(get_stat(obj)->st_gid)) | |
return st->st_mode & S_IXGRP ? Qtrue : Qfalse; | |
#endif | |
#ifdef S_IXOTH | |
if (!(st->st_mode & S_IXOTH)) return Qfalse; | |
#endif | |
return Qtrue; | |
} | |
/* | |
* call-seq: | |
* stat.file? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is a regular file (not | |
* a device file, pipe, socket, etc.). | |
* | |
* File.stat("testfile").file? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_f(VALUE obj) | |
{ | |
if (S_ISREG(get_stat(obj)->st_mode)) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.zero? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> is a zero-length file; | |
* <code>false</code> otherwise. | |
* | |
* File.stat("testfile").zero? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_z(VALUE obj) | |
{ | |
if (get_stat(obj)->st_size == 0) return Qtrue; | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* state.size -> integer | |
* | |
* Returns the size of <i>stat</i> in bytes. | |
* | |
* File.stat("testfile").size #=> 66 | |
* | |
*/ | |
static VALUE | |
rb_stat_s(VALUE obj) | |
{ | |
off_t size = get_stat(obj)->st_size; | |
if (size == 0) return Qnil; | |
return OFFT2NUM(size); | |
} | |
/* | |
* call-seq: | |
* stat.setuid? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> has the set-user-id | |
* permission bit set, <code>false</code> if it doesn't or if the | |
* operating system doesn't support this feature. | |
* | |
* File.stat("/bin/su").setuid? #=> true | |
*/ | |
static VALUE | |
rb_stat_suid(VALUE obj) | |
{ | |
#ifdef S_ISUID | |
if (get_stat(obj)->st_mode & S_ISUID) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.setgid? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> has the set-group-id | |
* permission bit set, <code>false</code> if it doesn't or if the | |
* operating system doesn't support this feature. | |
* | |
* File.stat("/usr/sbin/lpc").setgid? #=> true | |
* | |
*/ | |
static VALUE | |
rb_stat_sgid(VALUE obj) | |
{ | |
#ifdef S_ISGID | |
if (get_stat(obj)->st_mode & S_ISGID) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* stat.sticky? -> true or false | |
* | |
* Returns <code>true</code> if <i>stat</i> has its sticky bit set, | |
* <code>false</code> if it doesn't or if the operating system doesn't | |
* support this feature. | |
* | |
* File.stat("testfile").sticky? #=> false | |
* | |
*/ | |
static VALUE | |
rb_stat_sticky(VALUE obj) | |
{ | |
#ifdef S_ISVTX | |
if (get_stat(obj)->st_mode & S_ISVTX) return Qtrue; | |
#endif | |
return Qfalse; | |
} | |
VALUE rb_mFConst; | |
void | |
rb_file_const(const char *name, VALUE value) | |
{ | |
rb_define_const(rb_mFConst, name, value); | |
} | |
int | |
rb_is_absolute_path(const char *path) | |
{ | |
#ifdef DOSISH_DRIVE_LETTER | |
if (has_drive_letter(path) && isdirsep(path[2])) return 1; | |
#endif | |
#ifdef DOSISH_UNC | |
if (isdirsep(path[0]) && isdirsep(path[1])) return 1; | |
#endif | |
#ifndef DOSISH | |
if (path[0] == '/') return 1; | |
#endif | |
return 0; | |
} | |
#ifndef ENABLE_PATH_CHECK | |
# if defined DOSISH || defined __CYGWIN__ | |
# define ENABLE_PATH_CHECK 0 | |
# else | |
# define ENABLE_PATH_CHECK 1 | |
# endif | |
#endif | |
#if ENABLE_PATH_CHECK | |
static int | |
path_check_0(VALUE path, int execpath) | |
{ | |
struct stat st; | |
const char *p0 = StringValueCStr(path); | |
char *p = 0, *s; | |
if (!rb_is_absolute_path(p0)) { | |
char *buf = my_getcwd(); | |
VALUE newpath; | |
newpath = rb_str_new2(buf); | |
xfree(buf); | |
rb_str_cat2(newpath, "/"); | |
rb_str_cat2(newpath, p0); | |
path = newpath; | |
p0 = RSTRING_PTR(path); | |
} | |
for (;;) { | |
#ifndef S_IWOTH | |
# define S_IWOTH 002 | |
#endif | |
if (STAT(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) | |
#ifdef S_ISVTX | |
&& !(p && execpath && (st.st_mode & S_ISVTX)) | |
#endif | |
&& !access(p0, W_OK)) { | |
rb_warn("Insecure world writable dir %s in %sPATH, mode 0%" | |
PRI_MODET_PREFIX"o", | |
p0, (execpath ? "" : "LOAD_"), st.st_mode); | |
if (p) *p = '/'; | |
RB_GC_GUARD(path); | |
return 0; | |
} | |
s = strrdirsep(p0); | |
if (p) *p = '/'; | |
if (!s || s == p0) return 1; | |
p = s; | |
*p = '\0'; | |
} | |
} | |
#endif | |
#if ENABLE_PATH_CHECK | |
#define fpath_check(path) path_check_0((path), FALSE) | |
#else | |
#define fpath_check(path) 1 | |
#endif | |
int | |
rb_path_check(const char *path) | |
{ | |
#if ENABLE_PATH_CHECK | |
const char *p0, *p, *pend; | |
const char sep = PATH_SEP_CHAR; | |
if (!path) return 1; | |
pend = path + strlen(path); | |
p0 = path; | |
p = strchr(path, sep); | |
if (!p) p = pend; | |
for (;;) { | |
if (!path_check_0(rb_str_new(p0, p - p0), TRUE)) { | |
return 0; /* not safe */ | |
} | |
p0 = p + 1; | |
if (p0 > pend) break; | |
p = strchr(p0, sep); | |
if (!p) p = pend; | |
} | |
#endif | |
return 1; | |
} | |
static int | |
file_load_ok(const char *path) | |
{ | |
int ret = 1; | |
int fd; | |
#ifdef _WIN32 | |
static int use_attribute = -1; | |
if (use_attribute == -1) { | |
if (getenv("RUBY_USE_ATTRIBUTE") == NULL) { | |
use_attribute = 0; | |
} | |
else { | |
use_attribute = 1; | |
} | |
} | |
if (use_attribute) { | |
DWORD attr = GetFileAttributes(path); | |
if (attr == INVALID_FILE_ATTRIBUTES || | |
attr & FILE_ATTRIBUTE_DIRECTORY) { | |
ret = 0; | |
} | |
else { | |
HANDLE h = CreateFile(path, GENERIC_READ, | |
FILE_SHARE_READ | FILE_SHARE_WRITE, | |
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
if (h != INVALID_HANDLE_VALUE) { | |
CloseHandle(h); | |
} | |
else { | |
ret = 0; | |
} | |
} | |
return ret; | |
} | |
#endif | |
fd = open(path, O_RDONLY); | |
if (fd == -1) return 0; | |
rb_update_max_fd(fd); | |
#if !defined DOSISH | |
{ | |
struct stat st; | |
if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { | |
ret = 0; | |
} | |
} | |
#endif | |
(void)close(fd); | |
return ret; | |
} | |
int | |
rb_file_load_ok(const char *path) | |
{ | |
return file_load_ok(path); | |
} | |
static int | |
is_explicit_relative(const char *path) | |
{ | |
if (*path++ != '.') return 0; | |
if (*path == '.') path++; | |
return isdirsep(*path); | |
} | |
static VALUE | |
copy_path_class(VALUE path, VALUE orig) | |
{ | |
RBASIC(path)->klass = rb_obj_class(orig); | |
OBJ_FREEZE(path); | |
return path; | |
} | |
int | |
rb_find_file_ext(VALUE *filep, const char *const *ext) | |
{ | |
return rb_find_file_ext_safe(filep, ext, rb_safe_level()); | |
} | |
#define GET_LOAD_PATH() \ | |
if (cached_expanded_load_path) { \ | |
RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); \ | |
dirs_mode = FEP_DIR_EXPANDED; \ | |
} \ | |
else { \ | |
RB_GC_GUARD(load_path) = rb_get_load_path(); \ | |
dirs_mode = 0; \ | |
} | |
int | |
rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level) | |
{ | |
const char *f = StringValueCStr(*filep); | |
VALUE fname = *filep, load_path, tmp; | |
long i, j, fnlen; | |
int expanded = 0, dirs_mode; | |
if (!ext[0]) return 0; | |
if (f[0] == '~') { | |
fname = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, fname); | |
if (safe_level >= 1 && OBJ_TAINTED(fname)) { | |
rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); | |
} | |
f = RSTRING_PTR(fname); | |
*filep = fname; | |
expanded = 1; | |
} | |
if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) { | |
if (safe_level >= 1 && !fpath_check(fname)) { | |
rb_raise(rb_eSecurityError, "loading from unsafe path %s", f); | |
} | |
if (!expanded) fname = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, fname); | |
fnlen = RSTRING_LEN(fname); | |
for (i=0; ext[i]; i++) { | |
rb_str_cat2(fname, ext[i]); | |
if (file_load_ok(RSTRING_PTR(fname))) { | |
*filep = copy_path_class(fname, *filep); | |
return (int)(i+1); | |
} | |
rb_str_set_len(fname, fnlen); | |
} | |
return 0; | |
} | |
if (safe_level >= 4) { | |
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | |
} | |
GET_LOAD_PATH(); | |
if (!load_path) return 0; | |
fname = rb_str_dup(*filep); | |
fnlen = RSTRING_LEN(fname); | |
tmp = rb_str_tmp_new(MAXPATHLEN + 2); | |
for (j=0; ext[j]; j++) { | |
rb_str_cat2(fname, ext[j]); | |
for (i = 0; i < RARRAY_LEN(load_path); i++) { | |
VALUE str = RARRAY_PTR(load_path)[i]; | |
RB_GC_GUARD(str) = rb_get_path_check(str, safe_level); | |
if (RSTRING_LEN(str) == 0) continue; | |
tmp = rb_funcall(rb_cFile, rb_intern("expand_path"), 2, fname, str); | |
if (file_load_ok(RSTRING_PTR(tmp))) { | |
*filep = copy_path_class(tmp, *filep); | |
return (int)(j+1); | |
} | |
FL_UNSET(tmp, FL_TAINT | FL_UNTRUSTED); | |
} | |
rb_str_resize(fname, fnlen); | |
} | |
RB_GC_GUARD(load_path); | |
return 0; | |
} | |
VALUE | |
rb_find_file(VALUE path) | |
{ | |
return rb_find_file_safe(path, rb_safe_level()); | |
} | |
VALUE | |
rb_find_file_safe(VALUE path, int safe_level) | |
{ | |
VALUE tmp, load_path; | |
const char *f = StringValueCStr(path); | |
int expanded = 0, dirs_mode; | |
if (f[0] == '~') { | |
tmp = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, path); | |
if (safe_level >= 1 && OBJ_TAINTED(tmp)) { | |
rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); | |
} | |
path = copy_path_class(tmp, path); | |
f = RSTRING_PTR(path); | |
expanded = 1; | |
} | |
if (expanded || rb_is_absolute_path(f) || is_explicit_relative(f)) { | |
if (safe_level >= 1 && !fpath_check(path)) { | |
rb_raise(rb_eSecurityError, "loading from unsafe path %s", f); | |
} | |
if (!file_load_ok(f)) return 0; | |
if (!expanded) | |
path = copy_path_class(rb_funcall(rb_cFile, rb_intern("expand_path"), 1, path), path); | |
return path; | |
} | |
if (safe_level >= 4) { | |
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | |
} | |
GET_LOAD_PATH(); | |
if (load_path) { | |
long i; | |
tmp = rb_str_tmp_new(MAXPATHLEN + 2); | |
for (i = 0; i < RARRAY_LEN(load_path); i++) { | |
VALUE str = RARRAY_PTR(load_path)[i]; | |
RB_GC_GUARD(str) = rb_get_path_check(str, safe_level); | |
if (RSTRING_LEN(str) > 0) { | |
tmp = rb_funcall(rb_cFile, rb_intern("expand_path"), 2, path, str); | |
f = RSTRING_PTR(tmp); | |
if (file_load_ok(f)) goto found; | |
} | |
} | |
return 0; | |
} | |
else { | |
return 0; /* no path, no load */ | |
} | |
found: | |
if (safe_level >= 1 && !fpath_check(tmp)) { | |
rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); | |
} | |
return copy_path_class(tmp, path); | |
} | |
static void | |
define_filetest_function(const char *name, VALUE (*func)(ANYARGS), int argc) | |
{ | |
rb_define_module_function(rb_mFileTest, name, func, argc); | |
rb_define_singleton_method(rb_cFile, name, func, argc); | |
} | |
static const char null_device[] = | |
#if defined DOSISH | |
"NUL" | |
#elif defined AMIGA || defined __amigaos__ | |
"NIL" | |
#elif defined __VMS | |
"NL:" | |
#else | |
"/dev/null" | |
#endif | |
; | |
/* | |
* A <code>File</code> is an abstraction of any file object accessible | |
* by the program and is closely associated with class <code>IO</code> | |
* <code>File</code> includes the methods of module | |
* <code>FileTest</code> as class methods, allowing you to write (for | |
* example) <code>File.exist?("foo")</code>. | |
* | |
* In the description of File methods, | |
* <em>permission bits</em> are a platform-specific | |
* set of bits that indicate permissions of a file. On Unix-based | |
* systems, permissions are viewed as a set of three octets, for the | |
* owner, the group, and the rest of the world. For each of these | |
* entities, permissions may be set to read, write, or execute the | |
* file: | |
* | |
* The permission bits <code>0644</code> (in octal) would thus be | |
* interpreted as read/write for owner, and read-only for group and | |
* other. Higher-order bits may also be used to indicate the type of | |
* file (plain, directory, pipe, socket, and so on) and various other | |
* special features. If the permissions are for a directory, the | |
* meaning of the execute bit changes; when set the directory can be | |
* searched. | |
* | |
* On non-Posix operating systems, there may be only the ability to | |
* make a file read-only or read-write. In this case, the remaining | |
* permission bits will be synthesized to resemble typical values. For | |
* instance, on Windows NT the default permission bits are | |
* <code>0644</code>, which means read/write for owner, read-only for | |
* all others. The only change that can be made is to make the file | |
* read-only, which is reported as <code>0444</code>. | |
*/ | |
void | |
Init_File(void) | |
{ | |
rb_mFileTest = rb_define_module("FileTest"); | |
rb_cFile = rb_define_class("File", rb_cIO); | |
define_filetest_function("directory?", rb_file_directory_p, 1); | |
define_filetest_function("exist?", rb_file_exist_p, 1); | |
define_filetest_function("exists?", rb_file_exist_p, 1); | |
define_filetest_function("readable?", rb_file_readable_p, 1); | |
define_filetest_function("readable_real?", rb_file_readable_real_p, 1); | |
define_filetest_function("world_readable?", rb_file_world_readable_p, 1); | |
define_filetest_function("writable?", rb_file_writable_p, 1); | |
define_filetest_function("writable_real?", rb_file_writable_real_p, 1); | |
define_filetest_function("world_writable?", rb_file_world_writable_p, 1); | |
define_filetest_function("executable?", rb_file_executable_p, 1); | |
define_filetest_function("executable_real?", rb_file_executable_real_p, 1); | |
define_filetest_function("file?", rb_file_file_p, 1); | |
define_filetest_function("zero?", rb_file_zero_p, 1); | |
define_filetest_function("size?", rb_file_size_p, 1); | |
define_filetest_function("size", rb_file_s_size, 1); | |
define_filetest_function("owned?", rb_file_owned_p, 1); | |
define_filetest_function("grpowned?", rb_file_grpowned_p, 1); | |
define_filetest_function("pipe?", rb_file_pipe_p, 1); | |
define_filetest_function("symlink?", rb_file_symlink_p, 1); | |
define_filetest_function("socket?", rb_file_socket_p, 1); | |
define_filetest_function("blockdev?", rb_file_blockdev_p, 1); | |
define_filetest_function("chardev?", rb_file_chardev_p, 1); | |
define_filetest_function("setuid?", rb_file_suid_p, 1); | |
define_filetest_function("setgid?", rb_file_sgid_p, 1); | |
define_filetest_function("sticky?", rb_file_sticky_p, 1); | |
define_filetest_function("identical?", rb_file_identical_p, 2); | |
rb_define_singleton_method(rb_cFile, "stat", rb_file_s_stat, 1); | |
rb_define_singleton_method(rb_cFile, "lstat", rb_file_s_lstat, 1); | |
rb_define_singleton_method(rb_cFile, "ftype", rb_file_s_ftype, 1); | |
rb_define_singleton_method(rb_cFile, "atime", rb_file_s_atime, 1); | |
rb_define_singleton_method(rb_cFile, "mtime", rb_file_s_mtime, 1); | |
rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1); | |
rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1); | |
rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1); | |
rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1); | |
rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1); | |
rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1); | |
rb_define_singleton_method(rb_cFile, "link", rb_file_s_link, 2); | |
rb_define_singleton_method(rb_cFile, "symlink", rb_file_s_symlink, 2); | |
rb_define_singleton_method(rb_cFile, "readlink", rb_file_s_readlink, 1); | |
rb_define_singleton_method(rb_cFile, "unlink", rb_file_s_unlink, -2); | |
rb_define_singleton_method(rb_cFile, "delete", rb_file_s_unlink, -2); | |
rb_define_singleton_method(rb_cFile, "rename", rb_file_s_rename, 2); | |
rb_define_singleton_method(rb_cFile, "umask", rb_file_s_umask, -1); | |
rb_define_singleton_method(rb_cFile, "truncate", rb_file_s_truncate, 2); | |
rb_define_singleton_method(rb_cFile, "expand_path", rb_file_s_expand_path, -1); | |
rb_define_singleton_method(rb_cFile, "absolute_path", rb_file_s_absolute_path, -1); | |
rb_define_singleton_method(rb_cFile, "realpath", rb_file_s_realpath, -1); | |
rb_define_singleton_method(rb_cFile, "realdirpath", rb_file_s_realdirpath, -1); | |
rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1); | |
rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, 1); | |
rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1); | |
rb_define_singleton_method(rb_cFile, "path", rb_file_s_path, 1); | |
separator = rb_obj_freeze(rb_usascii_str_new2("/")); | |
rb_define_const(rb_cFile, "Separator", separator); | |
rb_define_const(rb_cFile, "SEPARATOR", separator); | |
rb_define_singleton_method(rb_cFile, "split", rb_file_s_split, 1); | |
rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -2); | |
#ifdef DOSISH | |
rb_define_const(rb_cFile, "ALT_SEPARATOR", rb_obj_freeze(rb_usascii_str_new2(file_alt_separator))); | |
#else | |
rb_define_const(rb_cFile, "ALT_SEPARATOR", Qnil); | |
#endif | |
rb_define_const(rb_cFile, "PATH_SEPARATOR", rb_obj_freeze(rb_str_new2(PATH_SEP))); | |
rb_define_method(rb_cIO, "stat", rb_io_stat, 0); /* this is IO's method */ | |
rb_define_method(rb_cFile, "lstat", rb_file_lstat, 0); | |
rb_define_method(rb_cFile, "atime", rb_file_atime, 0); | |
rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0); | |
rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0); | |
rb_define_method(rb_cFile, "size", rb_file_size, 0); | |
rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1); | |
rb_define_method(rb_cFile, "chown", rb_file_chown, 2); | |
rb_define_method(rb_cFile, "truncate", rb_file_truncate, 1); | |
rb_define_method(rb_cFile, "flock", rb_file_flock, 1); | |
rb_mFConst = rb_define_module_under(rb_cFile, "Constants"); | |
rb_include_module(rb_cIO, rb_mFConst); | |
rb_file_const("LOCK_SH", INT2FIX(LOCK_SH)); | |
rb_file_const("LOCK_EX", INT2FIX(LOCK_EX)); | |
rb_file_const("LOCK_UN", INT2FIX(LOCK_UN)); | |
rb_file_const("LOCK_NB", INT2FIX(LOCK_NB)); | |
rb_file_const("NULL", rb_obj_freeze(rb_usascii_str_new2(null_device))); | |
rb_define_method(rb_cFile, "path", rb_file_path, 0); | |
rb_define_method(rb_cFile, "to_path", rb_file_path, 0); | |
rb_define_global_function("test", rb_f_test, -1); | |
rb_cStat = rb_define_class_under(rb_cFile, "Stat", rb_cObject); | |
rb_define_alloc_func(rb_cStat, rb_stat_s_alloc); | |
rb_define_method(rb_cStat, "initialize", rb_stat_init, 1); | |
rb_define_method(rb_cStat, "initialize_copy", rb_stat_init_copy, 1); | |
rb_include_module(rb_cStat, rb_mComparable); | |
rb_define_method(rb_cStat, "<=>", rb_stat_cmp, 1); | |
rb_define_method(rb_cStat, "dev", rb_stat_dev, 0); | |
rb_define_method(rb_cStat, "dev_major", rb_stat_dev_major, 0); | |
rb_define_method(rb_cStat, "dev_minor", rb_stat_dev_minor, 0); | |
rb_define_method(rb_cStat, "ino", rb_stat_ino, 0); | |
rb_define_method(rb_cStat, "mode", rb_stat_mode, 0); | |
rb_define_method(rb_cStat, "nlink", rb_stat_nlink, 0); | |
rb_define_method(rb_cStat, "uid", rb_stat_uid, 0); | |
rb_define_method(rb_cStat, "gid", rb_stat_gid, 0); | |
rb_define_method(rb_cStat, "rdev", rb_stat_rdev, 0); | |
rb_define_method(rb_cStat, "rdev_major", rb_stat_rdev_major, 0); | |
rb_define_method(rb_cStat, "rdev_minor", rb_stat_rdev_minor, 0); | |
rb_define_method(rb_cStat, "size", rb_stat_size, 0); | |
rb_define_method(rb_cStat, "blksize", rb_stat_blksize, 0); | |
rb_define_method(rb_cStat, "blocks", rb_stat_blocks, 0); | |
rb_define_method(rb_cStat, "atime", rb_stat_atime, 0); | |
rb_define_method(rb_cStat, "mtime", rb_stat_mtime, 0); | |
rb_define_method(rb_cStat, "ctime", rb_stat_ctime, 0); | |
rb_define_method(rb_cStat, "inspect", rb_stat_inspect, 0); | |
rb_define_method(rb_cStat, "ftype", rb_stat_ftype, 0); | |
rb_define_method(rb_cStat, "directory?", rb_stat_d, 0); | |
rb_define_method(rb_cStat, "readable?", rb_stat_r, 0); | |
rb_define_method(rb_cStat, "readable_real?", rb_stat_R, 0); | |
rb_define_method(rb_cStat, "world_readable?", rb_stat_wr, 0); | |
rb_define_method(rb_cStat, "writable?", rb_stat_w, 0); | |
rb_define_method(rb_cStat, "writable_real?", rb_stat_W, 0); | |
rb_define_method(rb_cStat, "world_writable?", rb_stat_ww, 0); | |
rb_define_method(rb_cStat, "executable?", rb_stat_x, 0); | |
rb_define_method(rb_cStat, "executable_real?", rb_stat_X, 0); | |
rb_define_method(rb_cStat, "file?", rb_stat_f, 0); | |
rb_define_method(rb_cStat, "zero?", rb_stat_z, 0); | |
rb_define_method(rb_cStat, "size?", rb_stat_s, 0); | |
rb_define_method(rb_cStat, "owned?", rb_stat_owned, 0); | |
rb_define_method(rb_cStat, "grpowned?", rb_stat_grpowned, 0); | |
rb_define_method(rb_cStat, "pipe?", rb_stat_p, 0); | |
rb_define_method(rb_cStat, "symlink?", rb_stat_l, 0); | |
rb_define_method(rb_cStat, "socket?", rb_stat_S, 0); | |
rb_define_method(rb_cStat, "blockdev?", rb_stat_b, 0); | |
rb_define_method(rb_cStat, "chardev?", rb_stat_c, 0); | |
rb_define_method(rb_cStat, "setuid?", rb_stat_suid, 0); | |
rb_define_method(rb_cStat, "setgid?", rb_stat_sgid, 0); | |
rb_define_method(rb_cStat, "sticky?", rb_stat_sticky, 0); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* load methods from eval.c | |
*/ | |
#include "ruby/ruby.h" | |
#include "ruby/util.h" | |
#include "ruby/encoding.h" | |
#include "internal.h" | |
#include "dln.h" | |
#include "eval_intern.h" | |
VALUE ruby_dln_librefs; | |
#define IS_RBEXT(e) (strcmp((e), ".rb") == 0) | |
#define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0) | |
#ifdef DLEXT2 | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0 || strcmp((e), DLEXT2) == 0) | |
#else | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | |
#endif | |
static int sorted_loaded_features = 1; | |
static const char *const loadable_ext[] = { | |
".rb", DLEXT, | |
#ifdef DLEXT2 | |
DLEXT2, | |
#endif | |
0 | |
}; | |
VALUE | |
rb_get_load_path(void) | |
{ | |
VALUE load_path = GET_VM()->load_path; | |
return load_path; | |
} | |
static VALUE rb_checked_expanded_cache(int*); | |
static void rb_set_expanded_cache(VALUE, int); | |
static VALUE rb_expand_load_paths(int, VALUE*, int*); | |
int cached_expanded_load_path = 1; | |
VALUE | |
rb_get_expanded_load_path(void) | |
{ | |
VALUE expanded = rb_checked_expanded_cache(NULL); | |
if ( !RTEST(expanded) ) { | |
VALUE load_path = rb_get_load_path(); | |
int has_relative = 0; | |
if (!load_path) return 0; | |
expanded = rb_expand_load_paths( | |
RARRAY_LEN(load_path), RARRAY_PTR(load_path), | |
&has_relative); | |
RB_GC_GUARD(load_path); | |
if (cached_expanded_load_path) { | |
rb_set_expanded_cache(expanded, has_relative); | |
} | |
} else { | |
expanded = rb_ary_dup(expanded); | |
} | |
return expanded; | |
} | |
static VALUE | |
load_path_getter(ID id, rb_vm_t *vm) | |
{ | |
return vm->load_path; | |
} | |
static VALUE | |
get_loaded_features(void) | |
{ | |
return GET_VM()->loaded_features; | |
} | |
static st_table * | |
get_loading_table(void) | |
{ | |
return GET_VM()->loading_table; | |
} | |
static VALUE | |
loaded_feature_path(const char *name, long vlen, const char *feature, long len, | |
int type, VALUE load_path) | |
{ | |
long i; | |
long plen; | |
const char *e; | |
if(vlen < len) return 0; | |
if (!strncmp(name+(vlen-len),feature,len)){ | |
plen = vlen - len - 1; | |
} else { | |
for (e = name + vlen; name != e && *e != '.' && *e != '/'; --e); | |
if (*e!='.' || | |
e-name < len || | |
strncmp(e-len,feature,len) ) | |
return 0; | |
plen = e - name - len - 1; | |
} | |
for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
VALUE p = RARRAY_PTR(load_path)[i]; | |
const char *s = StringValuePtr(p); | |
long n = RSTRING_LEN(p); | |
if (n != plen ) continue; | |
if (n && (strncmp(name, s, n) || name[n] != '/')) continue; | |
switch (type) { | |
case 's': | |
if (IS_DLEXT(&name[n+len+1])) return p; | |
break; | |
case 'r': | |
if (IS_RBEXT(&name[n+len+1])) return p; | |
break; | |
default: | |
return p; | |
} | |
} | |
return 0; | |
} | |
struct loaded_feature_searching { | |
const char *name; | |
long len; | |
int type; | |
VALUE load_path; | |
const char *result; | |
}; | |
static int | |
loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f) | |
{ | |
const char *s = (const char *)v; | |
struct loaded_feature_searching *fp = (struct loaded_feature_searching *)f; | |
VALUE p = loaded_feature_path(s, strlen(s), fp->name, fp->len, | |
fp->type, fp->load_path); | |
if (!p) return ST_CONTINUE; | |
fp->result = s; | |
return ST_STOP; | |
} | |
static int rb_feature_first_equal_or_greater(VALUE, const char *, long); | |
static int rb_stop_search_feature(VALUE, const char *, long); | |
static int | |
rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn) | |
{ | |
VALUE v, features, p, load_path = 0; | |
const char *f, *e; | |
long i, len, elen, n; | |
st_table *loading_tbl; | |
st_data_t data; | |
int type; | |
if (fn) *fn = 0; | |
if (ext) { | |
elen = strlen(ext); | |
len = strlen(feature) - elen; | |
type = rb ? 'r' : 's'; | |
} | |
else { | |
len = strlen(feature); | |
elen = 0; | |
type = 0; | |
} | |
features = get_loaded_features(); | |
i = rb_feature_first_equal_or_greater(features, feature, len); | |
for (; i < RARRAY_LEN(features); ++i) { | |
v = RARRAY_PTR(features)[i]; | |
if (rb_stop_search_feature(v, feature, len)) break; | |
f = StringValuePtr(v); | |
if ((n = RSTRING_LEN(v)) < len) continue; | |
if (strncmp(f, feature, len) != 0) { | |
if (expanded) continue; | |
if (!load_path) load_path = rb_get_expanded_load_path(); | |
if (!(p = loaded_feature_path(f, n, feature, len, type, load_path))) | |
continue; | |
expanded = 1; | |
f += RSTRING_LEN(p) + 1; | |
} | |
if (!*(e = f + len)) { | |
if (ext) continue; | |
return 'u'; | |
} | |
if (*e != '.') continue; | |
if ((!rb || !ext) && (IS_SOEXT(e) || IS_DLEXT(e))) { | |
return 's'; | |
} | |
if ((rb || !ext) && (IS_RBEXT(e))) { | |
return 'r'; | |
} | |
} | |
loading_tbl = get_loading_table(); | |
if (loading_tbl && loading_tbl->num_entries > 0) { | |
f = 0; | |
if (!expanded) { | |
struct loaded_feature_searching fs; | |
fs.name = feature; | |
fs.len = len; | |
fs.type = type; | |
fs.load_path = load_path ? load_path : rb_get_expanded_load_path(); | |
fs.result = 0; | |
st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs); | |
if ((f = fs.result) != 0) { | |
if (fn) *fn = f; | |
goto loading; | |
} | |
} | |
if (st_get_key(loading_tbl, (st_data_t)feature, &data)) { | |
if (fn) *fn = (const char*)data; | |
loading: | |
if (!ext) return 'u'; | |
return !IS_RBEXT(ext) ? 's' : 'r'; | |
} | |
else { | |
VALUE bufstr; | |
char *buf; | |
if (ext && *ext) return 0; | |
bufstr = rb_str_tmp_new(len + DLEXT_MAXLEN); | |
buf = RSTRING_PTR(bufstr); | |
MEMCPY(buf, feature, char, len); | |
for (i = 0; (e = loadable_ext[i]) != 0; i++) { | |
strlcpy(buf + len, e, DLEXT_MAXLEN + 1); | |
if (st_get_key(loading_tbl, (st_data_t)buf, &data)) { | |
rb_str_resize(bufstr, 0); | |
if (fn) *fn = (const char*)data; | |
return i ? 's' : 'r'; | |
} | |
} | |
rb_str_resize(bufstr, 0); | |
} | |
} | |
return 0; | |
} | |
int | |
rb_provided(const char *feature) | |
{ | |
return rb_feature_provided(feature, 0); | |
} | |
int | |
rb_feature_provided(const char *feature, const char **loading) | |
{ | |
const char *ext = strrchr(feature, '.'); | |
volatile VALUE fullpath = 0; | |
if (*feature == '.' && | |
(feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) { | |
fullpath = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, rb_str_new2(feature)); | |
feature = RSTRING_PTR(fullpath); | |
} | |
if (ext && !strchr(ext, '/')) { | |
if (IS_RBEXT(ext)) { | |
if (rb_feature_p(feature, ext, TRUE, FALSE, loading)) return TRUE; | |
return FALSE; | |
} | |
else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { | |
if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE; | |
return FALSE; | |
} | |
} | |
if (rb_feature_p(feature, 0, TRUE, FALSE, loading)) | |
return TRUE; | |
return FALSE; | |
} | |
static int | |
feature_basename_length(const char *feature, long flen) | |
{ | |
if (sorted_loaded_features) { | |
const char *ext = strrchr(feature, '.'); | |
return ext && !strchr(ext, '/') ? ext - feature : flen; | |
} else { | |
return 0; | |
} | |
} | |
static int | |
compare_feature_name(const char *left, long llen, const char *right, long rlen) | |
{ | |
int diff = 0; | |
while (llen-- && rlen--) { | |
diff = left[llen] - right[rlen]; | |
if (diff) break; | |
if (left[llen] == '/') break; | |
} | |
return diff; | |
} | |
static int | |
rb_compare_feature_name(VALUE loaded, const char *feature, long flen) | |
{ | |
const char *loaded_name = StringValuePtr(loaded); | |
long loaded_len = feature_basename_length(loaded_name, RSTRING_LEN(loaded)); | |
return compare_feature_name(loaded_name, loaded_len, feature, flen); | |
} | |
/* used to find when equal features run out */ | |
static int | |
rb_stop_search_feature(VALUE loaded, const char *feature, long flen) | |
{ | |
if (sorted_loaded_features) | |
return rb_compare_feature_name(loaded, feature, flen) > 0; | |
else | |
return FALSE; | |
} | |
/* returns first position to search feature from */ | |
static int | |
rb_feature_first_equal_or_greater(VALUE features, const char *feature, long flen) | |
{ | |
if (sorted_loaded_features) { | |
int before = 0, first = RARRAY_LEN(features); | |
VALUE *values = RARRAY_PTR(features); | |
if (first == 0) | |
return 0; | |
if (rb_compare_feature_name(values[0], feature, flen) >= 0) | |
return 0; | |
while (first - before > 1) { | |
int mid = (first + before) / 2; | |
int cmp = rb_compare_feature_name(values[mid], feature, flen); | |
if (cmp >= 0) | |
first = mid; | |
else | |
before = mid; | |
} | |
return first; | |
} else { | |
return 0; | |
} | |
} | |
/* returns position to insert new feature in */ | |
static int | |
rb_feature_first_greater(VALUE features, const char *feature, long flen) | |
{ | |
if (sorted_loaded_features) { | |
int before = 0, first = RARRAY_LEN(features); | |
VALUE *values = RARRAY_PTR(features); | |
if (first == 0) | |
return 0; | |
if (rb_compare_feature_name(values[0], feature, flen) > 0) | |
return 0; | |
if (rb_compare_feature_name(values[first-1], feature, flen) <= 0) | |
return first; | |
while (first - before > 1) { | |
int mid = (first + before) / 2; | |
int cmp = rb_compare_feature_name(values[mid], feature, flen); | |
if (cmp > 0) | |
first = mid; | |
else | |
before = mid; | |
} | |
return first; | |
} else { | |
return RARRAY_LEN(features); | |
} | |
} | |
static VALUE | |
rb_push_feature_1(VALUE features, VALUE feature) | |
{ | |
const char *fname = StringValuePtr(feature); | |
long flen = feature_basename_length(fname, RSTRING_LEN(feature)); | |
int i = rb_feature_first_greater(features, fname, flen); | |
rb_ary_push(features, feature); | |
if ( i < RARRAY_LEN(features) - 1 ) { | |
MEMMOVE(RARRAY_PTR(features) + i + 1, RARRAY_PTR(features) + i, | |
VALUE, RARRAY_LEN(features) - i - 1); | |
RARRAY_PTR(features)[i] = feature; | |
} | |
return features; | |
} | |
static VALUE | |
rb_push_feature_m(int argc, VALUE *argv, VALUE features) | |
{ | |
while (argc--) { | |
rb_push_feature_1(features, *argv++); | |
} | |
return features; | |
} | |
static VALUE | |
rb_concat_features(VALUE features, VALUE add) | |
{ | |
add = rb_convert_type(add, T_ARRAY, "Array", "to_ary"); | |
if (RARRAY_LEN(add)) { | |
rb_push_feature_m(RARRAY_LEN(add), RARRAY_PTR(add), features); | |
} | |
return features; | |
} | |
static const char *load_features_undefined_methods[] = { | |
"[]=", "reverse!", "rotate!", "sort!", "sort_by!", | |
"collect!", "map!", "shuffle!", "fill", "insert", | |
NULL | |
}; | |
static VALUE | |
rb_loaded_features_init(void) | |
{ | |
char *sorted_flag; | |
const char **name; | |
VALUE loaded_features = rb_ary_new(); | |
VALUE loaded_features_c = rb_singleton_class(loaded_features); | |
sorted_flag = getenv("RUBY_LOADED_FEATURES_SORTED"); | |
if (sorted_flag != NULL) { | |
int sorted_set = atoi(sorted_flag); | |
if (RTEST(ruby_verbose)) | |
fprintf(stderr, "sorted_loaded_features=%d (%d)\n", sorted_set, sorted_loaded_features); | |
sorted_loaded_features = sorted_set; | |
} | |
for(name = load_features_undefined_methods; *name; name++) { | |
rb_undef_method(loaded_features_c, *name); | |
} | |
if (sorted_loaded_features) { | |
rb_define_method(loaded_features_c, "<<", rb_push_feature_1, 1); | |
rb_define_method(loaded_features_c, "push", rb_push_feature_m, -1); | |
rb_define_method(loaded_features_c, "concat", rb_concat_features, 1); | |
rb_define_method(loaded_features_c, "unshift", rb_push_feature_m, -1); | |
} | |
return loaded_features; | |
} | |
static void | |
rb_provide_feature(VALUE feature) | |
{ | |
if (OBJ_FROZEN(get_loaded_features())) { | |
rb_raise(rb_eRuntimeError, | |
"$LOADED_FEATURES is frozen; cannot append feature"); | |
} | |
if (sorted_loaded_features) | |
rb_push_feature_1(get_loaded_features(), feature); | |
else | |
rb_ary_push(get_loaded_features(), feature); | |
} | |
void | |
rb_provide(const char *feature) | |
{ | |
rb_provide_feature(rb_usascii_str_new2(feature)); | |
} | |
NORETURN(static void load_failed(VALUE)); | |
static void | |
rb_load_internal(VALUE fname, int wrap) | |
{ | |
int state; | |
rb_thread_t *th = GET_THREAD(); | |
volatile VALUE wrapper = th->top_wrapper; | |
volatile VALUE self = th->top_self; | |
volatile int loaded = FALSE; | |
volatile int mild_compile_error; | |
#ifndef __GNUC__ | |
rb_thread_t *volatile th0 = th; | |
#endif | |
th->errinfo = Qnil; /* ensure */ | |
if (!wrap) { | |
rb_secure(4); /* should alter global state */ | |
th->top_wrapper = 0; | |
} | |
else { | |
/* load in anonymous module as toplevel */ | |
th->top_self = rb_obj_clone(rb_vm_top_self()); | |
th->top_wrapper = rb_module_new(); | |
rb_extend_object(th->top_self, th->top_wrapper); | |
} | |
mild_compile_error = th->mild_compile_error; | |
PUSH_TAG(); | |
state = EXEC_TAG(); | |
if (state == 0) { | |
NODE *node; | |
VALUE iseq; | |
th->mild_compile_error++; | |
node = (NODE *)rb_load_file(RSTRING_PTR(fname)); | |
loaded = TRUE; | |
iseq = rb_iseq_new_top(node, rb_str_new2("<top (required)>"), fname, rb_funcall(rb_cFile, rb_intern("realpath"), 1, fname), Qfalse); | |
th->mild_compile_error--; | |
rb_iseq_eval(iseq); | |
} | |
POP_TAG(); | |
#ifndef __GNUC__ | |
th = th0; | |
fname = RB_GC_GUARD(fname); | |
#endif | |
th->mild_compile_error = mild_compile_error; | |
th->top_self = self; | |
th->top_wrapper = wrapper; | |
if (!loaded) { | |
rb_exc_raise(GET_THREAD()->errinfo); | |
} | |
if (state) { | |
rb_vm_jump_tag_but_local_jump(state, Qundef); | |
} | |
if (!NIL_P(GET_THREAD()->errinfo)) { | |
/* exception during load */ | |
rb_exc_raise(th->errinfo); | |
} | |
} | |
void | |
rb_load(VALUE fname, int wrap) | |
{ | |
VALUE tmp = rb_find_file(FilePathValue(fname)); | |
if (!tmp) load_failed(fname); | |
rb_load_internal(tmp, wrap); | |
} | |
void | |
rb_load_protect(VALUE fname, int wrap, int *state) | |
{ | |
int status; | |
PUSH_TAG(); | |
if ((status = EXEC_TAG()) == 0) { | |
rb_load(fname, wrap); | |
} | |
POP_TAG(); | |
if (state) | |
*state = status; | |
} | |
/* | |
* call-seq: | |
* load(filename, wrap=false) -> true | |
* | |
* Loads and executes the Ruby | |
* program in the file _filename_. If the filename does not | |
* resolve to an absolute path, the file is searched for in the library | |
* directories listed in <code>$:</code>. If the optional _wrap_ | |
* parameter is +true+, the loaded script will be executed | |
* under an anonymous module, protecting the calling program's global | |
* namespace. In no circumstance will any local variables in the loaded | |
* file be propagated to the loading environment. | |
*/ | |
static VALUE | |
rb_f_load(int argc, VALUE *argv) | |
{ | |
VALUE fname, wrap, path; | |
rb_scan_args(argc, argv, "11", &fname, &wrap); | |
path = rb_find_file(FilePathValue(fname)); | |
if (!path) { | |
if (!rb_file_load_ok(RSTRING_PTR(fname))) | |
load_failed(fname); | |
path = fname; | |
} | |
rb_load_internal(path, RTEST(wrap)); | |
return Qtrue; | |
} | |
static char * | |
load_lock(const char *ftptr) | |
{ | |
st_data_t data; | |
st_table *loading_tbl = get_loading_table(); | |
if (!loading_tbl || !st_lookup(loading_tbl, (st_data_t)ftptr, &data)) { | |
/* loading ruby library should be serialized. */ | |
if (!loading_tbl) { | |
GET_VM()->loading_table = loading_tbl = st_init_strtable(); | |
} | |
/* partial state */ | |
ftptr = ruby_strdup(ftptr); | |
data = (st_data_t)rb_barrier_new(); | |
st_insert(loading_tbl, (st_data_t)ftptr, data); | |
return (char *)ftptr; | |
} | |
if (RTEST(ruby_verbose)) { | |
rb_warning("loading in progress, circular require considered harmful - %s", ftptr); | |
rb_backtrace(); | |
} | |
return RTEST(rb_barrier_wait((VALUE)data)) ? (char *)ftptr : 0; | |
} | |
static void | |
load_unlock(const char *ftptr, int done) | |
{ | |
if (ftptr) { | |
st_data_t key = (st_data_t)ftptr; | |
st_data_t data; | |
st_table *loading_tbl = get_loading_table(); | |
if (st_delete(loading_tbl, &key, &data)) { | |
VALUE barrier = (VALUE)data; | |
xfree((char *)key); | |
if (done) | |
rb_barrier_destroy(barrier); | |
else | |
rb_barrier_release(barrier); | |
} | |
} | |
} | |
/* | |
* call-seq: | |
* require(name) -> true or false | |
* | |
* Loads the given +name+, returning +true+ if successful and +false+ if the | |
* feature is already loaded. | |
* | |
* If the filename does not resolve to an absolute path, it will be searched | |
* for in the directories listed in <code>$LOAD_PATH</code> (<code>$:</code>). | |
* | |
* If the filename has the extension ".rb", it is loaded as a source file; if | |
* the extension is ".so", ".o", or ".dll", or the default shared library | |
* extension on the current platform, Ruby loads the shared library as a | |
* Ruby extension. Otherwise, Ruby tries adding ".rb", ".so", and so on | |
* to the name until found. If the file named cannot be found, a LoadError | |
* will be raised. | |
* | |
* For Ruby extensions the filename given may use any shared library | |
* extension. For example, on Linux the socket extension is "socket.so" and | |
* <code>require 'socket.dll'</code> will load the socket extension. | |
* | |
* The absolute path of the loaded file is added to | |
* <code>$LOADED_FEATURES</code> (<code>$"</code>). A file will not be | |
* loaded again if its path already appears in <code>$"</code>. For example, | |
* <code>require 'a'; require './a'</code> will not load <code>a.rb</code> | |
* again. | |
* | |
* require "my-library.rb" | |
* require "db-driver" | |
*/ | |
VALUE | |
rb_f_require(VALUE obj, VALUE fname) | |
{ | |
return rb_require_safe(fname, rb_safe_level()); | |
} | |
/* | |
* call-seq: | |
* require_relative(string) -> true or false | |
* | |
* Ruby tries to load the library named _string_ relative to the requiring | |
* file's path. If the file's path cannot be determined a LoadError is raised. | |
* If a file is loaded +true+ is returned and false otherwise. | |
*/ | |
VALUE | |
rb_f_require_relative(VALUE obj, VALUE fname) | |
{ | |
VALUE base = rb_current_realfilepath(); | |
if (NIL_P(base)) { | |
rb_raise(rb_eLoadError, "cannot infer basepath"); | |
} | |
base = rb_file_dirname(base); | |
return rb_require_safe(rb_file_absolute_path(fname, base), rb_safe_level()); | |
} | |
static int | |
search_required(VALUE fname, volatile VALUE *path, int safe_level) | |
{ | |
VALUE tmp; | |
char *ext, *ftptr; | |
int type, ft = 0; | |
const char *loading; | |
*path = 0; | |
ext = strrchr(ftptr = RSTRING_PTR(fname), '.'); | |
if (ext && !strchr(ext, '/')) { | |
if (IS_RBEXT(ext)) { | |
if (rb_feature_p(ftptr, ext, TRUE, FALSE, &loading)) { | |
if (loading) *path = rb_str_new2(loading); | |
return 'r'; | |
} | |
if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) { | |
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); | |
if (!rb_feature_p(ftptr, ext, TRUE, TRUE, &loading) || loading) | |
*path = tmp; | |
return 'r'; | |
} | |
return 0; | |
} | |
else if (IS_SOEXT(ext)) { | |
if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) { | |
if (loading) *path = rb_str_new2(loading); | |
return 's'; | |
} | |
tmp = rb_str_new(RSTRING_PTR(fname), ext - RSTRING_PTR(fname)); | |
#ifdef DLEXT2 | |
OBJ_FREEZE(tmp); | |
if (rb_find_file_ext_safe(&tmp, loadable_ext + 1, safe_level)) { | |
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); | |
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading) | |
*path = tmp; | |
return 's'; | |
} | |
#else | |
rb_str_cat2(tmp, DLEXT); | |
OBJ_FREEZE(tmp); | |
if ((tmp = rb_find_file_safe(tmp, safe_level)) != 0) { | |
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); | |
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading) | |
*path = tmp; | |
return 's'; | |
} | |
#endif | |
} | |
else if (IS_DLEXT(ext)) { | |
if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) { | |
if (loading) *path = rb_str_new2(loading); | |
return 's'; | |
} | |
if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) { | |
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); | |
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading) | |
*path = tmp; | |
return 's'; | |
} | |
} | |
} | |
else if ((ft = rb_feature_p(ftptr, 0, FALSE, FALSE, &loading)) == 'r') { | |
if (loading) *path = rb_str_new2(loading); | |
return 'r'; | |
} | |
tmp = fname; | |
type = rb_find_file_ext_safe(&tmp, loadable_ext, safe_level); | |
switch (type) { | |
case 0: | |
if (ft) | |
break; | |
ftptr = RSTRING_PTR(tmp); | |
return rb_feature_p(ftptr, 0, FALSE, TRUE, 0); | |
default: | |
if (ft) | |
break; | |
case 1: | |
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); | |
if (rb_feature_p(ftptr, ext, !--type, TRUE, &loading) && !loading) | |
break; | |
*path = tmp; | |
} | |
return type ? 's' : 'r'; | |
} | |
static void | |
load_failed(VALUE fname) | |
{ | |
VALUE mesg = rb_str_buf_new_cstr("cannot load such file -- "); | |
rb_str_append(mesg, fname); /* should be ASCII compatible */ | |
rb_exc_raise(rb_exc_new3(rb_eLoadError, mesg)); | |
} | |
static VALUE | |
load_ext(VALUE path) | |
{ | |
SCOPE_SET(NOEX_PUBLIC); | |
return (VALUE)dln_load(RSTRING_PTR(path)); | |
} | |
VALUE | |
rb_require_safe(VALUE fname, int safe) | |
{ | |
volatile VALUE result = Qnil; | |
rb_thread_t *th = GET_THREAD(); | |
volatile VALUE errinfo = th->errinfo; | |
int state; | |
struct { | |
int safe; | |
} volatile saved; | |
char *volatile ftptr = 0; | |
PUSH_TAG(); | |
saved.safe = rb_safe_level(); | |
if ((state = EXEC_TAG()) == 0) { | |
VALUE path; | |
long handle; | |
int found; | |
rb_set_safe_level_force(safe); | |
FilePathValue(fname); | |
rb_set_safe_level_force(0); | |
found = search_required(fname, &path, safe); | |
if (found) { | |
if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) { | |
result = Qfalse; | |
} | |
else { | |
switch (found) { | |
case 'r': | |
rb_load_internal(path, 0); | |
break; | |
case 's': | |
handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext, | |
path, 0, path); | |
rb_ary_push(ruby_dln_librefs, LONG2NUM(handle)); | |
break; | |
} | |
rb_provide_feature(path); | |
result = Qtrue; | |
} | |
} | |
} | |
POP_TAG(); | |
load_unlock(ftptr, !state); | |
rb_set_safe_level_force(saved.safe); | |
if (state) { | |
JUMP_TAG(state); | |
} | |
if (NIL_P(result)) { | |
load_failed(fname); | |
} | |
th->errinfo = errinfo; | |
return result; | |
} | |
VALUE | |
rb_require(const char *fname) | |
{ | |
VALUE fn = rb_str_new2(fname); | |
OBJ_FREEZE(fn); | |
return rb_require_safe(fn, rb_safe_level()); | |
} | |
static VALUE | |
init_ext_call(VALUE arg) | |
{ | |
SCOPE_SET(NOEX_PUBLIC); | |
(*(void (*)(void))arg)(); | |
return Qnil; | |
} | |
RUBY_FUNC_EXPORTED void | |
ruby_init_ext(const char *name, void (*init)(void)) | |
{ | |
if (load_lock(name)) { | |
rb_vm_call_cfunc(rb_vm_top_self(), init_ext_call, (VALUE)init, | |
0, rb_str_new2(name)); | |
rb_provide(name); | |
load_unlock(name, 1); | |
} | |
} | |
/* | |
* call-seq: | |
* mod.autoload(module, filename) -> nil | |
* | |
* Registers _filename_ to be loaded (using <code>Kernel::require</code>) | |
* the first time that _module_ (which may be a <code>String</code> or | |
* a symbol) is accessed in the namespace of _mod_. | |
* | |
* module A | |
* end | |
* A.autoload(:B, "b") | |
* A::B.doit # autoloads "b" | |
*/ | |
static VALUE | |
rb_mod_autoload(VALUE mod, VALUE sym, VALUE file) | |
{ | |
ID id = rb_to_id(sym); | |
FilePathValue(file); | |
rb_autoload(mod, id, RSTRING_PTR(file)); | |
return Qnil; | |
} | |
/* | |
* call-seq: | |
* mod.autoload?(name) -> String or nil | |
* | |
* Returns _filename_ to be loaded if _name_ is registered as | |
* +autoload+ in the namespace of _mod_. | |
* | |
* module A | |
* end | |
* A.autoload(:B, "b") | |
* A.autoload?(:B) #=> "b" | |
*/ | |
static VALUE | |
rb_mod_autoload_p(VALUE mod, VALUE sym) | |
{ | |
return rb_autoload_p(mod, rb_to_id(sym)); | |
} | |
/* | |
* call-seq: | |
* autoload(module, filename) -> nil | |
* | |
* Registers _filename_ to be loaded (using <code>Kernel::require</code>) | |
* the first time that _module_ (which may be a <code>String</code> or | |
* a symbol) is accessed. | |
* | |
* autoload(:MyModule, "/usr/local/lib/modules/my_module.rb") | |
*/ | |
static VALUE | |
rb_f_autoload(VALUE obj, VALUE sym, VALUE file) | |
{ | |
VALUE klass = rb_class_real(rb_vm_cbase()); | |
if (NIL_P(klass)) { | |
rb_raise(rb_eTypeError, "Can not set autoload on singleton class"); | |
} | |
return rb_mod_autoload(klass, sym, file); | |
} | |
/* | |
* call-seq: | |
* autoload?(name) -> String or nil | |
* | |
* Returns _filename_ to be loaded if _name_ is registered as | |
* +autoload+. | |
* | |
* autoload(:B, "b") | |
* autoload?(:B) #=> "b" | |
*/ | |
static VALUE | |
rb_f_autoload_p(VALUE obj, VALUE sym) | |
{ | |
/* use rb_vm_cbase() as same as rb_f_autoload. */ | |
VALUE klass = rb_vm_cbase(); | |
if (NIL_P(klass)) { | |
return Qnil; | |
} | |
return rb_mod_autoload_p(klass, sym); | |
} | |
// $LOAD_PATH methods which invalidates cache | |
static const char *load_path_reset_cache_methods[] = { | |
"[]=", "collect!", "compact!", "delete", | |
"delete_if", "fill", "flatten!", "insert", "keep_if", | |
"map!", "reject!", "replace", "select!", "shuffle!", | |
"sort!", "sort_by!", "uniq!", NULL | |
}; | |
// $LOAD_PATH methods which sends also to cache | |
static const char *load_path_apply_to_cache_methods[] = { | |
"clear", "delete_at", "pop", "reverse!", "rotate!", | |
"shift", "slice!", NULL | |
}; | |
// $LOAD_PATH methods which sends to cache whith expanded arguments | |
static const char *load_path_apply_expanded_methods[] = { | |
"<<", "push", "unshift", NULL | |
}; | |
void | |
rb_reset_expanded_cache() | |
{ | |
GET_VM()->load_path_expanded_cache = 0; | |
} | |
static VALUE | |
rb_load_path_expanded_cache() | |
{ | |
VALUE cache = GET_VM()->load_path_expanded_cache; | |
VALUE expanded = Qnil; | |
if (RTEST(cache)) { | |
expanded = RARRAY_PTR(cache)[2]; | |
} | |
return expanded; | |
} | |
// Return cache only if we still in the same working directory | |
// and filesystem_encoding didn't change | |
// Invalidate cache otherwise | |
static VALUE | |
rb_checked_expanded_cache(int *has_relative) | |
{ | |
VALUE cache = GET_VM()->load_path_expanded_cache; | |
VALUE expanded = Qnil; | |
if (RTEST(cache)) { | |
VALUE curwd = RARRAY_PTR(cache)[0]; | |
VALUE encindex = RARRAY_PTR(cache)[1]; | |
int cache_valid = rb_filesystem_encindex() == FIX2INT(encindex); | |
if ( cache_valid ) { | |
cache_valid = curwd == Qtrue; | |
if (has_relative) { | |
*has_relative = cache_valid; | |
} | |
if (!cache_valid ) { | |
char *cwd = my_getcwd(); | |
cache_valid = !strcmp(RSTRING_PTR(curwd), cwd); | |
xfree(cwd); | |
} | |
} | |
if ( !cache_valid ) { | |
rb_reset_expanded_cache(); | |
} else { | |
expanded = RARRAY_PTR(cache)[2]; | |
} | |
} | |
RB_GC_GUARD(cache); | |
return expanded; | |
} | |
static void | |
rb_set_expanded_cache(VALUE expanded, int has_relative) | |
{ | |
VALUE cache = rb_ary_new2(2); | |
if (has_relative) { | |
char *cwd = my_getcwd(); | |
rb_ary_push(cache, rb_str_new_cstr(cwd)); | |
xfree(cwd); | |
} else { | |
rb_ary_push(cache, Qtrue); | |
} | |
rb_ary_push(cache, INT2FIX(rb_filesystem_encindex())); | |
rb_ary_push(cache, rb_ary_dup(expanded)); | |
GET_VM()->load_path_expanded_cache = cache; | |
} | |
static VALUE | |
rb_expand_load_paths(int pathc, VALUE* paths, int *has_relative) | |
{ | |
int i; | |
const char *p; | |
VALUE path, expanded = rb_ary_new2(pathc); | |
for(i = 0; i < pathc; i++) { | |
path = rb_get_path(paths[i]); | |
p = RSTRING_PTR(path); | |
*has_relative = *has_relative || !rb_is_absolute_path(p); | |
path = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, path); | |
rb_str_freeze(path); | |
rb_ary_push(expanded, path); | |
} | |
return expanded; | |
} | |
// Invalidating $LOAD_PATH methods implementation | |
static VALUE | |
rb_load_path_reset_cache_method(int argc, VALUE *argv, VALUE self) | |
{ | |
rb_reset_expanded_cache(); | |
return rb_call_super(argc, argv); | |
} | |
// Proxying $LOAD_PATH methods implementation | |
static VALUE | |
rb_load_path_apply_to_cache_method(int argc, VALUE *argv, VALUE self) | |
{ | |
VALUE load_path_expanded = rb_load_path_expanded_cache(); | |
if (RTEST(load_path_expanded)) { | |
ID func = rb_frame_this_func(); | |
rb_funcall2(load_path_expanded, func, argc, argv); | |
} | |
return rb_call_super(argc, argv); | |
} | |
// Proxying with expansion $LOAD_PATH methods implementation | |
static VALUE | |
rb_load_path_apply_expanded_method(int argc, VALUE *argv, VALUE self) | |
{ | |
int old_has_relative = 0; | |
// We call methods on cache only if we still in the same working directory | |
VALUE load_path_expanded = rb_checked_expanded_cache(&old_has_relative); | |
if (RTEST(load_path_expanded)) { | |
int has_relative = 0; | |
ID func = rb_frame_this_func(); | |
VALUE expanded = rb_expand_load_paths(argc, argv, &has_relative); | |
rb_funcall2(load_path_expanded, func, argc, RARRAY_PTR(expanded)); | |
if (!old_has_relative && has_relative) { | |
rb_set_expanded_cache(load_path_expanded, has_relative); | |
} | |
RB_GC_GUARD(expanded); | |
} | |
return rb_call_super(argc, argv); | |
} | |
// $LOAD_PATH.concat(ary) - special, we call push(*ary) instead | |
// cause I'm lazy a bit and wish not to rewrite method above second time :) | |
static VALUE | |
rb_load_path_concat(VALUE self, VALUE ary) | |
{ | |
ID push; | |
CONST_ID(push, "push"); | |
RB_GC_GUARD(ary); | |
return rb_funcall2(self, push, RARRAY_LEN(ary), RARRAY_PTR(ary)); | |
} | |
void | |
rb_load_path_ary_push(VALUE path) | |
{ | |
int old_has_relative = 0; | |
VALUE load_path_expanded = rb_checked_expanded_cache(&old_has_relative); | |
if (RTEST(load_path_expanded)) { | |
int has_relative = 0; | |
VALUE expanded = rb_expand_load_paths(1, &path, &has_relative); | |
rb_ary_push(load_path_expanded, RARRAY_PTR(expanded)[0]); | |
if (!old_has_relative && has_relative) { | |
rb_set_expanded_cache(load_path_expanded, has_relative); | |
} | |
RB_GC_GUARD(expanded); | |
} | |
rb_ary_push(rb_get_load_path(), path); | |
} | |
static VALUE | |
rb_load_path_init(void) | |
{ | |
const char **name; | |
VALUE load_path = rb_ary_new(); | |
char *cached_flag; | |
cached_flag = getenv("RUBY_CACHED_LOAD_PATH"); | |
if (cached_flag != NULL) { | |
cached_expanded_load_path = atoi(cached_flag); | |
} | |
// Do all the magick if user did not disable it | |
// with RUBY_CACHED_LOAD_PATH=0 environment variable | |
if (cached_expanded_load_path) { | |
VALUE load_path_c = rb_singleton_class(load_path); | |
for(name = load_path_reset_cache_methods; *name; name++ ) { | |
rb_define_method(load_path_c, *name, rb_load_path_reset_cache_method, -1); | |
} | |
for(name = load_path_apply_to_cache_methods; *name; name++ ) { | |
rb_define_method(load_path_c, *name, rb_load_path_apply_to_cache_method, -1); | |
} | |
for(name = load_path_apply_expanded_methods; *name; name++ ) { | |
rb_define_method(load_path_c, *name, rb_load_path_apply_expanded_method, -1); | |
} | |
rb_define_method(load_path_c, "concat", rb_load_path_concat, 1); | |
} | |
rb_reset_expanded_cache(); | |
return load_path; | |
} | |
void | |
Init_load() | |
{ | |
#undef rb_intern | |
#define rb_intern(str) rb_intern2((str), strlen(str)) | |
rb_vm_t *vm = GET_VM(); | |
static const char var_load_path[] = "$:"; | |
ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1); | |
rb_define_hooked_variable(var_load_path, (VALUE*)vm, load_path_getter, rb_gvar_readonly_setter); | |
rb_alias_variable(rb_intern("$-I"), id_load_path); | |
rb_alias_variable(rb_intern("$LOAD_PATH"), id_load_path); | |
vm->load_path = rb_load_path_init(); | |
rb_define_virtual_variable("$\"", get_loaded_features, 0); | |
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0); | |
vm->loaded_features = rb_loaded_features_init(); | |
rb_define_global_function("load", rb_f_load, -1); | |
rb_define_global_function("require", rb_f_require, 1); | |
rb_define_global_function("require_relative", rb_f_require_relative, 1); | |
rb_define_method(rb_cModule, "autoload", rb_mod_autoload, 2); | |
rb_define_method(rb_cModule, "autoload?", rb_mod_autoload_p, 1); | |
rb_define_global_function("autoload", rb_f_autoload, 2); | |
rb_define_global_function("autoload?", rb_f_autoload_p, 1); | |
ruby_dln_librefs = rb_ary_new(); | |
rb_gc_register_mark_object(ruby_dln_librefs); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment