/*
***** 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) 2004-2006 Thomas E Enebo <enebo@acm.org>
* Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
* Copyright (C) 2005-2008 Charles O Nutter <headius@headius.com>
* Copyright (C) 2006 Evan Buswell <evan@heron.sytes.net>
* Copyright (C) 2006 Dave Brosius <dbrosius@mebigfatguy.com>
*
* 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.util.io;
import jnr.constants.platform.Fcntl;
import jnr.constants.platform.OpenFlags;
import jnr.posix.POSIX;
import org.jruby.Ruby;
import org.jruby.platform.Platform;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
This file represents the POSIX-like mode flags an open channel (as in a
ChannelDescriptor) can have. It provides the basic flags for read/write as
well as flags for create, truncate, and others. In addition, it provides
methods for querying specific flag settings and converting to two other
formats: a Java mode string and an OpenFile mode int.
Note: In MRI these are called oflags.
See Also: - OpenFile
/**
* This file represents the POSIX-like mode flags an open channel (as in a
* ChannelDescriptor) can have. It provides the basic flags for read/write as
* well as flags for create, truncate, and others. In addition, it provides
* methods for querying specific flag settings and converting to two other
* formats: a Java mode string and an OpenFile mode int.
*
* Note: In MRI these are called oflags.
*
* @see org.jruby.util.io.OpenFile
*/
public class ModeFlags implements Cloneable {
read-only flag (default value if no other flags set) /** read-only flag (default value if no other flags set) */
public static final int RDONLY = OpenFlags.O_RDONLY.intValue();
write-only flag /** write-only flag */
public static final int WRONLY = OpenFlags.O_WRONLY.intValue();
read/write flag /** read/write flag */
public static final int RDWR = OpenFlags.O_RDWR.intValue();
create flag, to specify non-existing file should be created /** create flag, to specify non-existing file should be created */
public static final int CREAT = OpenFlags.O_CREAT.intValue();
exclusive access flag, to require locking the target file /** exclusive access flag, to require locking the target file */
public static final int EXCL = OpenFlags.O_EXCL.intValue();
truncate flag, to truncate the target file to zero length /** truncate flag, to truncate the target file to zero length */
public static final int TRUNC = OpenFlags.O_TRUNC.intValue();
append flag, to seek to the end of the file /** append flag, to seek to the end of the file */
public static final int APPEND = OpenFlags.O_APPEND.intValue();
nonblock flag, to perform all operations non-blocking. Unused currently /** nonblock flag, to perform all operations non-blocking. Unused currently */
public static final int NONBLOCK = OpenFlags.O_NONBLOCK.intValue();
binary flag, to ensure no encoding changes are made while writing (Windows only) /** binary flag, to ensure no encoding changes are made while writing (Windows only) */
public static final int BINARY = OpenFlags.O_BINARY.defined() ? OpenFlags.O_BINARY.intValue() : 0;
tmpfile flag (Linux only) /** tmpfile flag (Linux only) **/
public static final int TMPFILE = OpenFlags.O_TMPFILE.defined() ? OpenFlags.O_TMPFILE.intValue() : 0;
textmode flag, MRI has no equivalent but we use ModeFlags currently
to also capture what are oflags.
/** textmode flag, MRI has no equivalent but we use ModeFlags currently
* to also capture what are oflags.
*/
public static final int TEXT = 0x10000000;
accmode flag, used to mask the read/write mode /** accmode flag, used to mask the read/write mode */
public static final int ACCMODE = RDWR | WRONLY | RDONLY;
the set of flags for this ModeFlags instance /** the set of flags for this ModeFlags instance */
private final int flags;
Construct a new ModeFlags object with the default read-only flag.
/**
* Construct a new ModeFlags object with the default read-only flag.
*/
public ModeFlags() {
flags = RDONLY;
}
Construct a new ModeFlags object with the specified flags
Params: - flags – The flags to use for this object
Throws: - InvalidValueException – If the modes are invalid
/**
* Construct a new ModeFlags object with the specified flags
*
* @param flags The flags to use for this object
* @throws org.jruby.util.io.InvalidValueException If the modes are invalid
*/
public ModeFlags(long flags) throws InvalidValueException {
// TODO: Ruby does not seem to care about invalid numeric mode values
// I am not sure if ruby overflows here also...
this.flags = (int)flags;
}
public ModeFlags(String flagString) throws InvalidValueException {
this.flags = getOFlagsFromString(flagString);
}
public static int getOFlagsFromString(String modesString) throws InvalidValueException {
int modes = 0;
int length = modesString.length();
if (length == 0) {
throw new InvalidValueException();
}
switch (modesString.charAt(0)) {
case 'r' :
modes |= RDONLY;
break;
case 'a' :
modes |= APPEND | WRONLY | CREAT;
break;
case 'w' :
modes |= WRONLY | TRUNC | CREAT;
break;
default :
throw new InvalidValueException();
}
ModifierLoop: for (int n = 1; n < length; n++) {
switch (modesString.charAt(n)) {
case 'b':
modes |= BINARY;
break;
case '+':
modes = (modes & ~ACCMODE) | RDWR;
break;
case 't' :
modes |= TEXT;
break;
case ':':
break ModifierLoop;
default:
throw new InvalidValueException();
}
}
return modes;
}
public static int getOFlagsFromString(Ruby runtime, String modesString) {
try {
return getOFlagsFromString(modesString);
} catch (InvalidValueException ive) {
throw runtime.newErrnoEINVALError("mode string: " + modesString);
}
}
Build a set of mode flags using the specified channel's actual capabilities.
Params: - channel – the channel to examine for capabilities
Returns: the mode flags
/**
* Build a set of mode flags using the specified channel's actual capabilities.
*
* @param channel the channel to examine for capabilities
* @return the mode flags
*/
public static int oflagsFrom(POSIX posix, Channel channel) {
int mode;
int fileno = FilenoUtil.filenoFrom(channel);
if (FilenoUtil.isFake(fileno) || !posix.isNative() || Platform.IS_WINDOWS) {
// channel doesn't have a real fileno; best we can do is go off the Java type
if (channel instanceof ReadableByteChannel) {
if (channel instanceof WritableByteChannel) {
mode = RDWR;
} else {
mode = RDONLY;
}
} else if (channel instanceof WritableByteChannel) {
mode = WRONLY;
} else {
// FIXME: I don't like this
mode = RDWR;
}
} else {
// real fileno, we can use fcntl
mode = posix.fcntl(fileno, Fcntl.F_GETFL);
}
return mode;
}
Build a set of mode flags using the specified channel's actual capabilities.
Params: - channel – the channel to examine for capabilities
Returns: the mode flags
/**
* Build a set of mode flags using the specified channel's actual capabilities.
*
* @param channel the channel to examine for capabilities
* @return the mode flags
*/
@Deprecated
public static ModeFlags getModesFromChannel(Channel channel) {
try {
ModeFlags modes;
if (channel instanceof ReadableByteChannel) {
if (channel instanceof WritableByteChannel) {
modes = new ModeFlags(RDWR);
} else {
modes = new ModeFlags(RDONLY);
}
} else if (channel instanceof WritableByteChannel) {
modes = new ModeFlags(WRONLY);
} else {
// FIXME: I don't like this
modes = new ModeFlags(RDWR);
}
return modes;
} catch (InvalidValueException ive) {
// should never happen, because all values above are valid
throw new RuntimeException(ive);
}
}
Produce a Java IO mode string from the flags in this object.
Returns: A Java string suitable for opening files with RandomAccessFile
/**
* Produce a Java IO mode string from the flags in this object.
*
* @return A Java string suitable for opening files with RandomAccessFile
*/
public String toJavaModeString() {
// Do not open as 'rw' by default since a file with read-only permissions will fail on 'rw'
if (isWritable() || isCreate() || isTruncate()) {
// Java requires "w" for creating a file that does not exist
return "rw";
} else {
return "r";
}
}
Whether the flags specify"read only".
Returns: true if read-only, false otherwise
/**
* Whether the flags specify"read only".
*
* @return true if read-only, false otherwise
*/
public final boolean isReadOnly() {
return ((flags & WRONLY) == 0) && ((flags & RDWR) == 0);
}
Whether the flags specify "readable", either read/write or read-only.
Returns: true if readable, false otherwise
/**
* Whether the flags specify "readable", either read/write or read-only.
*
* @return true if readable, false otherwise
*/
public boolean isReadable() {
return ((flags & RDWR) != 0) || isReadOnly();
}
Whether the flags specify "binary" mode for reads and writes.
Returns: true if binary mode, false otherwise
/**
* Whether the flags specify "binary" mode for reads and writes.
*
* @return true if binary mode, false otherwise
*/
public boolean isBinary() {
return (flags & BINARY) != 0;
}
Whether the flags specify "text" mode for reads and writes.
Returns: true if text mode, false otherwise
/**
* Whether the flags specify "text" mode for reads and writes.
*
* @return true if text mode, false otherwise
*/
public boolean isText() {
return (flags & TEXT) != 0;
}
Whether the flags specify "unnamed temporary".
Returns: true if unnamed temporary mode, false otherwise
/**
* Whether the flags specify "unnamed temporary".
*
* @return true if unnamed temporary mode, false otherwise
*/
public boolean isTemporary() {
return (flags & TMPFILE) != 0;
}
Whether the flags specify to create nonexisting files.
Returns: true if nonexisting files should be created, false otherwise
/**
* Whether the flags specify to create nonexisting files.
*
* @return true if nonexisting files should be created, false otherwise
*/
public boolean isCreate() {
return (flags & CREAT) != 0;
}
Whether the flags specify "writable", either read/write or write-only
Returns: true if writable, false otherwise
/**
* Whether the flags specify "writable", either read/write or write-only
*
* @return true if writable, false otherwise
*/
public boolean isWritable() {
return (flags & RDWR) != 0 || (flags & WRONLY) != 0;
}
Whether the flags specify exclusive access.
Returns: true if exclusive, false otherwise
/**
* Whether the flags specify exclusive access.
*
* @return true if exclusive, false otherwise
*/
public boolean isExclusive() {
return (flags & EXCL) != 0;
}
Whether the flags specify to append to existing files.
Returns: true if append, false otherwise
/**
* Whether the flags specify to append to existing files.
*
* @return true if append, false otherwise
*/
public boolean isAppendable() {
return (flags & APPEND) != 0;
}
Whether the flags specify to truncate the target file.
Returns: true if truncate, false otherwise
/**
* Whether the flags specify to truncate the target file.
*
* @return true if truncate, false otherwise
*/
public boolean isTruncate() {
return (flags & TRUNC) != 0;
}
Check whether the target set of flags is a superset of this one; used to
ensure that a file is not re-opened with more privileges than it already
had.
Params: - superset – The ModeFlags object which should be a superset of this one
Returns: true if the object is a superset, false otherwise
/**
* Check whether the target set of flags is a superset of this one; used to
* ensure that a file is not re-opened with more privileges than it already
* had.
*
* @param superset The ModeFlags object which should be a superset of this one
* @return true if the object is a superset, false otherwise
*/
public boolean isSubsetOf(ModeFlags superset) {
// TODO: Make sure all appropriate open flags are added to this check.
if ((!superset.isReadable() && isReadable()) ||
(!superset.isWritable() && isWritable()) ||
!superset.isAppendable() && isAppendable()) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("ModeFlags(").append(flags).append("): ");
if (isAppendable()) buf.append("APPENDABLE ");
if (isBinary()) buf.append("BINARY ");
if (isCreate()) buf.append("CREATE ");
if (isExclusive()) buf.append("EXCLUSIVE ");
if (isReadOnly()) buf.append("READONLY ");
if (isText()) buf.append("TEXT ");
if (isTemporary()) buf.append("TMPFILE ");
if (isTruncate()) buf.append("TRUNCATE ");
if (isWritable()) {
if (isReadable()) {
buf.append("RDWR");
} else {
buf.append("WRITABLE ");
}
}
return buf.toString();
}
Return a value that, when passed to our constructor,
would create a copy of this instance.
Returns: an int of the private flags variable.
/**
* Return a value that, when passed to our constructor,
* would create a copy of this instance.
*
* @return an int of the private flags variable.
*/
public int getFlags() {
return flags;
}
// MRI: rb_io_oflags_fmode
With the provided open flags parameter what fmode values should be
set (fmode for us is represented by OpenFile).
/**
* With the provided open flags parameter what fmode values should be
* set (fmode for us is represented by OpenFile).
*/
public static int getOpenFileFlagsFor(int flags) {
int fmodeFlags;
int readWrite = flags & 3;
if (readWrite == RDONLY) {
fmodeFlags = OpenFile.READABLE;
} else if (readWrite == WRONLY) {
fmodeFlags = OpenFile.WRITABLE;
} else {
fmodeFlags = OpenFile.READWRITE;
}
if ((flags & APPEND) != 0) {
fmodeFlags |= OpenFile.APPEND;
}
if ((flags & CREAT) != 0) {
fmodeFlags |= OpenFile.CREATE;
}
if ((flags & BINARY) != 0) {
fmodeFlags |= OpenFile.BINMODE;
}
if ((flags & TMPFILE) != 0) {
fmodeFlags |= OpenFile.TMPFILE;
}
// This is unique to us to keep bridge betweeen mode_flags and oflags
if ((flags & TEXT) != 0) {
fmodeFlags |= OpenFile.TEXTMODE;
}
return fmodeFlags;
}
Convert the flags in this object to a set of flags appropriate for the
OpenFile structure and logic therein.
Returns: an int of flags appropriate for OpenFile
/**
* Convert the flags in this object to a set of flags appropriate for the
* OpenFile structure and logic therein.
*
* @return an int of flags appropriate for OpenFile
*/
public int getOpenFileFlags() {
return getOpenFileFlagsFor(flags);
}
Convert the flags in this object to a set of flags appropriate for the
fcntl.
Returns: an int of flags appropriate for fcntl
/**
* Convert the flags in this object to a set of flags appropriate for the
* fcntl.
*
* @return an int of flags appropriate for fcntl
*/
public int getFcntlFileFlags() {
int fcntlFlags;
int readWrite = flags & 3;
if (readWrite == RDONLY) {
fcntlFlags = 0;
} else if (readWrite == WRONLY) {
fcntlFlags = 1;
} else {
fcntlFlags = 2;
}
return fcntlFlags;
}
public static ModeFlags createModeFlags(int oflags) {
try {
return new ModeFlags(oflags);
} catch (InvalidValueException e) {
return new ModeFlags();
}
}
}