Rev 3408 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2006 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 03-Dec-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The internal representation of a file in the file system. In use it
* should only be referred to by the {@link ImgChannel} interface.
*
* @author Steve Ratcliffe
*/
public class FileNode
implements ImgChannel
{
private static final Logger log =
Logger.
getLogger(FileNode.
class);
private boolean open
;
private boolean writeable
;
private boolean readable
;
private final FileChannel file
;
private final BlockManager blockManager
;
private final Dirent dirent
;
// The position in this file
private long position
;
private byte xorByte
;
/**
* Creates a new file in the file system. You can treat this just like
* a regular file and write or read from it.
* Operations to two different files may not be interleaved although
* it may be possible to implement this.
*
* @param file The handle to the underlying file.
* @param dir The directory entry associated with this file.
* @param mode The mode "rw" for read and write etc.
*/
public FileNode
(FileChannel file, Dirent dir,
String mode
)
{
this.
file = file
;
this.
dirent = dir
;
if (mode.
indexOf('r') >=
0)
readable =
true;
if (mode.
indexOf('w') >=
0)
writeable =
true;
if (!(readable || writeable
))
throw new IllegalArgumentException("File must be readable or writeable");
blockManager = dir.
getBlockManager();
if (blockManager ==
null)
throw new IllegalArgumentException("no file system supplied");
open =
true;
}
/**
* Closes this channel.
* <p/>
* <p> After a channel is closed, any further attempt to invoke I/O
* operations upon it will cause a {@link ClosedChannelException} to be
* thrown.
* <p/>
* <p> If this channel is already closed then invoking this method has no
* effect.
* <p/>
* <p> This method may be invoked at any time. If some other thread has
* already invoked it, however, then another invocation will block until
* the first invocation is complete, after which it will return without
* effect. </p>
*
* @throws IOException If an I/O error occurs
*/
public void close
() throws IOException {
if (!open
)
return;
sync
();
open =
false;
readable =
false;
writeable =
false;
}
/**
* Tells whether or not this channel is open. </p>
*
* @return <tt>true</tt> if, and only if, this channel is open
*/
public boolean isOpen
() {
return open
;
}
/**
* Reads a sequence of bytes from this channel into the given buffer.
*
* @param dst The buffer into which bytes are to be transferred
*
* @return The number of bytes read, possibly zero, or <tt>-1</tt> if the
* channel has reached end-of-stream
*
* @throws NonReadableChannelException If this channel was not opened for reading
* @throws ClosedChannelException If this channel is closed
* @throws AsynchronousCloseException If another thread closes this channel
* while the read operation is in progress
* @throws ClosedByInterruptException If another thread interrupts the
* current thread while the read operation is in progress, thereby closing
* the channel and setting the current thread's interrupt status
* @throws IOException If some other I/O error occurs
*/
public int read
(ByteBuffer dst
) throws IOException {
if (!open
)
throw new ClosedChannelException();
if (!readable
)
throw new NonReadableChannelException();
int blockSize = blockManager.
getBlockSize();
long size = dst.
remaining();
long fileSize = dirent.
getSize();
if (position
>= fileSize
)
return -
1;
size =
Math.
min(size, fileSize - position
);
int totalRead =
0;
while (size
> 0) {
// Tet the logical block number, as we see it in our file.
int lblock =
(int) (position / blockSize
);
// Get the physical block number, the actual block number in
// the underlying file.
int pblock = dirent.
getPhysicalBlock(lblock
);
if (pblock == 0xffff
) {
// We are at the end of the file.
log.
debug("at eof");
break;
}
// Position the underlying file
int off =
(int) (position - lblock
*blockSize
);
file.
position((long) pblock
* blockSize + off
);
int n =
(int) size
;
if (n
> blockSize
)
n = blockSize
;
if (off
!=
0)
n =
Math.
min(n, blockSize - off
);
dst.
limit(dst.
position() + n
);
int pos = dst.
position();
int nr = file.
read(dst
);
if (nr == -
1)
return -
1;
if (nr ==
0)
throw new IOException("Read nothing");
if(xorByte
!=
0) {
byte[] bufBytes = dst.
array();
for(int i = pos + n -
1; i
>= pos
; --i
)
bufBytes
[i
] ^= xorByte
;
}
// Update the file positions
size -= nr
;
position += nr
;
totalRead += nr
;
}
log.
debug("read ret", totalRead
);
return totalRead
;
}
/**
* Writes a sequence of bytes to this channel from the given buffer.
* <p/>
* <p> An attempt is made to write up to <i>r</i> bytes to the channel,
* where <i>r</i> is the number of bytes remaining in the buffer, that is,
* <tt>dst.remaining()</tt>, at the moment this method is invoked.
* <p>The logical block has to be converted to a physical block in the
* underlying file.
*
* @param src The buffer from which bytes are to be retrieved
* @return The number of bytes written, possibly zero
* @throws NonWritableChannelException
* If this channel was not opened for writing
* @throws ClosedChannelException
* If this channel is closed
* @throws IOException If some other I/O error occurs
*/
public int write
(ByteBuffer src
) throws IOException {
if (!open
)
throw new ClosedChannelException();
int blockSize = blockManager.
getBlockSize();
// Get the size of this write
int size = src.
remaining();
// Loop over each block, this is to support the case (which we may
// not implement) of non-contiguous blocks.
int totalWritten =
0;
while (size
> 0) {
// Get the logical block, ie the block as we see it in our file.
int lblock =
(int) (position/blockSize
);
// First need to allocate enough blocks for this write. First check
// if the block exists already
int pblock = dirent.
getPhysicalBlock(lblock
);
log.
debug("lblock / pblock", lblock,
'/', pblock
);
if (pblock == 0xffff
) {
log.
debug("allocating new block");
pblock = blockManager.
allocate();
dirent.
addBlock(pblock
);
}
// Position the underlying file, so that it is in the correct place.
int off =
(int) (position - lblock
*blockSize
);
file.
position((long) pblock
* blockSize + off
);
int n = size
;
if (n
> blockSize
)
n = blockSize
;
if (off
!=
0)
n =
Math.
min(n, blockSize - off
);
src.
limit(src.
position() + n
);
// Write to the underlying file.
int nw = file.
write(src
);
if (nw ==
0)
throw new IOException("Wrote nothing");
// Update the file positions
size -= nw
;
position += nw
;
totalWritten += nw
;
// Update file size.
if (position
> dirent.
getSize())
dirent.
setSize((int) position
);
}
return totalWritten
;
}
public long position
() {
return position
;
}
public void position
(long pos
) {
int blockSize = blockManager.
getBlockSize();
while (pos
> position
) {
long lblock = position / blockSize
;
int pblock = dirent.
getPhysicalBlock((int) lblock
);
if (pblock == 0xffff
) {
if (writeable
) {
log.
debug("setting position allocating new block", lblock
);
pblock = blockManager.
allocate();
dirent.
addBlock(pblock
);
}
}
position =
(lblock+
1) * blockSize
;
}
this.
position = pos
;
}
/**
* Write out any unsaved data to disk.
*
* @throws IOException If there is an error writing to disk.
*/
private void sync
() throws IOException {
if (!writeable
)
return;
// Ensure that a complete block is written out.
int bs = blockManager.
getBlockSize();
long rem = bs -
(file.
position() % bs
);
ByteBuffer buf =
ByteBuffer.
allocate(blockManager.
getBlockSize());
// Complete any partial block.
for (int i =
0; i
< rem
; i++
)
buf.
put((byte) 0);
buf.
flip();
file.
write(buf
);
}
public void setXorByte
(byte xorByte
) {
this.
xorByte = xorByte
;
}
}