simple symlinks
Some checks failed
Server / build-dhfs (push) Failing after 6m43s
Server / build-webui (push) Successful in 2m25s
Server / publish-docker (push) Has been skipped
Server / publish-run-wrapper (push) Has been skipped

This commit is contained in:
2024-07-12 17:00:26 +02:00
parent 84a228b52e
commit bb8b10b3d2
7 changed files with 131 additions and 9 deletions

View File

@@ -92,6 +92,7 @@ public class FileConflictResolver implements ConflictResolver {
boolean wasChanged = oursFile.getMtime() != first.getMtime()
|| oursFile.getCtime() != first.getCtime()
|| first.isSymlink() != second.isSymlink()
|| chunksDiff;
if (m.getBestVersion() > newChangelog.values().stream().reduce(0L, Long::sum))
@@ -127,7 +128,7 @@ public class FileConflictResolver implements ConflictResolver {
oursFile.setMtime(first.getMtime());
oursFile.setCtime(first.getCtime());
var newFile = new File(UUID.randomUUID(), second.getMode(), oursDir.getUuid());
var newFile = new File(UUID.randomUUID(), second.getMode(), oursDir.getUuid(), second.isSymlink());
newFile.setMtime(second.getMtime());
newFile.setCtime(second.getCtime());

View File

@@ -18,10 +18,13 @@ public class File extends FsNode {
private final NavigableMap<Long, String> _chunks = new TreeMap<>();
@Getter
private final UUID _parent;
@Getter
private final boolean _symlink;
public File(UUID uuid, long mode, UUID parent) {
public File(UUID uuid, long mode, UUID parent, boolean symlink) {
super(uuid, mode);
_parent = parent;
_symlink = symlink;
}
@Override

View File

@@ -33,4 +33,9 @@ public interface DhfsFileService {
Long write(String fileUuid, long offset, byte[] data);
Boolean truncate(String fileUuid, long length);
String readlink(String uuid);
ByteString readlinkBS(String uuid);
String symlink(String oldpath, String newpath);
}

View File

@@ -17,6 +17,7 @@ import jakarta.inject.Inject;
import org.apache.commons.lang3.NotImplementedException;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
@@ -140,7 +141,7 @@ public class DhfsFileServiceImpl implements DhfsFileService {
var fuuid = UUID.randomUUID();
Log.trace("Creating file " + fuuid);
File f = new File(fuuid, mode, UUID.fromString(parent.getName()));
File f = new File(fuuid, mode, UUID.fromString(parent.getName()), false);
if (!parent.runWriteLocked(JObject.ResolutionStrategy.REMOTE, (m, d, bump, invalidate) -> {
if (!(d instanceof Directory dir))
@@ -151,10 +152,13 @@ public class DhfsFileServiceImpl implements DhfsFileService {
bump.apply();
boolean created = dir.putKid(fname, fuuid);
if (!created) return false;
jObjectManager.put(f, Optional.of(dir.getName()));
dir.setMtime(System.currentTimeMillis());
return dir.putKid(fname, fuuid);
return true;
}))
return Optional.empty();
@@ -270,7 +274,7 @@ public class DhfsFileServiceImpl implements DhfsFileService {
if (theFile.getData() instanceof Directory d) {
newDent = d;
} else if (theFile.getData() instanceof File f) {
var newFile = new File(UUID.randomUUID(), f.getMode(), UUID.fromString(dentTo.getName()));
var newFile = new File(UUID.randomUUID(), f.getMode(), UUID.fromString(dentTo.getName()), f.isSymlink());
newFile.setMtime(f.getMtime());
newFile.setCtime(f.getCtime());
newFile.getChunks().putAll(f.getChunks());
@@ -752,6 +756,65 @@ public class DhfsFileServiceImpl implements DhfsFileService {
return true;
}
@Override
public String readlink(String uuid) {
return readlinkBS(uuid).toStringUtf8();
}
@Override
public ByteString readlinkBS(String uuid) {
var fileOpt = jObjectManager.get(uuid).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to readlink: " + uuid)));
return fileOpt.runReadLocked(JObject.ResolutionStrategy.REMOTE, (md, fileData) -> {
if (!(fileData instanceof File)) {
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
}
if (!((File) fileData).isSymlink())
throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Not a symlink: " + uuid));
return read(uuid, 0, Math.toIntExact(size(uuid))).get();
});
}
@Override
public String symlink(String oldpath, String newpath) {
var parent = traverse(getRoot(), Path.of(newpath).getParent());
String fname = Path.of(newpath).getFileName().toString();
var fuuid = UUID.randomUUID();
Log.trace("Creating file " + fuuid);
File f = new File(fuuid, 0, UUID.fromString(parent.getName()), true);
ChunkData newChunkData = createChunk(UnsafeByteOperations.unsafeWrap(oldpath.getBytes(StandardCharsets.UTF_8)));
ChunkInfo newChunkInfo = new ChunkInfo(newChunkData.getHash(), newChunkData.getBytes().size());
f.getChunks().put(0L, newChunkInfo.getHash());
if (!parent.runWriteLocked(JObject.ResolutionStrategy.REMOTE, (m, d, bump, invalidate) -> {
if (!(d instanceof Directory dir))
return false;
if (dir.getKid(fname).isPresent())
return false;
bump.apply();
boolean created = dir.putKid(fname, fuuid);
if (!created) return false;
jObjectManager.put(newChunkData, Optional.of(newChunkInfo.getName()));
jObjectManager.put(newChunkInfo, Optional.of(f.getName()));
jObjectManager.put(f, Optional.of(dir.getName()));
dir.setMtime(System.currentTimeMillis());
return true;
})) return null;
return f.getName();
}
@Override
public Boolean setTimes(String fileUuid, long atimeMs, long mtimeMs) {
var file = jObjectManager.get(fileUuid).orElseThrow(

View File

@@ -28,8 +28,7 @@ import ru.serce.jnrfuse.struct.Timespec;
import java.nio.file.Paths;
import java.util.Optional;
import static jnr.posix.FileStat.S_IFDIR;
import static jnr.posix.FileStat.S_IFREG;
import static jnr.posix.FileStat.*;
@ApplicationScoped
public class DhfsFuse extends FuseStubFS {
@@ -92,7 +91,10 @@ public class DhfsFuse extends FuseStubFS {
return -ErrorCodes.ENOENT();
}
if (found.get() instanceof File f) {
stat.st_mode.set(S_IFREG | f.getMode());
if (f.isSymlink())
stat.st_mode.set(S_IFLNK | 0777); // FIXME?
else
stat.st_mode.set(S_IFREG | f.getMode());
stat.st_nlink.set(1);
stat.st_size.set(fileService.size(uuid));
} else if (found.get() instanceof Directory d) {
@@ -295,4 +297,34 @@ public class DhfsFuse extends FuseStubFS {
return -ErrorCodes.EIO();
}
}
@Override
public int readlink(String path, Pointer buf, long size) {
if (size < 0) return -ErrorCodes.EINVAL();
try {
var fileOpt = fileService.open(path);
if (fileOpt.isEmpty()) return -ErrorCodes.ENOENT();
var file = fileOpt.get();
var read = fileService.readlinkBS(fileOpt.get());
if (read.isEmpty()) return 0;
UnsafeByteOperations.unsafeWriteTo(read, new JnrPtrByteOutput(buf, size));
buf.putByte(Math.min(size - 1, read.size()), (byte) 0);
return 0;
} catch (Exception e) {
Log.error("When reading " + path, e);
return -ErrorCodes.EIO();
}
}
@Override
public int symlink(String oldpath, String newpath) {
try {
var ret = fileService.symlink(oldpath, newpath);
if (ret == null) return -ErrorCodes.EEXIST();
else return 0;
} catch (Exception e) {
Log.error("When creating " + newpath, e);
return -ErrorCodes.EIO();
}
}
}

View File

@@ -64,7 +64,7 @@ public class DhfsFileServiceSimpleTestImpl {
ChunkInfo c2i = new ChunkInfo(c2.getHash(), c2.getBytes().size());
ChunkData c3 = new ChunkData(ByteString.copyFrom("91011".getBytes()));
ChunkInfo c3i = new ChunkInfo(c3.getHash(), c3.getBytes().size());
File f = new File(fuuid, 777, null);
File f = new File(fuuid, 777, null, false);
f.getChunks().put(0L, c1.getHash());
f.getChunks().put((long) c1.getBytes().size(), c2.getHash());
f.getChunks().put((long) c1.getBytes().size() + c2.getBytes().size(), c3.getHash());

View File

@@ -32,4 +32,22 @@ public class DhfsFuseTest {
Assertions.assertDoesNotThrow(() -> Files.readAllBytes(testPath));
Assertions.assertArrayEquals(Files.readAllBytes(testPath), testString);
}
@Test
void symlinkTest() throws IOException, InterruptedException {
byte[] testString = "symlinkedfile".getBytes();
Path testPath = Path.of(root).resolve("symlinktarget");
Path testSymlink = Path.of(root).resolve("symlinktest");
Assertions.assertDoesNotThrow(() -> Files.createFile(testPath));
Assertions.assertDoesNotThrow(() -> Files.write(testPath, testString));
Assertions.assertDoesNotThrow(() -> Files.readAllBytes(testPath));
Assertions.assertArrayEquals(Files.readAllBytes(testPath), testString);
Assertions.assertDoesNotThrow(() -> Files.createSymbolicLink(testSymlink, testPath));
Assertions.assertTrue(() -> Files.isSymbolicLink(testSymlink));
Assertions.assertEquals(testPath, Files.readSymbolicLink(testSymlink));
Assertions.assertDoesNotThrow(() -> Files.readAllBytes(testSymlink));
Assertions.assertArrayEquals(Files.readAllBytes(testSymlink), testString);
}
}