| |
| // natFileChannelImplPosix.cc - Native part of FileChannelImpl class. |
| |
| /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2006 Free Software Foundation |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| #include <config.h> |
| #include <platform.h> |
| |
| #include <gcj/cni.h> |
| #include <gcj/javaprims.h> |
| #include <jvm.h> |
| |
| #include "posix.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/param.h> |
| |
| #include <gnu/gcj/RawData.h> |
| #include <gnu/java/nio/FileLockImpl.h> |
| #include <gnu/java/nio/channels/FileChannelImpl.h> |
| #include <java/io/FileNotFoundException.h> |
| #include <java/io/IOException.h> |
| #include <java/io/SyncFailedException.h> |
| #include <java/io/InterruptedIOException.h> |
| #include <java/io/EOFException.h> |
| #include <java/lang/ArrayIndexOutOfBoundsException.h> |
| #include <java/lang/NullPointerException.h> |
| #include <java/lang/System.h> |
| #include <java/lang/String.h> |
| #include <java/lang/StringBuffer.h> |
| #include <java/lang/Thread.h> |
| #include <java/nio/ByteBuffer.h> |
| #include <java/nio/MappedByteBufferImpl.h> |
| #include <java/nio/channels/FileChannel.h> |
| #include <java/nio/channels/FileLock.h> |
| #include <gnu/java/nio/channels/FileChannelImpl.h> |
| |
| #ifdef HAVE_SYS_IOCTL_H |
| #define BSD_COMP /* Get FIONREAD on Solaris2. */ |
| #include <sys/ioctl.h> |
| #endif |
| |
| // Pick up FIONREAD on Solaris 2.5. |
| #ifdef HAVE_SYS_FILIO_H |
| #include <sys/filio.h> |
| #endif |
| |
| #ifdef HAVE_MMAP |
| #include <sys/mman.h> |
| |
| // Use overload resolution to find out the argument types. |
| // E.g. Solaris 2.6 uses different argument types for munmap and msync. |
| // This is in case _POSIX_C_SOURCES is smaller than 3. |
| |
| template <typename T_implPtr, typename T_implLen> |
| static inline int |
| munmap_adaptor(int (*munmap)(T_implPtr caddr, T_implLen sizet), |
| void* caddr, size_t sizet) |
| { |
| return munmap ((T_implPtr) caddr, (T_implLen) sizet); |
| } |
| |
| template <typename T_implPtr, typename T_implLen, typename T_msync> |
| static inline int |
| msync_adaptor(int (*msync)(T_implPtr caddr, T_implLen sizet, T_msync msynct), |
| void* caddr, size_t sizet, int msynct) |
| { |
| return msync ((T_implPtr) caddr, (T_implLen) sizet, (T_msync) msynct); |
| } |
| #endif |
| |
| using gnu::gcj::RawData; |
| using java::io::IOException; |
| using java::nio::MappedByteBufferImpl; |
| using java::io::InterruptedIOException; |
| using java::io::FileNotFoundException; |
| using java::lang::ArrayIndexOutOfBoundsException; |
| using gnu::java::nio::channels::FileChannelImpl; |
| |
| #define NO_FSYNC_MESSAGE "sync unsupported" |
| |
| void |
| FileChannelImpl::init(void) |
| { |
| in = new FileChannelImpl((jint) 0, FileChannelImpl::READ); |
| out = new FileChannelImpl((jint) 1, FileChannelImpl::WRITE); |
| err = new FileChannelImpl((jint) 2, FileChannelImpl::WRITE); |
| } |
| |
| #if 0 |
| jboolean |
| FileChannelImpl::valid (void) |
| { |
| struct stat sb; |
| return fd >= 0 && ::fstat (fd, &sb) == 0; |
| } |
| |
| void |
| FileChannelImpl::sync (void) |
| { |
| // Some files don't support fsync. We don't bother reporting these |
| // as errors. |
| #ifdef HAVE_FSYNC |
| if (::fsync (fd) && errno != EROFS && errno != EINVAL) |
| throw new SyncFailedException (JvNewStringLatin1 (strerror (errno))); |
| #else |
| throw new SyncFailedException (JvNewStringLatin1 (NO_FSYNC_MESSAGE)); |
| #endif |
| } |
| #endif |
| |
| jint |
| FileChannelImpl::open (jstring path, jint jflags) |
| { |
| fd = -1; |
| char *buf = (char *) _Jv_AllocBytes (_Jv_GetStringUTFLength (path) + 1); |
| jsize total = JvGetStringUTFRegion (path, 0, path->length(), buf); |
| buf[total] = '\0'; |
| int flags = 0; |
| #ifdef O_BINARY |
| flags |= O_BINARY; |
| #endif |
| |
| JvAssert ((jflags & READ) || (jflags & WRITE)); |
| int mode = 0666; |
| if ((jflags & READ) && (jflags & WRITE)) |
| flags |= O_RDWR | O_CREAT; |
| else if ((jflags & READ)) |
| flags |= O_RDONLY; |
| else |
| { |
| flags |= O_WRONLY | O_CREAT; |
| if ((jflags & APPEND)) |
| flags |= O_APPEND; |
| else |
| flags |= O_TRUNC; |
| |
| if ((jflags & EXCL)) |
| { |
| flags |= O_EXCL; |
| // In this case we are making a temp file. |
| mode = 0600; |
| } |
| } |
| |
| if ((jflags & SYNC)) |
| flags |= O_SYNC; |
| |
| if ((jflags & DSYNC)) |
| flags |= O_DSYNC; |
| |
| int fd = ::open (buf, flags, mode); |
| if (fd == -1 && errno == EMFILE) |
| { |
| // Because finalize () calls close () we might be able to continue. |
| ::java::lang::System::gc (); |
| ::java::lang::System::runFinalization (); |
| fd = ::open (buf, flags, mode); |
| } |
| if (fd == -1) |
| { |
| // We choose the formatting here for JDK compatibility, believe |
| // it or not. |
| ::java::lang::StringBuffer *msg = new ::java::lang::StringBuffer (path); |
| msg->append (JvNewStringUTF (" (")); |
| msg->append (JvNewStringUTF (strerror (errno))); |
| msg->append (JvNewStringUTF (")")); |
| throw new ::java::io::FileNotFoundException (msg->toString ()); |
| } |
| |
| return fd; |
| } |
| |
| void |
| FileChannelImpl::write (jint b) |
| { |
| jbyte d = (jbyte) b; |
| int r = 0; |
| while (r != 1) |
| { |
| r = ::write (fd, &d, 1); |
| if (r == -1) |
| { |
| if (::java::lang::Thread::interrupted()) |
| { |
| ::java::io::InterruptedIOException *iioe |
| = new ::java::io::InterruptedIOException (JvNewStringLatin1 (strerror (errno))); |
| iioe->bytesTransferred = r == -1 ? 0 : r; |
| throw iioe; |
| } |
| if (errno != EINTR) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| } |
| pos++; |
| } |
| |
| void |
| FileChannelImpl::write (jbyteArray b, jint offset, jint len) |
| { |
| if (! b) |
| throw new ::java::lang::NullPointerException; |
| if (offset < 0 || len < 0 || offset + len > JvGetArrayLength (b)) |
| throw new ArrayIndexOutOfBoundsException; |
| jbyte *bytes = elements (b) + offset; |
| |
| int written = 0; |
| while (len > 0) |
| { |
| int r = ::write (fd, bytes, len); |
| if (r == -1) |
| { |
| if (::java::lang::Thread::interrupted()) |
| { |
| InterruptedIOException *iioe |
| = new InterruptedIOException (JvNewStringLatin1 (strerror (errno))); |
| iioe->bytesTransferred = written; |
| throw iioe; |
| } |
| if (errno != EINTR) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| continue; |
| } |
| |
| written += r; |
| len -= r; |
| bytes += r; |
| pos += r; |
| } |
| } |
| |
| void |
| FileChannelImpl::implCloseChannel (void) |
| { |
| jint save = fd; |
| fd = -1; |
| if (::close (save)) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| |
| void |
| FileChannelImpl::implTruncate (jlong size) |
| { |
| struct stat sb; |
| |
| #ifdef HAVE_FTRUNCATE |
| if (::fstat (fd, &sb)) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| |
| if ((jlong) sb.st_size == size) |
| return; |
| |
| // If the file is too short, we extend it. We can't rely on |
| // ftruncate() extending the file. So we lseek() to 1 byte less |
| // than we want, and then we write a single byte at the end. |
| if ((jlong) sb.st_size < size) |
| { |
| if (::lseek (fd, (off_t) (size - 1), SEEK_SET) == -1) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| char out = '\0'; |
| int r = ::write (fd, &out, 1); |
| if (r <= 0 || ::lseek (fd, pos, SEEK_SET) == -1) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| else |
| { |
| if (::ftruncate (fd, (off_t) size)) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| if (pos > size |
| && ::lseek (fd, (off_t) size, SEEK_SET) == -1) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| pos = size; |
| } |
| #else /* HAVE_FTRUNCATE */ |
| throw new IOException (JvNewStringLatin1 ("FileDescriptor.setLength not implemented")); |
| #endif /* HAVE_FTRUNCATE */ |
| } |
| |
| void |
| FileChannelImpl::seek (jlong newPos) |
| { |
| off_t r = ::lseek (fd, (off_t) newPos, SEEK_SET); |
| if (r == -1) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| pos = r; |
| } |
| |
| jlong |
| FileChannelImpl::size (void) |
| { |
| struct stat sb; |
| if (::fstat (fd, &sb)) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| return sb.st_size; |
| } |
| |
| jlong |
| FileChannelImpl::implPosition (void) |
| { |
| return pos; |
| } |
| |
| jint |
| FileChannelImpl::read (void) |
| { |
| jbyte b; |
| int r; |
| do |
| { |
| r = ::read (fd, &b, 1); |
| if (r == 0) |
| return -1; |
| if (r == -1) |
| { |
| if (::java::lang::Thread::interrupted()) |
| { |
| InterruptedIOException *iioe |
| = new InterruptedIOException (JvNewStringLatin1 (strerror (errno))); |
| iioe->bytesTransferred = r == -1 ? 0 : r; |
| throw iioe; |
| } |
| if (errno != EINTR) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| } |
| while (r != 1); |
| pos++; |
| return b & 0xFF; |
| } |
| |
| jint |
| FileChannelImpl::read (jbyteArray buffer, jint offset, jint count) |
| { |
| if (! buffer) |
| throw new ::java::lang::NullPointerException; |
| jsize bsize = JvGetArrayLength (buffer); |
| if (offset < 0 || count < 0 || offset + count > bsize) |
| throw new ::java::lang::ArrayIndexOutOfBoundsException; |
| |
| // Must return 0 if an attempt is made to read 0 bytes. |
| if (count == 0) |
| return 0; |
| |
| jbyte *bytes = elements (buffer) + offset; |
| int r; |
| do |
| { |
| r = ::read (fd, bytes, count); |
| if (r == 0) |
| return -1; |
| if (r == -1) |
| { |
| if (::java::lang::Thread::interrupted()) |
| { |
| InterruptedIOException *iioe |
| = new InterruptedIOException (JvNewStringLatin1 (strerror (errno))); |
| iioe->bytesTransferred = r == -1 ? 0 : r; |
| throw iioe; |
| } |
| if (errno != EINTR) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| } |
| while (r <= 0); |
| pos += r; |
| return r; |
| } |
| |
| jint |
| FileChannelImpl::available (void) |
| { |
| #if defined (FIONREAD) || defined (HAVE_SELECT) || defined (HAVE_FSTAT) |
| int num = 0; |
| int r = 0; |
| bool num_set = false; |
| |
| #if defined (FIONREAD) |
| r = ::ioctl (fd, FIONREAD, &num); |
| if (r == -1 && errno == ENOTTY) |
| { |
| // If the ioctl doesn't work, we don't care. |
| r = 0; |
| num = 0; |
| } |
| else |
| num_set = true; |
| #elif defined (HAVE_SELECT) |
| if (fd < 0) |
| { |
| errno = EBADF; |
| r = -1; |
| } |
| #endif |
| |
| if (r == -1) |
| { |
| posix_error: |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| |
| // If we didn't get anything, and we have fstat, then see if see if |
| // we're reading a regular file. On many systems, FIONREAD does not |
| // work on regular files; select() likewise returns a useless |
| // result. This is run incorrectly when FIONREAD does work on |
| // regular files and we are at the end of the file. However, this |
| // case probably isn't very important. |
| #if defined (HAVE_FSTAT) |
| if (! num_set) |
| { |
| struct stat sb; |
| off_t where = 0; |
| if (fstat (fd, &sb) != -1 |
| && S_ISREG (sb.st_mode) |
| && (where = lseek (fd, 0, SEEK_CUR)) != (off_t) -1) |
| { |
| num = (int) (sb.st_size - where); |
| num_set = true; |
| } |
| } |
| #endif /* HAVE_FSTAT */ |
| |
| #if defined (HAVE_SELECT) |
| if (! num_set) |
| { |
| fd_set rd; |
| FD_ZERO (&rd); |
| FD_SET (fd, &rd); |
| struct timeval tv; |
| tv.tv_sec = 0; |
| tv.tv_usec = 0; |
| r = _Jv_select (fd + 1, &rd, NULL, NULL, &tv); |
| if (r == -1) |
| goto posix_error; |
| num = r == 0 ? 0 : 1; |
| } |
| #endif /* HAVE_SELECT */ |
| |
| return (jint) num; |
| #else |
| return 0; |
| #endif |
| } |
| |
| jboolean |
| FileChannelImpl::lock |
| (jlong pos, jlong len, jboolean shared, jboolean wait) |
| { |
| struct flock lockdata; |
| |
| lockdata.l_type = shared ? F_RDLCK : F_WRLCK; |
| lockdata.l_whence = SEEK_SET; |
| lockdata.l_start = pos; |
| lockdata.l_len = len; |
| |
| if (::fcntl (fd, wait ? F_SETLKW : F_SETLK, &lockdata) == -1) |
| { |
| if (! wait && (errno == EACCES || errno == EAGAIN)) |
| return false; |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| return true; |
| } |
| |
| void |
| FileChannelImpl::unlock (jlong pos, jlong len) |
| { |
| struct flock lockdata; |
| |
| lockdata.l_type = F_UNLCK; |
| lockdata.l_whence = SEEK_SET; |
| lockdata.l_start = pos; |
| lockdata.l_len = len; |
| |
| if (::fcntl (fd, F_SETLK, &lockdata) == -1) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| } |
| |
| java::nio::MappedByteBuffer * |
| FileChannelImpl::mapImpl (jchar mmode, jlong position, jint size) |
| { |
| #if defined(HAVE_MMAP) |
| int prot, flags; |
| if (mmode == 'r') |
| { |
| prot = PROT_READ; |
| flags = MAP_PRIVATE; |
| } |
| else |
| { |
| prot = PROT_READ|PROT_WRITE; |
| flags = mmode == '+' ? MAP_SHARED : MAP_PRIVATE; |
| } |
| jint page_size = ::getpagesize(); |
| jint offset = position & ~(page_size-1); |
| jint align = position - offset; |
| void* ptr = ::mmap(NULL, size + align, prot, flags, fd, offset); |
| MappedByteBufferImpl *buf |
| = new MappedByteBufferImpl ((RawData *) ((char *) ptr + align), |
| size, mmode == 'r'); |
| if (ptr == (void *) MAP_FAILED) |
| throw new IOException (JvNewStringLatin1 (strerror (errno))); |
| buf->implPtr = reinterpret_cast<RawData*> (ptr); |
| buf->implLen = size+align; |
| return buf; |
| #else |
| throw new IOException (JvNewStringUTF ("mmap not implemented")); |
| #endif |
| } |
| |
| void |
| MappedByteBufferImpl::unmapImpl () |
| { |
| #if defined(HAVE_MMAP) |
| munmap_adaptor(munmap, implPtr, implLen); |
| #endif |
| } |
| |
| void |
| MappedByteBufferImpl::loadImpl () |
| { |
| } |
| |
| jboolean |
| MappedByteBufferImpl::isLoadedImpl () |
| { |
| return true; |
| } |
| |
| void |
| MappedByteBufferImpl::forceImpl () |
| { |
| #if defined(HAVE_MMAP) |
| ::msync_adaptor(msync, implPtr, implLen, MS_SYNC); |
| #endif |
| } |