io.daos.dfs.DaosFile Maven / Gradle / Ivy
/*
* (C) Copyright 2018-2021 Intel Corporation.
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
package io.daos.dfs;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.annotation.concurrent.NotThreadSafe;
import io.daos.*;
import io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.nio.ch.DirectBuffer;
/**
* This class mimics {@link java.io.File} class to represent DAOS FS object and provide similar methods, like
* {@link #delete()}, {@link #createNewFile()}, {@link #exists()}, {@link #length()}. The creation of this object
* doesn't involve any remote operation which is delayed and invoked on-demand.
*
*
* For each instance of {@link DaosFile}, its parent path, file name and path (parent path + / + file name) are
* generated for later DAOS access convenience.
*
* @see DaosFsClient
* @see Cleaner
*/
@NotThreadSafe
public class DaosFile {
private final String path;
private final String name;
private final DaosFsClient client;
private final long dfsPtr;
private final String parentPath;
private int accessFlags;
private int mode;
private DaosObjectType objectType;
private int chunkSize;
private DaosFile parent;
private Cleaner cleaner;
private long objId;
private volatile boolean cleaned;
private static final Logger log = LoggerFactory.getLogger(DaosFile.class);
protected DaosFile(String parentPath, String path, int accessFlags, DaosFsClient daosFsClient) {
String pnor = DaosUtils.normalize(parentPath);
String nor = DaosUtils.normalize(pnor.length() == 0 ? path : pnor + "/" + path);
if (nor == null || nor.length() == 0) {
throw new IllegalArgumentException("invalid path after normalizing " + nor);
}
this.path = nor;
int slash = nor.lastIndexOf('/');
if (slash > 0) {
this.parentPath = nor.substring(0, slash);
this.name = nor.substring(slash + 1);
} else if (slash < 0) {
this.parentPath = "";
this.name = nor;
} else {
if (nor.length() == 1) {
this.parentPath = "";
this.name = "/";
} else {
this.parentPath = "/";
this.name = nor.substring(1);
}
}
this.accessFlags = accessFlags;
this.client = daosFsClient;
if (this.client != null) {
this.dfsPtr = daosFsClient.getDfsPtr();
this.mode = client.getDefaultFileMode();
this.objectType = client.getDefaultFileObjType();
this.chunkSize = client.getDefaultFileChunkSize();
} else { //no client, could be for test purpose
this.dfsPtr = -1;
}
}
protected DaosFile(DaosFile parent, String path, int accessFlags, DaosFsClient daosFsClient) {
this(parent.path, path, accessFlags, daosFsClient);
this.parent = parent;
}
protected DaosFile(String path, int accessFlags, DaosFsClient daosFsClient) {
this((String) null, path, accessFlags, daosFsClient);
}
/**
* create new file with default mode, object type and chunk size.
* Parent directory should exist.
*
* @throws IOException
* {@link DaosIOException}
*/
public void createNewFile() throws IOException {
createNewFile(false);
}
/**
* create new file with default mode, object type and chunk size.
* This operation will fail if parent directory doesn't exist and createParent
is false.
*
* @param createParent
* create directory if parent doesn't exist
* @throws IOException
* {@link DaosIOException}
*/
public void createNewFile(boolean createParent) throws IOException {
createNewFile(mode, objectType, chunkSize, createParent);
}
/**
* create new file with mode, object type and chunk size as well as createParent.
*
* @param mode
* should be octal number, like 0775
* @param objectType
* object type, see {@link io.daos.dfs.DaosFsClient.DaosFsClientBuilder#defaultFileType}
* @param chunkSize
* file chunk size
* @param createParent
* create directory if parent doesn't exist
* @throws IOException
* {@link DaosIOException}
*/
public void createNewFile(int mode, DaosObjectType objectType, int chunkSize, boolean createParent)
throws IOException {
if (objId != 0) {
throw new IOException("file existed already");
}
// parse path to get parent and name.
// dfs lookup parent and then dfs open
objId = client.createNewFile(dfsPtr, parentPath, name, mode, accessFlags, objectType.nameWithoutOc(), chunkSize,
createParent);
createCleaner();
}
/**
* open FS object if hasn't opened yet.
*
*
* cleaner is created only open at the first time
*
* @param throwException
* throw exception if true, otherwise, keep exception and return immediately
* @throws DaosIOException
* DaosIOException
*/
private void open(boolean throwException) throws DaosIOException {
if (objId != 0) {
return;
}
try {
if (parent != null && parent.isOpen()) {
objId = client.dfsLookup(dfsPtr, parent.getObjId(), name, accessFlags, -1);
} else {
objId = client.dfsLookup(dfsPtr, path, accessFlags, -1);
}
} catch (Exception e) {
if (!(e instanceof DaosIOException)) { //unexpected exception
throw new DaosIOException(e);
}
if (throwException) {
throw (DaosIOException) e;
} else { //verify error code to determine existence, if it's other error code, throw it anyway.
DaosIOException de = (DaosIOException) e;
if (de.getErrorCode() != Constants.ERROR_CODE_NOT_EXIST) {
throw de;
}
}
}
if (isOpen()) {
createCleaner();
}
}
/**
* check if file is opened from Daos.
*
* @return true if file is opened, false otherwise
*/
public boolean isOpen() {
return objId != 0;
}
/**
* create cleaner for each opened {@link DaosFile} object. Cleaner calls {@link DaosFsClient#dfsRelease(long)}
* to release opened FS object.
*
*
* If object is deleted in advance, no {@link DaosFsClient#dfsRelease(long)} will be called.
*/
private void createCleaner() {
if (cleaner != null) {
throw new IllegalStateException("Cleaner created already");
}
cleaned = false;
//clean object by invoking dfs release
cleaner = Cleaner.create(this, () -> {
if (!cleaned) {
try {
client.dfsRelease(objId);
if (log.isDebugEnabled()) {
log.debug("file {} released", path);
}
} catch (IOException e) {
log.error("failed to release fs object with " + path, e);
}
}
});
}
/**
* delete FS object.
*
* @throws IOException
* {@link DaosIOException}
*/
public boolean delete() throws IOException {
return delete(false);
}
/**
* delete FS object. Non-empty directory will be deleted if
* force
set to true.
*
* @param force
* force deletion if directory is not empty
* @return true if FS object is deleted. False otherwise
* @throws IOException
* {@link DaosIOException}
*/
public boolean delete(boolean force) throws IOException {
boolean deleted = client.delete(dfsPtr, parentPath, name, force);
if (deleted) {
deleted();
}
return deleted;
}
/**
* length of FS object.
*
* @return length in bytes
* @throws IOException
* {@link DaosIOException}
*/
public long length() throws IOException {
open(true);
long size = client.dfsGetSize(dfsPtr, objId);
return size;
}
/**
* list all children of this directory.
*
* @return String array of file name. Empty string array is returned for empty directory
* @throws IOException
* {@link DaosIOException}
*/
public String[] listChildren() throws IOException {
open(true);
//no limit to max returned entries for now
String children = client.dfsReadDir(dfsPtr, objId, -1);
return (children == null || (children = children.trim()).length() == 0) ?
new String[]{} : children.split(",");
}
/**
* set extended attribute.
*
* @param name
* attribute name
* @param value
* attribute value
* @param flags should be one of below value
* {@link Constants#SET_XATTRIBUTE_NO_CHECK} : no attribute name check
* {@link Constants#SET_XATTRIBUTE_CREATE} : create or fail if attribute exits
* {@link Constants#SET_XATTRIBUTE_REPLACE} : replace or fail if attribute doesn't exist
* @throws IOException
* {@link DaosIOException}
*/
public void setExtAttribute(String name, String value, int flags) throws IOException {
open(true);
client.dfsSetExtAttr(dfsPtr, objId, name, value, flags);
}
/**
* get extended attribute.
*
* @param name
* attribute name
* @param expectedValueLen expected value length. Make sure you give enough length so that actual value
* is not truncated.
* @return value in string value may be truncated if parameter expectedValueLen
is less than
actual value length
* @throws IOException
* {@link DaosIOException}
*/
public String getExtAttribute(String name, int expectedValueLen) throws IOException {
open(true);
return client.dfsGetExtAttr(dfsPtr, objId, name, expectedValueLen);
}
/**
* remove extended attribute.
*
* @param name
* attribute name
* @throws IOException
* {@link DaosIOException}
*/
public void remoteExtAttribute(String name) throws IOException {
open(true);
client.dfsRemoveExtAttr(dfsPtr, objId, name);
}
/**
* get chunk size of this file.
*
* @throws IOException
* {@link DaosIOException}
*/
public long getChunkSize() throws IOException {
open(true);
return client.dfsGetChunkSize(objId);
}
/**
* create DFS description.
*
* @param dataBuffer
* dataBuffer to hold data for DFS read/write
* @param eq
* DaosEventQueue
* @return IODfsDesc
*/
public static IODfsDesc createDfsDesc(ByteBuf dataBuffer, DaosEventQueue eq) {
return new IODfsDesc(dataBuffer, eq);
}
/**
* read len
of data from file at fileOffset
to buffer
starting from
* bufferOffset
.
*
*
* Be note, caller should set buffer
indices, like position, limit or marker, by itself based on
* return value of this method.
*
* @param buffer Must be direct buffer
* @param bufferOffset buffer offset
* @param fileOffset file offset
* @param len expected length in bytes read from file to buffer
* @return actual read bytes
* @throws IOException
* {@link DaosIOException}
*/
public long read(ByteBuf buffer, long bufferOffset, long fileOffset, long len) throws IOException {
open(true);
//no asynchronous for now
if (len > buffer.capacity() - bufferOffset) {
throw new IOException(String.format("buffer (%d) has no enough space start at %d for reading %d " +
"bytes from file",
buffer.capacity(), bufferOffset, len));
}
return client.dfsRead(dfsPtr, objId, (buffer).memoryAddress() + bufferOffset,
fileOffset, len);
}
public void readAsync(IODfsDesc desc, long offset, long len) throws IOException {
open(true);
desc.encode(offset, len);
if (log.isDebugEnabled()) {
log.debug("read file with description: " + desc);
}
client.dfsReadAsync(dfsPtr, objId, desc.getDescBuffer().memoryAddress());
}
/**
* write len
bytes to file starting at fileOffset
from buffer
at
* bufferOffset
.
*
* @param buffer Must be direct buffer
* @param bufferOffset buffer offset
* @param fileOffset file offset
* @param len length in bytes of data to write
* @return it's same as the parameter len
since underlying DAOS FS doesn't give length of actual
written data
* @throws IOException
* {@link DaosIOException}
*/
public long write(ByteBuf buffer, long bufferOffset, long fileOffset, long len) throws IOException {
open(true);
//no asynchronous for now
if (len > buffer.capacity() - bufferOffset) {
throw new IOException(String.format("buffer (%d) has no enough data start at %d for write %d bytes to file",
buffer.capacity(), bufferOffset, len));
}
return client.dfsWrite(dfsPtr, objId, buffer.memoryAddress() + bufferOffset,
fileOffset, len);
}
public void writeAsync(IODfsDesc desc, long offset, long len) throws IOException {
open(true);
desc.encode(offset, len);
if (log.isDebugEnabled()) {
log.debug("write file with description: " + desc);
}
client.dfsWriteAsync(dfsPtr, objId, desc.getDescBuffer().memoryAddress());
}
/**
* create directory.
*
* @throws IOException
* {@link DaosIOException}
*/
public void mkdir() throws IOException {
mkdir(mode);
}
/**
* create directory with file mode
specified. It should be octal value like 0755.
*
* @param mode
* directory mode
* @throws IOException
* {@link DaosIOException}
*/
public void mkdir(int mode) throws IOException {
client.mkdir(path, mode, false);
}
/**
* create this directory or all its ancestors if they are not existed.
*
* @throws IOException
* {@link DaosIOException}
*/
public void mkdirs() throws IOException {
mkdirs(mode);
}
/**
* same as {@link #mkdirs()} with file mode
specified.
*
*
* check {@link #mkdir(int)} for mode
*
* @param mode
* directory mode
* @throws IOException
* {@link DaosIOException}
*/
public void mkdirs(int mode) throws IOException {
client.mkdir(path, mode, true);
}
/**
* check existence of file.
*
* @return true if file exists. false otherwise
* @throws IOException
* {@link DaosIOException}
*/
public boolean exists() throws IOException {
open(false);
if (!this.isOpen()) {
return false;
}
try {
getStatAttributes(false);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("not exists", e);
}
if (!(e instanceof DaosIOException)) {
throw new DaosIOException(e);
}
DaosIOException de = (DaosIOException) e;
if (de.getErrorCode() != Constants.ERROR_CODE_NOT_EXIST) {
throw de;
}
return false;
}
return true;
}
/**
* rename this file to another file denoted by destPath
.
*
* @param destPath
* destination path
* @return new DaosFile denoted by destPath
* @throws IOException
* {@link DaosIOException}
*/
public DaosFile rename(String destPath) throws IOException {
destPath = DaosUtils.normalize(destPath);
if (path.equals(destPath)) {
return this;
}
client.move(path, destPath);
deleted();
return new DaosFile(destPath, accessFlags, client);
}
private void deleted() {
if (cleaner != null) {
cleaned = true;
cleaner = null;
}
objId = 0;
}
/**
* check if this file is a directory.
*
* @return true if it's directory. false otherwise
* @throws IOException
* {@link DaosIOException}
*/
public boolean isDirectory() throws IOException {
return client.dfsIsDirectory(getMode());
}
/**
* release DAOS FS object actively.
*/
public void release() {
if (cleaner != null) {
cleaner.clean();
cleaned = true;
}
}
/**
* get mode of this file.
*
* @return file mode
* @throws IOException
* {@link DaosIOException}
*/
public int getMode() throws IOException {
open(true);
return client.dfsGetMode(objId);
}
/**
* get stat attributes of this file. It's also used for checking existence of opened FS object.
*
* @param retrieve true if you want to retrieve attributes info back. false if you want to just check file existence.
* @return StatAttributes
* @throws IOException
* {@link DaosIOException}
* @see StatAttributes
*/
StatAttributes getStatAttributes(boolean retrieve) throws IOException {
open(true);
ByteBuffer buffer = null;
if (retrieve) {
buffer = BufferAllocator.directBuffer(StatAttributes.objectSize());
}
client.dfsOpenedObjStat(dfsPtr, objId, buffer == null ? -1 : ((DirectBuffer) buffer).address());
return buffer == null ? null : new StatAttributes(buffer);
}
/**
* retrieve stat attributes of this file.
*
* @return StatAttributes
* @throws IOException
* {@link DaosIOException}
* @see StatAttributes
*/
public StatAttributes getStatAttributes() throws IOException {
return getStatAttributes(true);
}
/**
* parent {@link DaosFile}.
*
* @return parent file
*/
public DaosFile getParent() {
return parent;
}
/**
* parent path of this file.
*
* @return parent path
*/
public String getParentPath() {
return parentPath;
}
/**
* full path of this file.
*
* @return file path
*/
public String getPath() {
return path;
}
/**
* name part of this file.
*
* @return file name
*/
public String getName() {
return name;
}
/**
* get DAOS object id of this file.
*
* @return DAOS object id
* @throws IOException
* {@link DaosIOException}
*/
protected long getObjId() throws IOException {
open(true);
return objId;
}
@Override
public String toString() {
return path;
}
}