Skip to content

Commit 0c757e4

Browse files
committed
Initial work supporting permissions sync
1 parent 5d89eca commit 0c757e4

7 files changed

Lines changed: 202 additions & 11 deletions

File tree

jsync-sftp/src/main/java/com/fizzed/jsync/sftp/SftpVirtualFileSystem.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ private VirtualPath toVirtualPathWithStats(VirtualPath path, SftpATTRS attrs) th
202202
final long size = attrs.getSize();
203203
final long modifiedTime = attrs.getMTime() * 1000L;
204204
final long accessedTime = attrs.getATime() * 1000L;
205+
final int perms = attrs.getPermissions();
205206

206207
final VirtualFileType type;
207208
if (attrs.isDir()) {
@@ -214,7 +215,7 @@ private VirtualPath toVirtualPathWithStats(VirtualPath path, SftpATTRS attrs) th
214215
type = VirtualFileType.OTHER;
215216
}
216217

217-
final VirtualFileStat stat = new VirtualFileStat(type, size, modifiedTime, accessedTime);
218+
final VirtualFileStat stat = new VirtualFileStat(type, size, modifiedTime, accessedTime, perms);
218219

219220
return new VirtualPath(path.getParentPath(), path.getName(), type == VirtualFileType.DIR, stat);
220221
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.fizzed.jsync.sftp;
2+
3+
import com.fizzed.jsync.vfs.VirtualPath;
4+
import com.fizzed.jsync.vfs.VirtualVolume;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.nio.file.Paths;
9+
10+
import static com.fizzed.jsync.sftp.SftpVirtualVolume.sftpVolume;
11+
import static com.fizzed.jsync.vfs.LocalVirtualVolume.localVolume;
12+
13+
public class SftpVirtualFileSystemDemo {
14+
static private final Logger log = LoggerFactory.getLogger(SftpVirtualFileSystemDemo.class);
15+
16+
static public void main(String[] args) throws Exception {
17+
18+
// final SftpVirtualFileSystem vfs = SftpVirtualFileSystem.open("bmh-dev-x64-indy25-1");
19+
final SftpVirtualFileSystem vfs = SftpVirtualFileSystem.open("bmh-build-x64-win11-1");
20+
21+
// final VirtualPath stat = vfs.stat(VirtualPath.parse(".ssh/authorized_keys"));
22+
final VirtualPath stat = vfs.stat(VirtualPath.parse("remote-build"));
23+
24+
log.info("file: {}, perms={}", stat, stat.getStat().getPermissionsOctal());
25+
}
26+
27+
}

jsync-vfs/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
</dependency>
2020

2121
<!-- testing -->
22-
22+
23+
<dependency>
24+
<groupId>com.fizzed</groupId>
25+
<artifactId>crux-util</artifactId>
26+
<scope>test</scope>
27+
</dependency>
28+
2329
<dependency>
2430
<groupId>org.junit.jupiter</groupId>
2531
<artifactId>junit-jupiter</artifactId>

jsync-vfs/src/main/java/com/fizzed/jsync/vfs/LocalVirtualFileSystem.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.fizzed.jsync.vfs;
22

33
import com.fizzed.jsync.vfs.util.Checksums;
4+
import com.fizzed.jsync.vfs.util.Permissions;
45
import org.slf4j.Logger;
56
import org.slf4j.LoggerFactory;
67

@@ -11,6 +12,7 @@
1112
import java.nio.file.attribute.BasicFileAttributeView;
1213
import java.nio.file.attribute.BasicFileAttributes;
1314
import java.nio.file.attribute.FileTime;
15+
import java.nio.file.attribute.PosixFileAttributes;
1416
import java.util.ArrayList;
1517
import java.util.Iterator;
1618
import java.util.List;
@@ -19,8 +21,11 @@
1921
public class LocalVirtualFileSystem extends AbstractVirtualFileSystem {
2022
static private final Logger log = LoggerFactory.getLogger(LocalVirtualFileSystem.class);
2123

22-
public LocalVirtualFileSystem(String name, VirtualPath pwd, boolean caseSensitive) {
24+
private final boolean posix;
25+
26+
public LocalVirtualFileSystem(String name, VirtualPath pwd, boolean caseSensitive, boolean posix) {
2327
super(name, pwd, caseSensitive);
28+
this.posix = posix;
2429
}
2530

2631
static public LocalVirtualFileSystem open() {
@@ -37,21 +42,29 @@ static public LocalVirtualFileSystem open(Path workingDir) {
3742

3843
final VirtualPath pwd = VirtualPath.parse(currentWorkingDir.toString(), true);
3944

40-
log.debug("Detected filesystem {} has pwd {}", name, pwd);
45+
final boolean isPosixAttributes = FileSystems.getDefault()
46+
.supportedFileAttributeViews()
47+
.contains("posix");
48+
49+
log.debug("Detected filesystem {} has pwd {}, posixAttrs {}", name, pwd, isPosixAttributes);
4150

4251
// everything is case-sensitive except windows
4352
final boolean caseSensitive = !System.getProperty("os.name").toLowerCase().contains("windows");
4453

4554
log.debug("Detected filesystem {} is case-sensitive={}", name, caseSensitive);
4655

47-
return new LocalVirtualFileSystem(name, pwd, caseSensitive);
56+
return new LocalVirtualFileSystem(name, pwd, caseSensitive, isPosixAttributes);
4857
}
4958

5059
@Override
5160
public void close() throws Exception {
5261
// nothing to do
5362
}
5463

64+
public boolean isPosix() {
65+
return this.posix;
66+
}
67+
5568
protected Path toNativePath(VirtualPath path) {
5669
return Paths.get(path.toString());
5770
}
@@ -60,10 +73,17 @@ protected VirtualPath toVirtualPathWithStat(VirtualPath path) throws IOException
6073
final Path nativePath = this.toNativePath(path);
6174

6275
// Fetch all attributes in ONE operation (and don't follow symlinks, we need to know the type)
63-
final BasicFileAttributes attrs = Files.readAttributes(nativePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
64-
// TODO: if we're on posix, we can also do this
65-
// fetches size, times, PLUS owner, group, and permissions
66-
// PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class);
76+
final PosixFileAttributes posixAttrs;
77+
final BasicFileAttributes attrs;
78+
if (this.posix) {
79+
posixAttrs = Files.readAttributes(nativePath, PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
80+
attrs = posixAttrs;
81+
} else {
82+
posixAttrs = null;
83+
attrs = Files.readAttributes(nativePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
84+
}
85+
86+
// basic attributes get us much of what we need
6787
final long size = attrs.size();
6888
final long modifiedTime = attrs.lastModifiedTime().toMillis();
6989
final long accessedTime = attrs.lastAccessTime().toMillis();
@@ -84,7 +104,13 @@ protected VirtualPath toVirtualPathWithStat(VirtualPath path) throws IOException
84104
type = VirtualFileType.OTHER;
85105
}
86106

87-
final VirtualFileStat stat = new VirtualFileStat(type, size, modifiedTime, accessedTime);
107+
// permissions are a tad trickier if they aren't really supported
108+
int perms = -1;
109+
if (posixAttrs != null) {
110+
perms = Permissions.toPosixInt(posixAttrs.permissions());
111+
}
112+
113+
final VirtualFileStat stat = new VirtualFileStat(type, size, modifiedTime, accessedTime, perms);
88114

89115
return new VirtualPath(path.getParentPath(), path.getName(), type == VirtualFileType.DIR, stat);
90116
}

jsync-vfs/src/main/java/com/fizzed/jsync/vfs/VirtualFileStat.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ public class VirtualFileStat {
66
final private long size;
77
final private long modifiedTime;
88
final private long accessedTime;
9+
final private int permissions;
910
// there are values that can be populated later as they are expensive operations
1011
private Long cksum;
1112
private String md5;
1213
private String sha1;
1314

14-
public VirtualFileStat(VirtualFileType type, long size, long modifiedTime, long accessedTime) {
15+
public VirtualFileStat(VirtualFileType type, long size, long modifiedTime, long accessedTime, int permissions) {
1516
this.size = size;
1617
this.type = type;
1718
this.modifiedTime = modifiedTime;
1819
this.accessedTime = accessedTime;
20+
this.permissions = permissions;
1921
}
2022

2123
public VirtualFileType getType() {
@@ -34,6 +36,14 @@ public long getAccessedTime() {
3436
return accessedTime;
3537
}
3638

39+
public int getPermissions() {
40+
return this.permissions;
41+
}
42+
43+
public String getPermissionsOctal() {
44+
return Integer.toOctalString(this.permissions);
45+
}
46+
3747
public Long getCksum() {
3848
return cksum;
3949
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.fizzed.jsync.vfs.util;
2+
3+
import java.nio.file.attribute.PosixFilePermission;
4+
import java.util.EnumSet;
5+
import java.util.Set;
6+
7+
public class Permissions {
8+
9+
/**
10+
* Converts a set of {@link PosixFilePermission} to its corresponding POSIX integer representation.
11+
* The resulting integer is a bitmask representing the file permission mode.
12+
*
13+
* @param permissions the set of {@link PosixFilePermission} to convert; if null, the result will be 0
14+
* @return an integer representing the POSIX file permission mode derived from the given set of permissions
15+
*/
16+
static public int toPosixInt(Set<PosixFilePermission> permissions) {
17+
int mode = 0;
18+
19+
// Null check safety
20+
if (permissions == null) return mode;
21+
22+
for (PosixFilePermission perm : permissions) {
23+
switch (perm) {
24+
case OWNER_READ: mode |= 0400; break; // 256 in decimal
25+
case OWNER_WRITE: mode |= 0200; break; // 128
26+
case OWNER_EXECUTE: mode |= 0100; break; // 64
27+
28+
case GROUP_READ: mode |= 0040; break; // 32
29+
case GROUP_WRITE: mode |= 0020; break; // 16
30+
case GROUP_EXECUTE: mode |= 0010; break; // 8
31+
32+
case OTHERS_READ: mode |= 0004; break; // 4
33+
case OTHERS_WRITE: mode |= 0002; break; // 2
34+
case OTHERS_EXECUTE: mode |= 0001; break; // 1
35+
}
36+
}
37+
38+
return mode;
39+
}
40+
41+
/**
42+
* Converts a POSIX file permission integer into a set of {@link PosixFilePermission}.
43+
* The input integer is interpreted as the POSIX file permission bitmask, where each bit
44+
* corresponds to a specific owner, group, or others permission (read, write, execute).
45+
*
46+
* @param permissions the integer representing the POSIX file permission bitmask
47+
* @return a set of {@link PosixFilePermission} that represents the provided integer bitmask
48+
*/
49+
public static Set<PosixFilePermission> toPosixFilePermissions(int permissions) {
50+
// Create an empty set specifically for this Enum type
51+
Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
52+
53+
// Owner (User)
54+
if ((permissions & 0400) != 0) perms.add(PosixFilePermission.OWNER_READ);
55+
if ((permissions & 0200) != 0) perms.add(PosixFilePermission.OWNER_WRITE);
56+
if ((permissions & 0100) != 0) perms.add(PosixFilePermission.OWNER_EXECUTE);
57+
58+
// Group
59+
if ((permissions & 0040) != 0) perms.add(PosixFilePermission.GROUP_READ);
60+
if ((permissions & 0020) != 0) perms.add(PosixFilePermission.GROUP_WRITE);
61+
if ((permissions & 0010) != 0) perms.add(PosixFilePermission.GROUP_EXECUTE);
62+
63+
// Others (World)
64+
if ((permissions & 0004) != 0) perms.add(PosixFilePermission.OTHERS_READ);
65+
if ((permissions & 0002) != 0) perms.add(PosixFilePermission.OTHERS_WRITE);
66+
if ((permissions & 0001) != 0) perms.add(PosixFilePermission.OTHERS_EXECUTE);
67+
68+
return perms;
69+
}
70+
71+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.fizzed.jsync.vfs;
2+
3+
import com.fizzed.crux.util.MoreFiles;
4+
import com.fizzed.crux.util.Resources;
5+
import org.junit.jupiter.api.BeforeAll;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.attribute.PosixFilePermissions;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
16+
17+
class LocalVirtualFileSystemTest {
18+
19+
static Path projectDir;
20+
private Path sourceDir;
21+
private LocalVirtualFileSystem defaultVfs;
22+
23+
@BeforeAll
24+
static public void setup() throws Exception {
25+
projectDir = Resources.file("/locator.txt").resolve("../..").toAbsolutePath().normalize();
26+
}
27+
28+
@BeforeEach
29+
public void before() throws IOException {
30+
this.sourceDir = projectDir.resolve("local-vfs-source");
31+
MoreFiles.deleteDirectoryIfExists(this.sourceDir);
32+
Files.createDirectories(this.sourceDir);
33+
this.defaultVfs = LocalVirtualFileSystem.open(this.sourceDir);
34+
}
35+
36+
@Test
37+
public void readPermissions() throws Exception {
38+
if (this.defaultVfs.isPosix()) {
39+
Path file = this.sourceDir.resolve("test.sh");
40+
Files.write(file, "#!/bin/sh\necho hello".getBytes());
41+
// 1. Set permissions to 755 (rwxr-xr-x)
42+
Files.setPosixFilePermissions(file, PosixFilePermissions.fromString("rwxr-xr-x"));
43+
44+
final VirtualPath fileWithStat = this.defaultVfs.stat(VirtualPath.parse(file.toString()));
45+
46+
assertThat(fileWithStat.getStat().getPermissionsOctal()).isEqualTo("755");
47+
}
48+
}
49+
50+
}

0 commit comments

Comments
 (0)