RegularFileResource.java

  1. package org.jruby.util;

  2. import jnr.constants.platform.Errno;
  3. import jnr.constants.platform.Fcntl;
  4. import jnr.enxio.channels.NativeDeviceChannel;
  5. import jnr.posix.FileStat;
  6. import jnr.posix.POSIX;
  7. import java.io.File;
  8. import java.io.FileInputStream;
  9. import java.io.FileNotFoundException;
  10. import java.io.FileOutputStream;
  11. import java.io.InputStream;
  12. import java.io.IOException;
  13. import java.io.RandomAccessFile;
  14. import java.nio.channels.Channel;
  15. import java.nio.channels.FileChannel;

  16. import org.jruby.ext.fcntl.FcntlLibrary;
  17. import org.jruby.RubyFile;
  18. import org.jruby.util.io.ModeFlags;
  19. import org.jruby.util.io.PosixShim;

  20. /**
  21.  * Represents a "regular" file, backed by regular file system.
  22.  */
  23. class RegularFileResource extends AbstractFileResource {
  24.     private final JRubyFile file;
  25.     private final POSIX posix;

  26.     RegularFileResource(POSIX posix, File file) {
  27.         this(posix, file.getAbsolutePath());
  28.     }

  29.     protected RegularFileResource(POSIX posix, String filename) {
  30.         this.file = new JRubyFile(filename);
  31.         this.posix = posix;
  32.     }

  33.     @Override
  34.     public String absolutePath() {
  35.         return RubyFile.canonicalize(file.getAbsolutePath());
  36.     }

  37.     @Override
  38.     public String canonicalPath() {
  39.         // Seems like for Ruby absolute path implies resolving system links,
  40.         // so canonicalization is in order.
  41.         try {
  42.             return file.getCanonicalPath();
  43.         } catch (IOException ioError) {
  44.             // I guess absolute path is next best thing?
  45.             return file.getAbsolutePath();
  46.         }
  47.     }

  48.     @Override
  49.     public long length() {
  50.         return file.length();
  51.     }

  52.     @Override
  53.     public long lastModified() {
  54.         return file.lastModified();
  55.     }

  56.     @Override
  57.     public boolean exists() {
  58.         // MRI behavior: Even broken symlinks should return true.
  59.         // FIXME: Where is the above statement true?  For RubyFile{,Test} it does not seem to be.
  60.         return file.exists(); // || isSymLink();
  61.     }

  62.     @Override
  63.     public boolean canExecute() {
  64.         return file.canExecute();
  65.     }

  66.     public int errno() {
  67.         return posix.errno();
  68.     }

  69.     @Override
  70.     public boolean isFile() {
  71.         return file.isFile();
  72.     }

  73.     @Override
  74.     public boolean isDirectory() {
  75.         return file.isDirectory();
  76.     }

  77.     @Override
  78.     public boolean isSymLink() {
  79.         FileStat stat = posix.allocateStat();

  80.         return posix.lstat(file.getAbsolutePath(), stat) < 0 ?
  81.                 false : stat.isSymlink();
  82.     }

  83.     @Override
  84.     public boolean canRead() {
  85.         return file.canRead();
  86.     }

  87.     @Override
  88.     public boolean canWrite() {
  89.         return file.canWrite();
  90.     }

  91.     @Override
  92.     public String[] list() {
  93.         String[] fileList = file.list();

  94.         if (fileList == null) return null;

  95.         // If we got some entries, then it's probably a directory and in Ruby all file
  96.         // directories should have '.' and '..' entries
  97.         String[] list = new String[fileList.length + 2];
  98.         list[0] = ".";
  99.         list[1] = "..";
  100.         System.arraycopy(fileList, 0, list, 2, fileList.length);

  101.         return list;
  102.     }

  103.     @Override
  104.     public FileStat stat() {
  105.         FileStat stat = posix.allocateStat();

  106.         return posix.stat(file.getAbsolutePath(), stat) < 0 ? null : stat;
  107.     }

  108.     @Override
  109.     public FileStat lstat() {
  110.         FileStat stat = posix.allocateStat();

  111.         return posix.lstat(file.getAbsolutePath(), stat) < 0 ? null : stat;
  112.     }

  113.     @Override
  114.     public String toString() {
  115.         return file.toString();
  116.     }

  117.     @Override
  118.     public JRubyFile hackyGetJRubyFile() {
  119.         return file;
  120.     }

  121.     @Override
  122.     InputStream openInputStream() throws IOException {
  123.         return new FileInputStream(file);
  124.     }

  125.     @Override
  126.     public Channel openChannel(ModeFlags flags, int perm) throws ResourceException {
  127.         if (posix.isNative()) {
  128.             int fd = posix.open(absolutePath(), flags.getFlags(), perm);
  129.             if (fd < 0) {
  130.                 Errno errno = Errno.valueOf(posix.errno());
  131.                 switch (errno) {
  132.                     case EACCES:
  133.                         throw new ResourceException.PermissionDenied(absolutePath());
  134.                     case EEXIST:
  135.                         throw new ResourceException.FileExists(absolutePath());
  136.                     case EINVAL:
  137.                         throw new ResourceException.InvalidArguments(absolutePath());
  138.                     case ENOENT:
  139.                         throw new ResourceException.NotFound(absolutePath());
  140.                     case ELOOP:
  141.                         throw new ResourceException.TooManySymlinks(absolutePath());
  142.                     default:
  143.                         throw new ResourceException.IOError(new IOException("unhandled errno: " + errno));

  144.                 }
  145.             }
  146.             posix.fcntlInt(fd, Fcntl.F_SETFD, posix.fcntl(fd, Fcntl.F_GETFD) | FcntlLibrary.FD_CLOEXEC);
  147.             return new NativeDeviceChannel(fd);
  148.         }

  149.         if (flags.isCreate()) {
  150.             boolean fileCreated;
  151.             try {
  152.                 fileCreated = file.createNewFile();
  153.             } catch (IOException ioe) {
  154.                 // See JRUBY-4380.
  155.                 // when the directory for the file doesn't exist.
  156.                 // Java in such cases just throws IOException.
  157.                 File parent = file.getParentFile();
  158.                 if (parent != null && parent != file && !parent.exists()) {
  159.                     throw new ResourceException.NotFound(absolutePath());
  160.                 } else if (!file.canWrite()) {
  161.                     throw new ResourceException.PermissionDenied(absolutePath());
  162.                 } else {
  163.                     // for all other IO errors, we report it as general IO error
  164.                     throw new ResourceException.IOError(ioe);
  165.                 }
  166.             }

  167.             if (!fileCreated && flags.isExclusive()) {
  168.                 throw new ResourceException.FileExists(absolutePath());
  169.             }

  170.             Channel channel = createChannel(flags);

  171.             // attempt to set the permissions, if we have been passed a POSIX instance,
  172.             // perm is > 0, and only if the file was created in this call.
  173.             if (fileCreated && posix != null) {
  174.                 perm = perm & ~PosixShim.umask(posix);

  175.                 if (perm > 0) posix.chmod(file.getPath(), perm);
  176.             }

  177.             return channel;
  178.         }

  179.         if (file.isDirectory() && flags.isWritable()) {
  180.             throw new ResourceException.FileIsDirectory(absolutePath());
  181.         }

  182.         if (!file.exists()) {
  183.             throw new ResourceException.NotFound(absolutePath());
  184.         }

  185.         return createChannel(flags);
  186.     }

  187.     private Channel createChannel(ModeFlags flags) throws ResourceException {
  188.         FileChannel fileChannel;

  189.         /* Because RandomAccessFile does not provide a way to pass append
  190.          * mode, we must manually seek if using RAF. FileOutputStream,
  191.          * however, does properly honor append mode at the lowest levels,
  192.          * reducing append write costs when we're only doing writes.
  193.          *
  194.          * The code here will use a FileOutputStream if we're only writing,
  195.          * setting isInAppendMode to true to disable our manual seeking.
  196.          *
  197.          * RandomAccessFile does not handle append for us, so if we must
  198.          * also be readable we pass false for isInAppendMode to indicate
  199.          * we need manual seeking.
  200.          */
  201.         try{
  202.             if (flags.isWritable() && !flags.isReadable()) {
  203.                 FileOutputStream fos = new FileOutputStream(file, flags.isAppendable());
  204.                 fileChannel = fos.getChannel();
  205.             } else {
  206.                 RandomAccessFile raf = new RandomAccessFile(file, flags.toJavaModeString());
  207.                 fileChannel = raf.getChannel();
  208.             }
  209.         } catch (FileNotFoundException fnfe) {
  210.             // Jave throws FileNotFoundException both if the file doesn't exist or there were
  211.             // permission issues, but Ruby needs to disambiguate those two cases
  212.             throw file.exists() ?
  213.                     new ResourceException.PermissionDenied(absolutePath()) :
  214.                     new ResourceException.NotFound(absolutePath());
  215.         } catch (IOException ioe) {
  216.             throw new ResourceException.IOError(ioe);
  217.         }

  218.         try {
  219.             if (flags.isTruncate()) fileChannel.truncate(0);
  220.         } catch (IOException ioe) {
  221.             // ignore; it's a pipe or fifo that can't be truncated (we only care about illegal seek).
  222.             if (!ioe.getMessage().equals("Illegal seek")) throw new ResourceException.IOError(ioe);
  223.         }

  224.         return fileChannel;
  225.     }
  226. }