/**********************************************************************
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/signal.h"
#include "ruby/util.h"
#include "dln.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_LSTAT && !defined lstat
#define lstat stat
#endif
#ifdef __BEOS__ /* should not change ID if -1 */
static int
be_chown(const char *path, uid_t owner, gid_t group)
{
if (owner == -1 || group == -1) {
struct stat st;
if (stat(path, &st) < 0) return -1;
if (owner == -1) owner = st.st_uid;
if (group == -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 == -1 || group == -1) {
struct stat st;
if (fstat(fd, &st) < 0) return -1;
if (owner == -1) owner = st.st_uid;
if (group == -1) group = st.st_gid;
}
return fchown(fd, owner, group);
}
#define fchown be_fchown
#endif /* __BEOS__ */
VALUE rb_cFile;
VALUE rb_mFileTest;
VALUE rb_cStat;
static VALUE
rb_get_path_check(VALUE obj, int check)
{
VALUE tmp;
ID to_path;
if (check) rb_check_safe_obj(obj);
tmp = rb_check_string_type(obj);
if (!NIL_P(tmp)) goto exit;
CONST_ID(to_path, "to_path");
if (rb_respond_to(obj, to_path)) {
tmp = rb_funcall(obj, to_path, 0, 0);
}
else {
tmp = obj;
}
exit:
StringValueCStr(tmp);
if (check && obj != tmp) {
rb_check_safe_obj(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, 1);
}
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]);
(*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 (!fptr->path) return Qnil;
return rb_tainted_str_new2(fptr->path);
}
static VALUE
stat_new_0(VALUE klass, struct stat *st)
{
struct stat *nst = 0;
if (st) {
nst = ALLOC(struct stat);
*nst = *st;
}
return Data_Wrap_Struct(klass, NULL, -1, 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;
Data_Get_Struct(self, struct stat, 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
*
* 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;
}
/*
* 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 INT2NUM(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)
long dev = get_stat(self)->st_dev;
return ULONG2NUM(major(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)
long dev = get_stat(self)->st_dev;
return ULONG2NUM(minor(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)
{
#ifdef HUGE_ST_INO
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)
{
#ifdef __BORLANDC__
return UINT2NUM((unsigned short)(get_stat(self)->st_mode));
#else
return UINT2NUM(get_stat(self)->st_mode);
#endif
}
/*
* 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 ULONG2NUM(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)
long rdev = get_stat(self)->st_rdev;
return ULONG2NUM(major(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)
long rdev = get_stat(self)->st_rdev;
return ULONG2NUM(minor(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_ST_BLOCKS
return ULONG2NUM(get_stat(self)->st_blocks);
#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).
*
* 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;
int 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},
};
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", NUM2ULONG(v));
}
else if (i == 0 || i == 6) { /* dev/rdev */
rb_str_catf(str, "0x%lx", NUM2ULONG(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);
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 {
FilePathValue(*file);
f = CreateFile(StringValueCStr(*file), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
rb_w32_iswin95() ? 0 : FILE_FLAG_BACKUP_SEMANTICS, NULL);
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(StringValueCStr(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;
GetOpenFile(obj, fptr);
if (fstat(fptr->fd, &st) == -1) {
rb_sys_fail(fptr->path);
}
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
*
*/
|