package ru.serce.jnrfuse.examples; import jnr.ffi.Pointer; import jnr.ffi.types.mode_t; import jnr.ffi.types.off_t; import jnr.ffi.types.size_t; import ru.serce.jnrfuse.ErrorCodes; import ru.serce.jnrfuse.FuseFillDir; import ru.serce.jnrfuse.FuseStubFS; import ru.serce.jnrfuse.struct.FileStat; import ru.serce.jnrfuse.struct.FuseBufvec; import ru.serce.jnrfuse.struct.FuseFileInfo; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public class MemoryFS extends FuseStubFS { private class MemoryDirectory extends MemoryPath { private List<MemoryPath> contents = new ArrayList<>(); private MemoryDirectory(String name) { super(name); } private MemoryDirectory(String name, MemoryDirectory parent) { super(name, parent); } public synchronized void add(MemoryPath p) { contents.add(p); p.parent = this; } private synchronized void deleteChild(MemoryPath child) { contents.remove(child); } @Override protected MemoryPath find(String path) { if (super.find(path) != null) { return super.find(path); } while (path.startsWith("/")) { path = path.substring(1); } synchronized (this) { if (!path.contains("/")) { for (MemoryPath p : contents) { if (p.name.equals(path)) { return p; } } return null; } String nextName = path.substring(0, path.indexOf("/")); String rest = path.substring(path.indexOf("/")); for (MemoryPath p : contents) { if (p.name.equals(nextName)) { return p.find(rest); } } } return null; } @Override protected void getattr(FileStat stat) { stat.st_mode.set(FileStat.S_IFDIR | 0777); } private synchronized void mkdir(String lastComponent) { contents.add(new MemoryDirectory(lastComponent, this)); } public synchronized void mkfile(String lastComponent) { contents.add(new MemoryFile(lastComponent, this)); } public synchronized void read(Pointer buf, FuseFillDir filler) { for (MemoryPath p : contents) { filler.apply(buf, p.name, null, 0); } } } private class MemoryFile extends MemoryPath { private ByteBuffer contents = ByteBuffer.allocate(0); private MemoryFile(String name) { super(name); } private MemoryFile(String name, MemoryDirectory parent) { super(name, parent); } public MemoryFile(String name, String text) { super(name); try { byte[] contentBytes = text.getBytes("UTF-8"); contents = ByteBuffer.wrap(contentBytes); } catch (UnsupportedEncodingException e) { // Not going to happen } } @Override protected void getattr(FileStat stat) { stat.st_mode.set(FileStat.S_IFREG | 0777); stat.st_size.set(contents.capacity()); } private int read(Pointer buffer, long size, long offset) { int bytesToRead = (int) Math.min(contents.capacity() - offset, size); byte[] bytesRead = new byte[bytesToRead]; synchronized (this) { contents.position((int) offset); contents.get(bytesRead, 0, bytesToRead); buffer.put(0, bytesRead, 0, bytesToRead); contents.position(0); // Rewind } return bytesToRead; } private synchronized void truncate(long size) { if (size < contents.capacity()) { // Need to create a new, smaller buffer ByteBuffer newContents = ByteBuffer.allocate((int) size); byte[] bytesRead = new byte[(int) size]; contents.get(bytesRead); newContents.put(bytesRead); contents = newContents; } } private int write(Pointer buffer, long bufSize, long writeOffset) { int maxWriteIndex = (int) (writeOffset + bufSize); byte[] bytesToWrite = new byte[(int) bufSize]; synchronized (this) { if (maxWriteIndex > contents.capacity()) { // Need to create a new, larger buffer ByteBuffer newContents = ByteBuffer.allocate(maxWriteIndex); newContents.put(contents); contents = newContents; } buffer.get(0, bytesToWrite, 0, (int) bufSize); contents.position((int) writeOffset); contents.put(bytesToWrite); contents.position(0); // Rewind } return (int) bufSize; } } private abstract class MemoryPath { private String name; private MemoryDirectory parent; private MemoryPath(String name) { this(name, null); } private MemoryPath(String name, MemoryDirectory parent) { this.name = name; this.parent = parent; } private synchronized void delete() { if (parent != null) { parent.deleteChild(this); parent = null; } } protected MemoryPath find(String path) { while (path.startsWith("/")) { path = path.substring(1); } if (path.equals(name) || path.isEmpty()) { return this; } return null; } protected abstract void getattr(FileStat stat); private void rename(String newName) { while (newName.startsWith("/")) { newName = newName.substring(1); } name = newName; } } public static void main(String[] args) { MemoryFS memfs = new MemoryFS(); try { memfs.mount(Paths.get("/tmp/mnttt"), true); } finally { memfs.umount(); } } private MemoryDirectory rootDirectory = new MemoryDirectory(""); public MemoryFS() { // Sprinkle some files around rootDirectory.add(new MemoryFile("Sample file.txt", "Hello there, feel free to look around.\n")); rootDirectory.add(new MemoryDirectory("Sample directory")); MemoryDirectory dirWithFiles = new MemoryDirectory("Directory with files"); rootDirectory.add(dirWithFiles); dirWithFiles.add(new MemoryFile("hello.txt", "This is some sample text.\n")); dirWithFiles.add(new MemoryFile("hello again.txt", "This another file with text in it! Oh my!\n")); MemoryDirectory nestedDirectory = new MemoryDirectory("Sample nested directory"); dirWithFiles.add(nestedDirectory); nestedDirectory.add(new MemoryFile("So deep.txt", "Man, I'm like, so deep in this here file structure.\n")); } @Override public int create(String path, @mode_t long mode, FuseFileInfo fi) { if (getPath(path) != null) { return -ErrorCodes.EEXIST(); } MemoryPath parent = getParentPath(path); if (parent instanceof MemoryDirectory) { ((MemoryDirectory) parent).mkfile(getLastComponent(path)); return 0; } return -ErrorCodes.ENOENT(); } @Override public int getattr(String path, FileStat stat) { MemoryPath p = getPath(path); if (p != null) { p.getattr(stat); return 0; } return -ErrorCodes.ENOENT(); } private String getLastComponent(String path) { while (path.substring(path.length() - 1).equals("/")) { path = path.substring(0, path.length() - 1); } if (path.isEmpty()) { return ""; } return path.substring(path.lastIndexOf("/") + 1); } private MemoryPath getParentPath(String path) { return rootDirectory.find(path.substring(0, path.lastIndexOf("/"))); } private MemoryPath getPath(String path) { return rootDirectory.find(path); } @Override public int mkdir(String path, @mode_t long mode) { if (getPath(path) != null) { return -ErrorCodes.EEXIST(); } MemoryPath parent = getParentPath(path); if (parent instanceof MemoryDirectory) { ((MemoryDirectory) parent).mkdir(getLastComponent(path)); return 0; } return -ErrorCodes.ENOENT(); } @Override public int read(String path, Pointer buf, @size_t long size, @off_t long offset, FuseFileInfo fi) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } if (!(p instanceof MemoryFile)) { return -ErrorCodes.EISDIR(); } return ((MemoryFile) p).read(buf, size, offset); } @Override public int readdir(String path, Pointer buf, FuseFillDir filter, @off_t long offset, FuseFileInfo fi) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } if (!(p instanceof MemoryDirectory)) { return -ErrorCodes.ENOTDIR(); } filter.apply(buf, ".", null, 0); filter.apply(buf, "..", null, 0); ((MemoryDirectory) p).read(buf, filter); return 0; } @Override public int rename(String path, String newName) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } MemoryPath newParent = getParentPath(newName); if (newParent == null) { return -ErrorCodes.ENOENT(); } if (!(newParent instanceof MemoryDirectory)) { return -ErrorCodes.ENOTDIR(); } p.delete(); p.rename(newName.substring(newName.lastIndexOf("/"))); ((MemoryDirectory) newParent).add(p); return 0; } @Override public int rmdir(String path) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } if (!(p instanceof MemoryDirectory)) { return -ErrorCodes.ENOTDIR(); } p.delete(); return 0; } @Override public int truncate(String path, long offset) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } if (!(p instanceof MemoryFile)) { return -ErrorCodes.EISDIR(); } ((MemoryFile) p).truncate(offset); return 0; } @Override public int unlink(String path) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } p.delete(); return 0; } @Override public int write(String path, Pointer buf, @size_t long size, @off_t long offset, FuseFileInfo fi) { MemoryPath p = getPath(path); if (p == null) { return -ErrorCodes.ENOENT(); } if (!(p instanceof MemoryFile)) { return -ErrorCodes.EISDIR(); } return ((MemoryFile) p).write(buf, size, offset); } }