├── .gitignore
├── src
└── main
│ ├── resources
│ ├── exports
│ ├── logback.xml
│ └── logback-test.xml
│ └── java
│ └── org
│ └── dcache
│ └── simplenfs
│ ├── App.java
│ ├── SimpleNfsServer.java
│ └── LocalFileSystem.java
├── README.md
├── pom.xml
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
3 | *.iml
--------------------------------------------------------------------------------
/src/main/resources/exports:
--------------------------------------------------------------------------------
1 | / *(rw,no_root_squash)
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} [%X{mdc.client}] [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{mdc.client}] [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/java/org/dcache/simplenfs/App.java:
--------------------------------------------------------------------------------
1 | package org.dcache.simplenfs;
2 |
3 | import org.kohsuke.args4j.CmdLineException;
4 | import org.kohsuke.args4j.CmdLineParser;
5 | import org.kohsuke.args4j.Option;
6 |
7 | import java.io.IOException;
8 | import java.nio.file.Path;
9 | import org.dcache.nfs.ExportFile;
10 | import org.dcache.oncrpc4j.portmap.OncRpcEmbeddedPortmap;
11 |
12 | /**
13 | *
14 | */
15 | public class App {
16 |
17 | @Option(name = "-root", usage = "root of the file system to export", metaVar = "")
18 | private Path root;
19 | @Option(name = "-exports", usage = "path to file with export tables", metaVar = "")
20 | private Path exportsFile;
21 | @Option(name = "-nfsvers", usage = "NFS version (3, 4, 0==3+4) to use", metaVar = "")
22 | private int nfsVers = 0;
23 | @Option(name = "-port", usage = "TCP port to use", metaVar = "")
24 | private int rpcPort = 2049;
25 | @Option(name = "-with-portmap", usage = "start embedded portmap")
26 | private boolean withPortmap;
27 |
28 | public static void main(String[] args) throws Exception {
29 | new App().run(args);
30 | }
31 |
32 | public void run(String[] args) throws CmdLineException, IOException {
33 |
34 | CmdLineParser parser = new CmdLineParser(this);
35 |
36 | try {
37 | parser.parseArgument(args);
38 | } catch (CmdLineException e) {
39 | System.err.println();
40 | System.err.println(e.getMessage());
41 | System.err.println("Usage:");
42 | System.err.println(" App [options...]");
43 | System.err.println();
44 | parser.printUsage(System.err);
45 | System.exit(1);
46 | }
47 |
48 | ExportFile exportFile = null;
49 | if (exportsFile != null) {
50 | exportFile = new ExportFile(exportsFile.toFile());
51 | }
52 |
53 | if (withPortmap) {
54 | new OncRpcEmbeddedPortmap();
55 | }
56 |
57 | try (SimpleNfsServer ignored = new SimpleNfsServer(nfsVers, rpcPort, root, exportFile, null)) {
58 | //noinspection ResultOfMethodCallIgnored
59 | System.in.read(); //any key to shutdown
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Simple NFS server
2 | -----------------
3 |
4 | This is a demo/testing/playgourd code on top of dCache's [nfs4j](https://github.com/dcache/nfs4j)
5 |
6 |
7 | How to contribute
8 | =================
9 |
10 | **simple-nfs** uses the linux kernel model where git is not only source repository,
11 | but also the way to track contributions and copyrights.
12 |
13 | Each submitted patch must have a "Signed-off-by" line. Patches without
14 | this line will not be accepted.
15 |
16 | The sign-off is a simple line at the end of the explanation for the
17 | patch, which certifies that you wrote it or otherwise have the right to
18 | pass it on as an open-source patch. The rules are pretty simple: if you
19 | can certify the below:
20 | ```
21 |
22 | Developer's Certificate of Origin 1.1
23 |
24 | By making a contribution to this project, I certify that:
25 |
26 | (a) The contribution was created in whole or in part by me and I
27 | have the right to submit it under the open source license
28 | indicated in the file; or
29 |
30 | (b) The contribution is based upon previous work that, to the best
31 | of my knowledge, is covered under an appropriate open source
32 | license and I have the right under that license to submit that
33 | work with modifications, whether created in whole or in part
34 | by me, under the same open source license (unless I am
35 | permitted to submit under a different license), as indicated
36 | in the file; or
37 |
38 | (c) The contribution was provided directly to me by some other
39 | person who certified (a), (b) or (c) and I have not modified
40 | it.
41 |
42 | (d) I understand and agree that this project and the contribution
43 | are public and that a record of the contribution (including all
44 | personal information I submit with it, including my sign-off) is
45 | maintained indefinitely and may be redistributed consistent with
46 | this project or the open source license(s) involved.
47 |
48 | ```
49 | then you just add a line saying ( git commit -s )
50 |
51 | Signed-off-by: Random J Developer
52 |
53 | using your real name (sorry, no pseudonyms or anonymous contributions.)
54 |
55 |
56 | LICENSE
57 | -------
58 | This work is published under [Apache v2](https://www.apache.org/licenses/) license.
59 |
--------------------------------------------------------------------------------
/src/main/java/org/dcache/simplenfs/SimpleNfsServer.java:
--------------------------------------------------------------------------------
1 | package org.dcache.simplenfs;
2 |
3 | import org.dcache.nfs.ExportFile;
4 | import org.dcache.nfs.v3.MountServer;
5 | import org.dcache.nfs.v3.NfsServerV3;
6 | import org.dcache.nfs.v3.xdr.mount_prot;
7 | import org.dcache.nfs.v3.xdr.nfs3_prot;
8 | import org.dcache.nfs.v4.MDSOperationExecutor;
9 | import org.dcache.nfs.v4.NFSServerV41;
10 | import org.dcache.nfs.v4.xdr.nfs4_prot;
11 | import org.dcache.nfs.vfs.VirtualFileSystem;
12 | import org.dcache.oncrpc4j.rpc.OncRpcProgram;
13 | import org.dcache.oncrpc4j.rpc.OncRpcSvc;
14 | import org.dcache.oncrpc4j.rpc.OncRpcSvcBuilder;
15 |
16 | import java.io.Closeable;
17 | import java.io.IOException;
18 | import java.io.InputStreamReader;
19 | import java.io.UncheckedIOException;
20 | import java.nio.file.Files;
21 | import java.nio.file.Path;
22 | import java.util.stream.Collectors;
23 |
24 | public class SimpleNfsServer implements Closeable {
25 | private final OncRpcSvc nfsSvc;
26 | private final Path root;
27 | private final int port;
28 | private final String name;
29 |
30 | public SimpleNfsServer(Path root) {
31 | this(0, 2049, root, null, null);
32 | }
33 |
34 | public SimpleNfsServer(int nfsVers, int port, Path root, ExportFile exportFile, String name) {
35 | try {
36 | NfsServerV3 nfs3 = null;
37 | NFSServerV41 nfs4 = null;
38 | boolean startNfsV3 = ((nfsVers == 0) || (nfsVers == 3));
39 | boolean startNfsV4 = ((nfsVers == 0) || (nfsVers == 4));
40 |
41 | if (exportFile == null) {
42 | exportFile = new ExportFile(new InputStreamReader(SimpleNfsServer.class.getClassLoader().getResourceAsStream("exports")));
43 | }
44 |
45 | this.port = port;
46 |
47 | if (root == null) {
48 | root = Files.createTempDirectory(null);
49 | }
50 | this.root = root;
51 |
52 | if (name == null) {
53 | name = "nfs@" + this.port;
54 | }
55 | this.name = name;
56 |
57 | VirtualFileSystem vfs = new LocalFileSystem(this.root, exportFile.exports().collect(Collectors.toList()));
58 |
59 | nfsSvc = new OncRpcSvcBuilder()
60 | .withPort(this.port)
61 | .withTCP()
62 | .withAutoPublish()
63 | .withWorkerThreadIoStrategy()
64 | .withServiceName(this.name)
65 | .build();
66 |
67 | if (startNfsV4) {
68 | nfs4 = new NFSServerV41.Builder()
69 | .withVfs(vfs)
70 | .withOperationExecutor(new MDSOperationExecutor())
71 | .withExportTable(exportFile)
72 | .build();
73 | }
74 |
75 | if (startNfsV3) {
76 | nfs3 = new NfsServerV3(exportFile, vfs);
77 | }
78 |
79 | MountServer mountd = new MountServer(exportFile, vfs);
80 |
81 | if (startNfsV3) {
82 | nfsSvc.register(new OncRpcProgram(mount_prot.MOUNT_PROGRAM, mount_prot.MOUNT_V3), mountd);
83 | nfsSvc.register(new OncRpcProgram(mount_prot.MOUNT_PROGRAM, mount_prot.MOUNT_V1), mountd);
84 | nfsSvc.register(new OncRpcProgram(nfs3_prot.NFS_PROGRAM, nfs3_prot.NFS_V3), nfs3);
85 | }
86 |
87 | if (startNfsV4) {
88 | nfsSvc.register(new OncRpcProgram(nfs4_prot.NFS4_PROGRAM, nfs4_prot.NFS_V4), nfs4);
89 | }
90 |
91 | nfsSvc.start();
92 | } catch (IOException e) {
93 | throw new UncheckedIOException(e);
94 | }
95 | }
96 |
97 | @Override
98 | public void close() throws IOException {
99 | nfsSvc.stop();
100 | }
101 |
102 | public Path getRoot() {
103 | return root;
104 | }
105 |
106 | public int getPort() {
107 | return port;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | org.dcache
6 | simple-nfs
7 | 1.0-SNAPSHOT
8 |
9 | Simple nfs server
10 | http://www.dcache.org/
11 |
12 |
13 | UTF-8
14 |
15 |
16 |
17 |
18 |
19 | org.apache.maven.plugins
20 | maven-compiler-plugin
21 | 3.8.0
22 |
23 | 11
24 | 11
25 |
26 |
27 |
28 | org.apache.maven.plugins
29 | maven-assembly-plugin
30 | 3.1.0
31 |
32 |
33 |
34 | single
35 |
36 | package
37 |
38 |
39 | jar-with-dependencies
40 |
41 |
42 |
43 | org.dcache.simplenfs.App
44 | org.dcache.simplenfs
45 |
46 |
47 |
48 | development
49 | org.dcache.simplenfs
50 | ${project.version}
51 | ${buildNumber}
52 | dCache
53 | ${project.url}
54 | ${maven.build.timestamp}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | org.dcache
67 | nfs4j-core
68 | 0.25.0
69 |
70 |
71 | org.slf4j
72 | slf4j-api
73 | 1.7.26
74 |
75 |
76 | org.slf4j
77 | log4j-over-slf4j
78 | 1.7.26
79 |
80 |
81 | ch.qos.logback
82 | logback-classic
83 | 1.2.13
84 |
85 |
86 | args4j
87 | args4j
88 | 2.33
89 |
90 |
91 | com.boundary
92 | high-scale-lib
93 | 1.0.6
94 |
95 |
96 | junit
97 | junit
98 | 4.13.1
99 | test
100 |
101 |
102 |
103 |
104 |
105 | dcache releases
106 | https://download.dcache.org/nexus/content/repositories/releases
107 |
108 | true
109 |
110 |
111 | false
112 |
113 |
114 |
115 | dcache snapshots
116 | https://download.dcache.org/nexus/content/repositories/snapshots
117 |
118 | false
119 |
120 |
121 | true
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/main/java/org/dcache/simplenfs/LocalFileSystem.java:
--------------------------------------------------------------------------------
1 | package org.dcache.simplenfs;
2 |
3 | import com.google.common.primitives.Longs;
4 | import com.sun.security.auth.UnixNumericGroupPrincipal;
5 | import com.sun.security.auth.UnixNumericUserPrincipal;
6 | import org.cliffc.high_scale_lib.NonBlockingHashMap;
7 | import org.cliffc.high_scale_lib.NonBlockingHashMapLong;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import org.dcache.nfs.FsExport;
12 | import org.dcache.nfs.status.ExistException;
13 | import org.dcache.nfs.status.NoEntException;
14 | import org.dcache.nfs.status.NotEmptyException;
15 | import org.dcache.nfs.v4.NfsIdMapping;
16 | import org.dcache.nfs.v4.SimpleIdMap;
17 | import org.dcache.nfs.v4.xdr.nfsace4;
18 | import org.dcache.nfs.vfs.AclCheckable;
19 | import org.dcache.nfs.vfs.DirectoryEntry;
20 | import org.dcache.nfs.vfs.FsStat;
21 | import org.dcache.nfs.vfs.Inode;
22 | import org.dcache.nfs.vfs.Stat;
23 | import org.dcache.nfs.vfs.Stat.Type;
24 | import org.dcache.nfs.vfs.VirtualFileSystem;
25 |
26 | import javax.security.auth.Subject;
27 |
28 | import java.io.IOException;
29 | import java.io.RandomAccessFile;
30 | import java.nio.ByteBuffer;
31 | import java.nio.channels.FileChannel;
32 | import java.nio.file.DirectoryNotEmptyException;
33 | import java.nio.file.FileAlreadyExistsException;
34 | import java.nio.file.FileStore;
35 | import java.nio.file.FileSystems;
36 | import java.nio.file.FileVisitResult;
37 | import java.nio.file.Files;
38 | import java.nio.file.Path;
39 | import java.nio.file.SimpleFileVisitor;
40 | import java.nio.file.StandardCopyOption;
41 | import java.nio.file.StandardOpenOption;
42 | import java.nio.file.attribute.BasicFileAttributes;
43 | import java.nio.file.attribute.BasicFileAttributeView;
44 | import java.nio.file.attribute.DosFileAttributeView;
45 | import java.nio.file.attribute.DosFileAttributes;
46 | import java.nio.file.attribute.FileTime;
47 | import java.nio.file.attribute.GroupPrincipal;
48 | import java.nio.file.attribute.PosixFileAttributeView;
49 | import java.nio.file.attribute.UserPrincipal;
50 | import java.nio.file.attribute.UserPrincipalLookupService;
51 | import java.security.Principal;
52 | import java.util.ArrayList;
53 | import java.util.List;
54 | import java.util.concurrent.atomic.AtomicLong;
55 |
56 | import org.dcache.nfs.status.NotSuppException;
57 | import org.dcache.nfs.status.PermException;
58 | import org.dcache.nfs.status.ServerFaultException;
59 | import org.dcache.nfs.vfs.DirectoryStream;
60 |
61 | import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
62 |
63 | /**
64 | *
65 | */
66 | public class LocalFileSystem implements VirtualFileSystem {
67 |
68 | private static final Logger LOG = LoggerFactory.getLogger(LocalFileSystem.class);
69 |
70 | private final Path _root;
71 | private final NonBlockingHashMapLong inodeToPath = new NonBlockingHashMapLong<>();
72 | private final NonBlockingHashMap pathToInode = new NonBlockingHashMap<>();
73 | private final AtomicLong fileId = new AtomicLong(1); //numbering starts at 1
74 | private final NfsIdMapping _idMapper = new SimpleIdMap();
75 | private final UserPrincipalLookupService _lookupService =
76 | FileSystems.getDefault().getUserPrincipalLookupService();
77 |
78 | private final static boolean IS_UNIX;
79 | static {
80 | IS_UNIX = !System.getProperty("os.name").startsWith("Win");
81 | }
82 |
83 | private Inode toFh(long inodeNumber) {
84 | return Inode.forFile(Longs.toByteArray(inodeNumber));
85 | }
86 |
87 | private long getInodeNumber(Inode inode) {
88 | return Longs.fromByteArray(inode.getFileId());
89 | }
90 |
91 | private Path resolveInode(long inodeNumber) throws NoEntException {
92 | Path path = inodeToPath.get(inodeNumber);
93 | if (path == null) {
94 | throw new NoEntException("inode #" + inodeNumber);
95 | }
96 | return path;
97 | }
98 |
99 | private long resolvePath(Path path) throws NoEntException {
100 | Long inodeNumber = pathToInode.get(path);
101 | if (inodeNumber == null) {
102 | throw new NoEntException("path " + path);
103 | }
104 | return inodeNumber;
105 | }
106 |
107 | /**
108 | * Map an inode number to a path.
109 | * @param inodeNumber the inode number
110 | * @param path the path
111 | * @param force if true, overwrite any existing mapping
112 | */
113 | private void map(long inodeNumber, Path path, boolean force) {
114 | if (inodeToPath.putIfAbsent(inodeNumber, path) != null) {
115 | throw new IllegalStateException();
116 | }
117 |
118 | if (force) {
119 | pathToInode.put(path, inodeNumber);
120 | } else {
121 | Long otherInodeNumber = pathToInode.putIfAbsent(path, inodeNumber);
122 | if (otherInodeNumber != null) {
123 | //try rollback
124 | if (inodeToPath.remove(inodeNumber) != path) {
125 | throw new IllegalStateException("cant map, rollback failed");
126 | }
127 | throw new IllegalStateException("path " + path + " already mapped to " + otherInodeNumber);
128 | }
129 | }
130 | }
131 |
132 | private void map(long inodeNumber, Path path) {
133 | map(inodeNumber, path, false);
134 | }
135 |
136 | private void unmap(long inodeNumber, Path path) {
137 | Path removedPath = inodeToPath.remove(inodeNumber);
138 | if (!path.equals(removedPath)) {
139 | throw new IllegalStateException();
140 | }
141 | if (pathToInode.remove(path) != inodeNumber) {
142 | throw new IllegalStateException();
143 | }
144 | }
145 |
146 | private void remap(long inodeNumber, Path oldPath, Path newPath) {
147 | //TODO - attempt rollback?
148 | unmap(inodeNumber, oldPath);
149 | map(inodeNumber, newPath, true);
150 | }
151 |
152 | public LocalFileSystem(Path root, Iterable exportIterable) throws IOException {
153 | _root = root;
154 | assert (Files.exists(_root));
155 | for (FsExport export : exportIterable) {
156 | String relativeExportPath = export.getPath().substring(1); // remove the opening '/'
157 | Path exportRootPath = root.resolve(relativeExportPath);
158 | if (!Files.exists(exportRootPath)) {
159 | Files.createDirectories(exportRootPath);
160 | }
161 | }
162 | //map existing structure (if any)
163 | map(fileId.getAndIncrement(), _root); //so root is always inode #1
164 | Files.walkFileTree(_root, new SimpleFileVisitor() {
165 | @Override
166 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
167 | FileVisitResult superRes = super.preVisitDirectory(dir, attrs);
168 | if (superRes != FileVisitResult.CONTINUE) {
169 | return superRes;
170 | }
171 | if (dir.equals(_root)) {
172 | return FileVisitResult.CONTINUE;
173 | }
174 | map(fileId.getAndIncrement(), dir);
175 | return FileVisitResult.CONTINUE;
176 | }
177 |
178 | @Override
179 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
180 | FileVisitResult superRes = super.visitFile(file, attrs);
181 | if (superRes != FileVisitResult.CONTINUE) {
182 | return superRes;
183 | }
184 | map(fileId.getAndIncrement(), file);
185 | return FileVisitResult.CONTINUE;
186 | }
187 | });
188 | }
189 |
190 | @Override
191 | public Inode create(Inode parent, Type type, String path, Subject subject, int mode) throws IOException {
192 | long parentInodeNumber = getInodeNumber(parent);
193 | Path parentPath = resolveInode(parentInodeNumber);
194 | Path newPath = parentPath.resolve(path);
195 | try {
196 | Files.createFile(newPath);
197 | } catch (FileAlreadyExistsException e) {
198 | throw new ExistException("path " + newPath);
199 | }
200 | long newInodeNumber = fileId.getAndIncrement();
201 | map(newInodeNumber, newPath);
202 | setOwnershipAndMode(newPath, subject, mode);
203 | return toFh(newInodeNumber);
204 | }
205 |
206 | @Override
207 | public FsStat getFsStat() throws IOException {
208 | FileStore store = Files.getFileStore(_root);
209 | long total = store.getTotalSpace();
210 | long free = store.getUsableSpace();
211 | return new FsStat(total, Long.MAX_VALUE, total-free, pathToInode.size());
212 | }
213 |
214 | @Override
215 | public Inode getRootInode() throws IOException {
216 | return toFh(1); //always #1 (see constructor)
217 | }
218 |
219 | @Override
220 | public Inode lookup(Inode parent, String path) throws IOException {
221 | //TODO - several issues
222 | //2. we might accidentally allow composite paths here ("/dome/dir/down")
223 | //3. we dont actually check that the parent exists
224 | long parentInodeNumber = getInodeNumber(parent);
225 | Path parentPath = resolveInode(parentInodeNumber);
226 | Path child;
227 | if(path.equals(".")) {
228 | child = parentPath;
229 | } else if(path.equals("..")) {
230 | child = parentPath.getParent();
231 | } else {
232 | child = parentPath.resolve(path);
233 | }
234 | long childInodeNumber = resolvePath(child);
235 | return toFh(childInodeNumber);
236 | }
237 |
238 | @Override
239 | public Inode link(Inode parent, Inode existing, String target, Subject subject) throws IOException {
240 | long parentInodeNumber = getInodeNumber(parent);
241 | Path parentPath = resolveInode(parentInodeNumber);
242 |
243 | long existingInodeNumber = getInodeNumber(existing);
244 | Path existingPath = resolveInode(existingInodeNumber);
245 |
246 | Path targetPath = parentPath.resolve(target);
247 |
248 | try {
249 | Files.createLink(targetPath, existingPath);
250 | } catch (UnsupportedOperationException e) {
251 | throw new NotSuppException("Not supported", e);
252 | } catch (FileAlreadyExistsException e) {
253 | throw new ExistException("Path exists " + target, e);
254 | } catch (SecurityException e) {
255 | throw new PermException("Permission denied: " + e.getMessage(), e);
256 | } catch (IOException e) {
257 | throw new ServerFaultException("Failed to create: " + e.getMessage(), e);
258 | }
259 |
260 | long newInodeNumber = fileId.getAndIncrement();
261 | map(newInodeNumber, targetPath);
262 | return toFh(newInodeNumber);
263 | }
264 |
265 | @Override
266 | public DirectoryStream list(Inode inode, byte[] bytes, long l) throws IOException {
267 | long inodeNumber = getInodeNumber(inode);
268 | Path path = resolveInode(inodeNumber);
269 | final List list = new ArrayList<>();
270 | try (java.nio.file.DirectoryStream ds = Files.newDirectoryStream(path)) {
271 | int cookie = 2; // first allowed cookie
272 | for (Path p : ds) {
273 | cookie++;
274 | if (cookie > l) {
275 | long ino = resolvePath(p);
276 | list.add(new DirectoryEntry(p.getFileName().toString(), toFh(ino), statPath(p, ino), cookie));
277 | }
278 | }
279 | }
280 | return new DirectoryStream(list);
281 | }
282 |
283 | @Override
284 | public byte[] directoryVerifier(Inode inode) throws IOException {
285 | return DirectoryStream.ZERO_VERIFIER;
286 | }
287 |
288 | @Override
289 | public Inode mkdir(Inode parent, String path, Subject subject, int mode) throws IOException {
290 | long parentInodeNumber = getInodeNumber(parent);
291 | Path parentPath = resolveInode(parentInodeNumber);
292 | Path newPath = parentPath.resolve(path);
293 | try {
294 | Files.createDirectory(newPath);
295 | } catch (FileAlreadyExistsException e) {
296 | throw new ExistException("path " + newPath);
297 | }
298 | long newInodeNumber = fileId.getAndIncrement();
299 | map(newInodeNumber, newPath);
300 | setOwnershipAndMode(newPath, subject, mode);
301 | return toFh(newInodeNumber);
302 | }
303 |
304 | private void setOwnershipAndMode(Path target, Subject subject, int mode)
305 | {
306 | if (!IS_UNIX) {
307 | // FIXME: windows must support some kind of file owhership as well
308 | return;
309 | }
310 |
311 | int uid = -1;
312 | int gid = -1;
313 | for (Principal principal : subject.getPrincipals()) {
314 | if (principal instanceof UnixNumericUserPrincipal) {
315 | uid = (int) ((UnixNumericUserPrincipal)principal).longValue();
316 | }
317 | if (principal instanceof UnixNumericGroupPrincipal) {
318 | gid = (int) ((UnixNumericGroupPrincipal)principal).longValue();
319 | }
320 | }
321 |
322 | if (uid != -1) {
323 | try {
324 | Files.setAttribute(target, "unix:uid", uid, NOFOLLOW_LINKS);
325 | } catch (IOException e) {
326 | LOG.warn("Unable to chown file {}: {}", target, e.getMessage());
327 | }
328 | } else {
329 | LOG.warn("File created without uid: {}", target);
330 | }
331 | if (gid != -1) {
332 | try {
333 | Files.setAttribute(target, "unix:gid", gid, NOFOLLOW_LINKS);
334 | } catch (IOException e) {
335 | LOG.warn("Unable to chown file {}: {}", target, e.getMessage());
336 | }
337 | } else {
338 | LOG.warn("File created without gid: {}", target);
339 | }
340 |
341 | try {
342 | Files.setAttribute(target, "unix:mode", mode, NOFOLLOW_LINKS);
343 | } catch (IOException e) {
344 | LOG.warn("Unable to set mode of file {}: {}", target, e.getMessage());
345 | }
346 | }
347 |
348 | @Override
349 | public boolean move(Inode src, String oldName, Inode dest, String newName) throws IOException {
350 | //TODO - several issues
351 | //1. we might not deal with "." and ".." properly
352 | //2. we might accidentally allow composite paths here ("/dome/dir/down")
353 | //3. we return true (changed) even though in theory a file might be renamed to itself?
354 | long currentParentInodeNumber = getInodeNumber(src);
355 | Path currentParentPath = resolveInode(currentParentInodeNumber);
356 | long destParentInodeNumber = getInodeNumber(dest);
357 | Path destPath = resolveInode(destParentInodeNumber);
358 | Path currentPath = currentParentPath.resolve(oldName);
359 | long targetInodeNumber = resolvePath(currentPath);
360 | Path newPath = destPath.resolve(newName);
361 | try {
362 | Files.move(currentPath, newPath, StandardCopyOption.ATOMIC_MOVE);
363 | } catch (FileAlreadyExistsException e) {
364 | throw new ExistException("path " + newPath);
365 | }
366 | remap(targetInodeNumber, currentPath, newPath);
367 | return true;
368 | }
369 |
370 | @Override
371 | public Inode parentOf(Inode inode) throws IOException {
372 | long inodeNumber = getInodeNumber(inode);
373 | if (inodeNumber == 1) {
374 | throw new NoEntException("no parent"); //its the root
375 | }
376 | Path path = resolveInode(inodeNumber);
377 | Path parentPath = path.getParent();
378 | long parentInodeNumber = resolvePath(parentPath);
379 | return toFh(parentInodeNumber);
380 | }
381 |
382 | @Override
383 | public int read(Inode inode, byte[] data, long offset, int count) throws IOException {
384 | long inodeNumber = getInodeNumber(inode);
385 | Path path = resolveInode(inodeNumber);
386 | ByteBuffer destBuffer = ByteBuffer.wrap(data, 0, count);
387 | try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
388 | return channel.read(destBuffer, offset);
389 | }
390 | }
391 |
392 | @Override
393 | public String readlink(Inode inode) throws IOException {
394 | long inodeNumber = getInodeNumber(inode);
395 | Path path = resolveInode(inodeNumber);
396 | return Files.readSymbolicLink(path).toString();
397 | }
398 |
399 | @Override
400 | public void remove(Inode parent, String path) throws IOException {
401 | long parentInodeNumber = getInodeNumber(parent);
402 | Path parentPath = resolveInode(parentInodeNumber);
403 | Path targetPath = parentPath.resolve(path);
404 | long targetInodeNumber = resolvePath(targetPath);
405 | try {
406 | Files.delete(targetPath);
407 | } catch (DirectoryNotEmptyException e) {
408 | throw new NotEmptyException("dir " + targetPath + " is note empty", e);
409 | }
410 | unmap(targetInodeNumber, targetPath);
411 | }
412 |
413 | @Override
414 | public Inode symlink(Inode parent, String linkName, String targetName, Subject subject, int mode) throws IOException {
415 | long parentInodeNumber = getInodeNumber(parent);
416 | Path parentPath = resolveInode(parentInodeNumber);
417 | Path link = parentPath.resolve(linkName);
418 | Path target = parentPath.resolve(targetName);
419 | if (!targetName.startsWith("/")) {
420 | target = parentPath.relativize(target);
421 | }
422 | try {
423 | Files.createSymbolicLink(link, target);
424 | } catch (UnsupportedOperationException e) {
425 | throw new NotSuppException("Not supported", e);
426 | } catch (FileAlreadyExistsException e) {
427 | throw new ExistException("Path exists " + linkName, e);
428 | } catch (SecurityException e) {
429 | throw new PermException("Permission denied: " + e.getMessage(), e);
430 | } catch (IOException e) {
431 | throw new ServerFaultException("Failed to create: " + e.getMessage(), e);
432 | }
433 |
434 | setOwnershipAndMode(link, subject, mode);
435 |
436 | long newInodeNumber = fileId.getAndIncrement();
437 | map(newInodeNumber, link);
438 | return toFh(newInodeNumber);
439 | }
440 |
441 | @Override
442 | public WriteResult write(Inode inode, byte[] data, long offset, int count, StabilityLevel stabilityLevel) throws IOException {
443 | long inodeNumber = getInodeNumber(inode);
444 | Path path = resolveInode(inodeNumber);
445 | ByteBuffer srcBuffer = ByteBuffer.wrap(data, 0, count);
446 | try (FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE)) {
447 | int bytesWritten = channel.write(srcBuffer, offset);
448 | return new WriteResult(StabilityLevel.FILE_SYNC, bytesWritten);
449 | }
450 | }
451 |
452 | @Override
453 | public void commit(Inode inode, long l, int i) throws IOException {
454 | throw new UnsupportedOperationException("Not supported yet.");
455 | }
456 |
457 | private Stat statPath(Path p, long inodeNumber) throws IOException {
458 |
459 | Class extends BasicFileAttributeView> attributeClass =
460 | IS_UNIX ? PosixFileAttributeView.class : DosFileAttributeView.class;
461 |
462 | BasicFileAttributes attrs = Files.getFileAttributeView(p, attributeClass, NOFOLLOW_LINKS).readAttributes();
463 |
464 | Stat stat = new Stat();
465 |
466 | stat.setATime(attrs.lastAccessTime().toMillis());
467 | stat.setCTime(attrs.creationTime().toMillis());
468 | stat.setMTime(attrs.lastModifiedTime().toMillis());
469 |
470 | if (IS_UNIX) {
471 | stat.setGid((Integer) Files.getAttribute(p, "unix:gid", NOFOLLOW_LINKS));
472 | stat.setUid((Integer) Files.getAttribute(p, "unix:uid", NOFOLLOW_LINKS));
473 | stat.setMode((Integer) Files.getAttribute(p, "unix:mode", NOFOLLOW_LINKS));
474 | stat.setNlink((Integer) Files.getAttribute(p, "unix:nlink", NOFOLLOW_LINKS));
475 | } else {
476 | DosFileAttributes dosAttrs = (DosFileAttributes)attrs;
477 | stat.setGid(0);
478 | stat.setUid(0);
479 | int type = dosAttrs.isSymbolicLink() ? Stat.S_IFLNK : dosAttrs.isDirectory() ? Stat.S_IFDIR : Stat.S_IFREG;
480 | stat.setMode( type |(dosAttrs.isReadOnly()? 0400 : 0600));
481 | stat.setNlink(1);
482 | }
483 |
484 | stat.setDev(17);
485 | stat.setIno(inodeNumber);
486 | stat.setRdev(17);
487 | stat.setSize(attrs.size());
488 | stat.setGeneration(attrs.lastModifiedTime().toMillis());
489 |
490 | return stat;
491 | }
492 |
493 | @Override
494 | public int access(Subject subject, Inode inode, int mode) throws IOException {
495 | return mode;
496 | }
497 |
498 | @Override
499 | public Stat getattr(Inode inode) throws IOException {
500 | long inodeNumber = getInodeNumber(inode);
501 | Path path = resolveInode(inodeNumber);
502 | return statPath(path, inodeNumber);
503 | }
504 |
505 | @Override
506 | public void setattr(Inode inode, Stat stat) throws IOException {
507 | if (!IS_UNIX) {
508 | // FIXME: windows must support some kind of attribute update as well
509 | return;
510 | }
511 |
512 | long inodeNumber = getInodeNumber(inode);
513 | Path path = resolveInode(inodeNumber);
514 | PosixFileAttributeView attributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class, NOFOLLOW_LINKS);
515 | if (stat.isDefined(Stat.StatAttribute.OWNER)) {
516 | try {
517 | String uid = String.valueOf(stat.getUid());
518 | UserPrincipal user = _lookupService.lookupPrincipalByName(uid);
519 | attributeView.setOwner(user);
520 | } catch (IOException e) {
521 | throw new UnsupportedOperationException("set uid failed: " + e.getMessage(), e);
522 | }
523 | }
524 | if (stat.isDefined(Stat.StatAttribute.GROUP)) {
525 | try {
526 | String gid = String.valueOf(stat.getGid());
527 | GroupPrincipal group = _lookupService.lookupPrincipalByGroupName(gid);
528 | attributeView.setGroup(group);
529 | } catch (IOException e) {
530 | throw new UnsupportedOperationException("set gid failed: " + e.getMessage(), e);
531 | }
532 | }
533 | if (stat.isDefined(Stat.StatAttribute.MODE)) {
534 | try {
535 | Files.setAttribute(path, "unix:mode", stat.getMode(), NOFOLLOW_LINKS);
536 | } catch (IOException e) {
537 | throw new UnsupportedOperationException("set mode unsupported: " + e.getMessage(), e);
538 | }
539 | }
540 | if (stat.isDefined(Stat.StatAttribute.SIZE)) {
541 | try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw")) {
542 | raf.setLength(stat.getSize());
543 | }
544 | }
545 | if (stat.isDefined(Stat.StatAttribute.ATIME)) {
546 | try {
547 | FileTime time = FileTime.fromMillis(stat.getCTime());
548 | Files.setAttribute(path, "unix:lastAccessTime", time, NOFOLLOW_LINKS);
549 | } catch (IOException e) {
550 | throw new UnsupportedOperationException("set atime failed: " + e.getMessage(), e);
551 | }
552 | }
553 | if (stat.isDefined(Stat.StatAttribute.MTIME)) {
554 | try {
555 | FileTime time = FileTime.fromMillis(stat.getMTime());
556 | Files.setAttribute(path, "unix:lastModifiedTime", time, NOFOLLOW_LINKS);
557 | } catch (IOException e) {
558 | throw new UnsupportedOperationException("set mtime failed: " + e.getMessage(), e);
559 | }
560 | }
561 | if (stat.isDefined(Stat.StatAttribute.CTIME)) {
562 | try {
563 | FileTime time = FileTime.fromMillis(stat.getCTime());
564 | Files.setAttribute(path, "unix:ctime", time, NOFOLLOW_LINKS);
565 | } catch (IOException e) {
566 | throw new UnsupportedOperationException("set ctime failed: " + e.getMessage(), e);
567 | }
568 | }
569 | }
570 |
571 | @Override
572 | public nfsace4[] getAcl(Inode inode) throws IOException {
573 | return new nfsace4[0];
574 | }
575 |
576 | @Override
577 | public void setAcl(Inode inode, nfsace4[] acl) throws IOException {
578 | // NOP
579 | }
580 |
581 | @Override
582 | public boolean hasIOLayout(Inode inode) throws IOException {
583 | return false;
584 | }
585 |
586 | @Override
587 | public AclCheckable getAclCheckable() {
588 | return AclCheckable.UNDEFINED_ALL;
589 | }
590 |
591 | @Override
592 | public NfsIdMapping getIdMapper() {
593 | return _idMapper;
594 | }
595 |
596 | @Override
597 | public boolean getCaseInsensitive() {
598 | return true;
599 | }
600 |
601 | @Override
602 | public boolean getCasePreserving() {
603 | return true;
604 | }
605 |
606 | }
607 |
--------------------------------------------------------------------------------