/* ***** BEGIN LICENSE BLOCK ***** * Version: EPL 2.0/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Eclipse Public * License Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.eclipse.org/legal/epl-v20.html * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * Copyright (C) 2002-2004 Anders Bengtsson * Copyright (C) 2002-2004 Jan Arne Petersen * Copyright (C) 2004 Thomas E Enebo * Copyright (C) 2004 Joey Gibson * Copyright (C) 2004 Charles O Nutter * Copyright (C) 2004 Stefan Matthias Aust * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the EPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the EPL, the GPL or the LGPL. ***** END LICENSE BLOCK *****/ package org.jruby; import java.io.FileDescriptor; import java.nio.file.attribute.FileTime; import jnr.posix.NanosecondFileStat; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import jnr.posix.FileStat; import jnr.posix.util.Platform; import org.jruby.runtime.Block; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.FileResource; import org.jruby.util.JRubyFile; import static org.jruby.RubyTime.newTimeFromNanoseconds; import static org.jruby.api.Convert.asBoolean; import static org.jruby.api.Convert.asFixnum; import static org.jruby.api.Create.newString; import static org.jruby.api.Error.typeError; /** * Implements File::Stat */ @JRubyClass(name="File::Stat", include="Comparable") public class RubyFileStat extends RubyObject { private static final long serialVersionUID = 1L; private static final int S_IRUGO = (FileStat.S_IRUSR | FileStat.S_IRGRP | FileStat.S_IROTH); private static final int S_IWUGO = (FileStat.S_IWUSR | FileStat.S_IWGRP | FileStat.S_IWOTH); private static final int S_IXUGO = (FileStat.S_IXUSR | FileStat.S_IXGRP | FileStat.S_IXOTH); public static final int BILLION = 1000000000; private FileResource file; private FileStat stat; private void checkInitialized(ThreadContext context) { if (stat == null) throw typeError(context, "uninitialized File::Stat"); } public static RubyClass createFileStatClass(ThreadContext context, RubyClass Object, RubyClass File, RubyModule Comparable) { return File.defineClassUnder(context, "Stat", Object, RubyFileStat::new). include(context, Comparable). defineMethods(context, RubyFileStat.class); } protected RubyFileStat(Ruby runtime, RubyClass clazz) { super(runtime, clazz); } public static RubyFileStat newFileStat(Ruby runtime, String filename, boolean lstat) { RubyFileStat stat = new RubyFileStat(runtime, runtime.getFileStat()); stat.setup(filename, lstat); return stat; } public static RubyFileStat newFileStat(Ruby runtime, FileDescriptor descriptor) { RubyFileStat stat = new RubyFileStat(runtime, runtime.getFileStat()); stat.setup(descriptor); return stat; } public static RubyFileStat newFileStat(Ruby runtime, int fileno) { RubyFileStat stat = new RubyFileStat(runtime, runtime.getFileStat()); stat.setup(fileno); return stat; } private void setup(FileDescriptor descriptor) { stat = getRuntime().getPosix().fstat(descriptor); } private void setup(int fileno) { stat = getRuntime().getPosix().fstat(fileno); } private void setup(String filename, boolean lstat) { Ruby runtime = getRuntime(); if (Platform.IS_WINDOWS && filename.length() == 2 && filename.charAt(1) == ':' && Character.isLetter(filename.charAt(0))) { filename += '/'; } file = JRubyFile.createResource(runtime, filename); stat = lstat ? file.lstat() : file.stat(); if (stat == null) { if (Platform.IS_WINDOWS) { switch (file.errno()) { case 2: // ERROR_FILE_NOT_FOUND case 3: // ERROR_PATH_NOT_FOUND case 53: // ERROR_BAD_NETPATH case 123: // ERROR_INVALID_NAME throw runtime.newErrnoENOENTError(filename); } } throw runtime.newErrnoFromInt(file.errno(), filename); } } @Deprecated(since = "10.0.0.0") public IRubyObject initialize19(IRubyObject fname, Block unusedBlock) { return initialize(fname, unusedBlock); } @Deprecated(since = "10.0.0.0") public IRubyObject initialize(IRubyObject fname, Block unusedBlock) { return initialize(getCurrentContext(), fname, unusedBlock); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject fname, Block unusedBlock) { setup(RubyFile.get_path(context, fname).toString(), false); return this; } @JRubyMethod(name = "atime") public IRubyObject atime(ThreadContext context) { checkInitialized(context); return stat instanceof NanosecondFileStat nanoStat ? newTimeFromNanoseconds(context, stat.atime() * BILLION + nanoStat.aTimeNanoSecs()) : context.runtime.newTime(stat.atime() * 1000); } @Deprecated(since = "10.0.0.0") public IRubyObject atime() { return atime(getCurrentContext()); } @JRubyMethod(name = "blksize") public IRubyObject blockSize(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.blockSize()); } @JRubyMethod(name = "blockdev?") public IRubyObject blockdev_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isBlockDev()); } @Deprecated(since = "10.0.0.0") public IRubyObject blockdev_p() { return blockdev_p(getCurrentContext()); } @JRubyMethod(name = "blocks") public IRubyObject blocks(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.blocks()); } @Deprecated(since = "10.0.0.0") public IRubyObject blocks() { return blocks(getCurrentContext()); } @JRubyMethod(name = "chardev?") public IRubyObject chardev_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isCharDev()); } @Deprecated(since = "10.0.0.0") public IRubyObject chardev_p() { return chardev_p(getCurrentContext()); } @JRubyMethod(name = "<=>") public IRubyObject cmp(ThreadContext context, IRubyObject other) { checkInitialized(context); if (!(other instanceof RubyFileStat)) return context.nil; long time1 = stat.mtime(); long time2 = ((RubyFileStat) other).stat.mtime(); if (time1 == time2) { return asFixnum(context, 0); } else if (time1 < time2) { return asFixnum(context, -1); } else { return asFixnum(context, 1); } } @Deprecated(since = "10.0.0.0") public IRubyObject cmp(IRubyObject other) { return cmp(getCurrentContext(), other); } @JRubyMethod(name = "ctime") public IRubyObject ctime(ThreadContext context) { checkInitialized(context); if (stat instanceof NanosecondFileStat nfs) { return newTimeFromNanoseconds(context, stat.ctime() * BILLION + nfs.cTimeNanoSecs()); } return context.runtime.newTime(stat.ctime() * 1000); } @Deprecated(since = "10.0.0.0") public IRubyObject ctime() { return ctime(getCurrentContext()); } @JRubyMethod(name = "birthtime") public IRubyObject birthtime(ThreadContext context) { checkInitialized(context); FileTime btime; if (Platform.IS_LINUX) { throw context.runtime.newNotImplementedError("birthtime() function is unimplemented on this machine"); } if (file == null || (btime = RubyFile.getBirthtimeWithNIO(file.absolutePath())) == null) { return ctime(); } return context.runtime.newTime(btime.toMillis()); } @Deprecated(since = "10.0.0.0") public IRubyObject birthtime() { return birthtime(getCurrentContext()); } @JRubyMethod(name = "dev") public IRubyObject dev(ThreadContext context) { checkInitialized(context); return asFixnum(context, stat.dev()); } @Deprecated(since = "10.0.0.0") public IRubyObject dev() { return dev(getCurrentContext()); } @JRubyMethod(name = "dev_major") public IRubyObject devMajor(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.major(stat.dev())); } @Deprecated(since = "10.0.0.0") public IRubyObject devMajor() { return devMajor(getCurrentContext()); } @JRubyMethod(name = "dev_minor") public IRubyObject devMinor(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.minor(stat.dev())); } @Deprecated(since = "10.0.0.0") public IRubyObject devMinor() { return devMinor(getCurrentContext()); } @JRubyMethod(name = "directory?") public RubyBoolean directory_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isDirectory()); } @Deprecated(since = "10.0.0.0") public RubyBoolean directory_p() { return directory_p(getCurrentContext()); } @JRubyMethod(name = "executable?") public IRubyObject executable_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isExecutable()); } @Deprecated(since = "10.0.0.0") public IRubyObject executable_p() { return executable_p(getCurrentContext()); } @JRubyMethod(name = "executable_real?") public IRubyObject executableReal_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isExecutableReal()); } @Deprecated(since = "10.0.0.0") public IRubyObject executableReal_p() { return executableReal_p(getCurrentContext()); } @JRubyMethod(name = "file?") public RubyBoolean file_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isFile()); } @Deprecated(since = "10.0.0.0") public RubyBoolean file_p() { return file_p(getCurrentContext()); } @JRubyMethod(name = "ftype") public RubyString ftype(ThreadContext context) { checkInitialized(context); return newString(context, stat.ftype()); } @Deprecated(since = "10.0.0.0") public RubyString ftype() { return ftype(getCurrentContext()); } @JRubyMethod(name = "gid") public IRubyObject gid(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return RubyFixnum.zero(context.runtime); return asFixnum(context, stat.gid()); } @Deprecated(since = "10.0.0.0") public IRubyObject gid() { return gid(getCurrentContext()); } @JRubyMethod(name = "grpowned?") public IRubyObject group_owned_p(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.fals; return asBoolean(context, stat.isGroupOwned()); } @Deprecated(since = "10.0.0.0") public IRubyObject group_owned_p() { return group_owned_p(getCurrentContext()); } @JRubyMethod(name = "initialize_copy", visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(ThreadContext context, IRubyObject original) { if (!(original instanceof RubyFileStat)) throw typeError(context, "wrong argument class"); checkFrozen(); RubyFileStat originalFileStat = (RubyFileStat) original; file = originalFileStat.file; stat = originalFileStat.stat; return this; } @JRubyMethod(name = "ino") public IRubyObject ino(ThreadContext context) { checkInitialized(context); return asFixnum(context, stat.ino()); } @Deprecated(since = "10.0.0.0") public IRubyObject ino() { return ino(getCurrentContext()); } @JRubyMethod(name = "inspect") public IRubyObject inspect(ThreadContext context) { StringBuilder buf = new StringBuilder("#<"); buf.append(getMetaClass().getRealClass().getName(context)); if (stat == null) { buf.append(": uninitialized"); } else { buf.append(' '); // FIXME: Obvious issue that not all platforms can display all attributes. Ugly hacks. // Using generic posix library makes pushing inspect behavior into specific system impls // rather painful. try { buf.append("dev=0x").append(Long.toHexString(stat.dev())); } catch (Exception e) {} finally { buf.append(", "); } try { buf.append("ino=").append(stat.ino()); } catch (Exception e) {} finally { buf.append(", "); } buf.append("mode=0").append(Integer.toOctalString(stat.mode())).append(", "); try { buf.append("nlink=").append(stat.nlink()); } catch (Exception e) {} finally { buf.append(", "); } try { buf.append("uid=").append(stat.uid()); } catch (Exception e) {} finally { buf.append(", "); } try { buf.append("gid=").append(stat.gid()); } catch (Exception e) {} finally { buf.append(", "); } try { buf.append("rdev=0x").append(Long.toHexString(stat.rdev())); } catch (Exception e) {} finally { buf.append(", "); } buf.append("size=").append(sizeInternal(context)).append(", "); try { buf.append("blksize=").append(blockSize(context).inspect(context).toString()); } catch (Exception e) {} finally { buf.append(", "); } try { buf.append("blocks=").append(blocks().inspect(context).toString()); } catch (Exception e) {} finally { buf.append(", "); } buf.append("atime=").append(atime().inspect(context)).append(", "); buf.append("mtime=").append(mtime().inspect(context)).append(", "); buf.append("ctime=").append(ctime().inspect(context)); if (Platform.IS_BSD || Platform.IS_MAC) { buf.append(", ").append("birthtime=").append(birthtime()); } } buf.append('>'); return newString(context, buf.toString()); } @JRubyMethod(name = "uid") public IRubyObject uid(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return RubyFixnum.zero(context.runtime); return asFixnum(context, stat.uid()); } @Deprecated(since = "10.0.0.0") public IRubyObject uid() { return uid(getCurrentContext()); } @JRubyMethod(name = "mode") public IRubyObject mode(ThreadContext context) { checkInitialized(context); return asFixnum(context, stat.mode()); } @Deprecated(since = "10.0.0.0") public IRubyObject mode() { return mode(getCurrentContext()); } @JRubyMethod(name = "mtime") public IRubyObject mtime(ThreadContext context) { checkInitialized(context); return stat instanceof NanosecondFileStat ? newTimeFromNanoseconds(context, stat.mtime() * BILLION + ((NanosecondFileStat) stat).mTimeNanoSecs()) : context.runtime.newTime(stat.mtime() * 1000); } @Deprecated(since = "10.0.0.0") public IRubyObject mtime() { return mtime(getCurrentContext()); } public IRubyObject mtimeEquals(ThreadContext context, IRubyObject other) { FileStat otherStat = newFileStat(context.runtime, other.convertToString().toString(), false).stat; boolean equal = stat.mtime() == otherStat.mtime(); if (stat instanceof NanosecondFileStat && otherStat instanceof NanosecondFileStat) { equal = equal && ((NanosecondFileStat) stat).mTimeNanoSecs() == ((NanosecondFileStat) otherStat).mTimeNanoSecs(); } return asBoolean(context, equal); } @Deprecated(since = "10.0.0.0") public IRubyObject mtimeEquals(IRubyObject other) { return mtimeEquals(getCurrentContext(), other); } public IRubyObject mtimeGreaterThan(ThreadContext context, IRubyObject other) { FileStat otherStat = newFileStat(context.runtime, other.convertToString().toString(), false).stat; boolean gt; if (stat instanceof NanosecondFileStat && otherStat instanceof NanosecondFileStat) { gt = (stat.mtime() * BILLION + ((NanosecondFileStat) stat).mTimeNanoSecs()) > (otherStat.mtime() * BILLION + ((NanosecondFileStat) otherStat).mTimeNanoSecs()); } else { gt = stat.mtime() > otherStat.mtime(); } return asBoolean(context, gt); } @Deprecated(since = "10.0.0.0") public IRubyObject mtimeGreaterThan(IRubyObject other) { return mtimeGreaterThan(getCurrentContext(), other); } public IRubyObject mtimeLessThan(ThreadContext context, IRubyObject other) { FileStat otherStat = newFileStat(context.runtime, other.convertToString().toString(), false).stat; boolean lt; if (stat instanceof NanosecondFileStat && otherStat instanceof NanosecondFileStat) { lt = (stat.mtime() * BILLION + ((NanosecondFileStat) stat).mTimeNanoSecs()) < (otherStat.mtime() * BILLION + ((NanosecondFileStat) otherStat).mTimeNanoSecs()); } else { lt = stat.mtime() < otherStat.mtime(); } return asBoolean(context, lt); } @Deprecated(since = "10.0.0.0") public IRubyObject mtimeLessThan(IRubyObject other) { return mtimeLessThan(getCurrentContext(), other); } @JRubyMethod(name = "nlink") public IRubyObject nlink(ThreadContext context) { checkInitialized(context); return asFixnum(context, stat.nlink()); } @Deprecated(since = "10.0.0.0") public IRubyObject nlink() { return nlink(getCurrentContext()); } @JRubyMethod(name = "owned?") public IRubyObject owned_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isOwned()); } @Deprecated(since = "10.0.0.0") public IRubyObject owned_p() { return owned_p(getCurrentContext()); } @JRubyMethod(name = "pipe?") public IRubyObject pipe_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isNamedPipe()); } @Deprecated(since = "10.0.0.0") public IRubyObject pipe_p() { return pipe_p(getCurrentContext()); } @JRubyMethod(name = "rdev") public IRubyObject rdev(ThreadContext context) { checkInitialized(context); return asFixnum(context, stat.rdev()); } @Deprecated(since = "10.0.0.0") public IRubyObject rdev() { return rdev(getCurrentContext()); } @JRubyMethod(name = "rdev_major") public IRubyObject rdevMajor(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.major(stat.rdev())); } @Deprecated(since = "10.0.0.0") public IRubyObject rdevMajor() { return rdevMajor(getCurrentContext()); } @JRubyMethod(name = "rdev_minor") public IRubyObject rdevMinor(ThreadContext context) { checkInitialized(context); if (Platform.IS_WINDOWS) return context.nil; return asFixnum(context, stat.minor(stat.rdev())); } @Deprecated(since = "10.0.0.0") public IRubyObject rdevMinor() { return rdevMinor(getCurrentContext()); } @JRubyMethod(name = "readable?") public IRubyObject readable_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isReadable()); } @Deprecated(since = "10.0.0.0") public IRubyObject readable_p() { return readable_p(getCurrentContext()); } @JRubyMethod(name = "readable_real?") public IRubyObject readableReal_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isReadableReal()); } @Deprecated(since = "10.0.0.0") public IRubyObject readableReal_p() { return readableReal_p(getCurrentContext()); } @JRubyMethod(name = "setgid?") public IRubyObject setgid_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isSetgid()); } @Deprecated(since = "10.0.0.0") public IRubyObject setgid_p() { return setgid_p(getCurrentContext()); } @JRubyMethod(name = "setuid?") public IRubyObject setuid_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isSetuid()); } @Deprecated(since = "10.0.0.0") public IRubyObject setuid_p() { return setuid_p(getCurrentContext()); } private long sizeInternal(ThreadContext context) { checkInitialized(context); // Workaround for JRUBY-4149 if (Platform.IS_WINDOWS && file != null) { try { return file.length(); } catch (SecurityException ex) { return 0L; } } else { return stat.st_size(); } } @JRubyMethod(name = "size") public IRubyObject size(ThreadContext context) { return asFixnum(context, sizeInternal(context)); } @Deprecated(since = "10.0.0.0") public IRubyObject size() { return size(getCurrentContext()); } @JRubyMethod(name = "size?") public IRubyObject size_p(ThreadContext context) { long size = sizeInternal(context); if (size == 0) return context.nil; return asFixnum(context, size); } @Deprecated(since = "10.0.0.0") public IRubyObject size_p() { return size_p(getCurrentContext()); } @JRubyMethod(name = "socket?") public IRubyObject socket_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isSocket()); } @Deprecated(since = "10.0.0.0") public IRubyObject socket_p() { return socket_p(getCurrentContext()); } @JRubyMethod(name = "sticky?") public IRubyObject sticky_p(ThreadContext context) { checkInitialized(context); return context.runtime.getPosix().isNative() ? asBoolean(context, stat.isSticky()) : context.nil; } @Deprecated(since = "10.0.0.0") public IRubyObject sticky_p() { return sticky_p(getCurrentContext()); } @JRubyMethod(name = "symlink?") public IRubyObject symlink_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isSymlink()); } @Deprecated(since = "10.0.0.0") public IRubyObject symlink_p() { return symlink_p(getCurrentContext()); } @JRubyMethod(name = "writable?") public IRubyObject writable_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isWritable()); } @Deprecated(since = "10.0.0.0") public IRubyObject writable_p() { return writable_p(getCurrentContext()); } @JRubyMethod(name = "writable_real?") public IRubyObject writableReal_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isWritableReal()); } @Deprecated(since = "10.0.0.0") public IRubyObject writableReal_p() { return writableReal_p(getCurrentContext()); } @JRubyMethod(name = "zero?") public IRubyObject zero_p(ThreadContext context) { checkInitialized(context); return asBoolean(context, stat.isEmpty()); } @Deprecated(since = "10.0.0.0") public IRubyObject zero_p() { return zero_p(getCurrentContext()); } @JRubyMethod(name = "world_readable?") public IRubyObject worldReadable(ThreadContext context) { return getWorldMode(context, FileStat.S_IROTH); } @JRubyMethod(name = "world_writable?") public IRubyObject worldWritable(ThreadContext context) { return getWorldMode(context, FileStat.S_IWOTH); } private IRubyObject getWorldMode(ThreadContext context, int mode) { checkInitialized(context); if ((stat.mode() & mode) == mode) { return asFixnum(context, (stat.mode() & (S_IRUGO | S_IWUGO | S_IXUGO))); } return context.nil; } }