Skip to content

Commit f663554

Browse files
Added umount command and unmount() API (#611)
* Added umount command and unmount() API Co-authored-by: LSantha <LSantha@users.noreply.github.com> * Fixed review: desc, tests, revert Co-authored-by: LSantha <LSantha@users.noreply.github.com> * Fixed rollback ordering, test mocks, removed flush Co-authored-by: LSantha <LSantha@users.noreply.github.com> --------- Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: LSantha <LSantha@users.noreply.github.com>
1 parent 601f1f4 commit f663554

7 files changed

Lines changed: 295 additions & 2 deletions

File tree

fs/build-tests.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ help Output these messages
4141
<test name="org.jnode.test.fs.filesystem.FSTestSuite" todir="${basedir}/build/reports/junit"/>
4242
<test name="org.jnode.test.fs.driver.tests.BlockDeviceAPITest" todir="${basedir}/build/reports/junit"/>
4343
<test name="org.jnode.test.fs.command.SyncCommandTest" todir="${basedir}/build/reports/junit"/>
44+
<test name="org.jnode.fs.service.def.FileSystemAPIImplTest" todir="${basedir}/build/reports/junit"/>
4445
</junit>
4546
</target>
4647

fs/descriptors/org.jnode.fs.command.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<alias name="eject" class="org.jnode.fs.command.EjectCommand"/>
4444
<alias name="mount" class="org.jnode.fs.command.MountCommand"/>
4545
<alias name="sync" class="org.jnode.fs.command.SyncCommand"/>
46+
<alias name="umount" class="org.jnode.fs.command.UnmountCommand"/>
4647
</extension>
4748

4849
<extension point="org.jnode.shell.syntaxes">
@@ -66,6 +67,11 @@
6667
<argument argLabel="path" description="the path of the filesystem to flush"/>
6768
</sequence>
6869
</syntax>
70+
<syntax alias="umount">
71+
<sequence description="Unmount the filesystem at the given path">
72+
<argument argLabel="directory" description="the mount point to unmount"/>
73+
</sequence>
74+
</syntax>
6975
</extension>
7076

7177
<extension point="org.jnode.security.permissions">
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* $Id$
3+
*
4+
* Copyright (C) 2003-2015 JNode.org
5+
*
6+
* This library is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU Lesser General Public License as published
8+
* by the Free Software Foundation; either version 2.1 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This library is distributed in the hope that it will be useful, but
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14+
* License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this library; If not, write to the Free Software Foundation, Inc.,
18+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*/
20+
21+
package org.jnode.fs.command;
22+
23+
import java.io.IOException;
24+
import java.io.PrintWriter;
25+
import org.jnode.fs.service.FileSystemService;
26+
import org.jnode.naming.InitialNaming;
27+
import org.jnode.shell.AbstractCommand;
28+
import org.jnode.shell.syntax.Argument;
29+
import org.jnode.shell.syntax.FileArgument;
30+
31+
/**
32+
* Unmount a filesystem.
33+
*/
34+
public class UnmountCommand extends AbstractCommand {
35+
36+
private final FileArgument argDir =
37+
new FileArgument("directory", Argument.MANDATORY,
38+
"the mount point to unmount");
39+
40+
public UnmountCommand() {
41+
super("Unmount a filesystem");
42+
registerArguments(argDir);
43+
}
44+
45+
public static void main(String[] args) throws Exception {
46+
new UnmountCommand().execute(args);
47+
}
48+
49+
public void execute() throws Exception {
50+
FileSystemService fss = InitialNaming.lookup(FileSystemService.NAME);
51+
PrintWriter out = getOutput().getPrintWriter();
52+
PrintWriter err = getError().getPrintWriter();
53+
String path = argDir.getValue().getCanonicalPath();
54+
55+
if (!fss.isMount(path)) {
56+
err.println("Not a mount point: " + path);
57+
err.println("Use 'mount' to list mounted filesystems.");
58+
exit(1);
59+
}
60+
61+
try {
62+
fss.unmount(path);
63+
out.println("Unmounted " + path);
64+
} catch (IOException ex) {
65+
err.println("Failed to unmount " + path + ": " + ex.getMessage());
66+
exit(1);
67+
}
68+
}
69+
}

fs/src/fs/org/jnode/fs/service/FileSystemService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ public <T extends FileSystemType<?>> T getFileSystemType(Class<T> name)
115115
*/
116116
public boolean isMount(String fullPath);
117117

118+
/**
119+
* Unmount the filesystem at the given path.
120+
* Flushes the filesystem, closes it, and removes the mount point.
121+
*
122+
* @param fullPath the mount point path
123+
* @throws IOException if flush or close fails
124+
* @throws IllegalArgumentException if path is not a mount point
125+
*/
126+
public void unmount(String fullPath) throws IOException;
127+
118128
/**
119129
* Gets the filesystem API.
120130
*/

fs/src/fs/org/jnode/fs/service/def/FileSystemAPIImpl.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.jnode.fs.FSDirectory;
3838
import org.jnode.fs.FSEntry;
3939
import org.jnode.fs.FileSystem;
40+
import org.jnode.fs.spi.AbstractFileSystem;
4041
import org.jnode.java.io.VMFileHandle;
4142

4243
/**
@@ -460,8 +461,35 @@ void mount(String fullPath, FileSystem<?> fs, String fsPath) throws IOException
460461
fullPath = File.separatorChar + fullPath;
461462
}
462463

463-
mountPoints.put(fullPath, fs); // TODO handle removal (+ add unmount
464-
// method) of filesystems
464+
mountPoints.put(fullPath, fs);
465+
}
466+
467+
/**
468+
* Unmount the filesystem at the given path.
469+
* Flushes the filesystem, closes it, and removes the mount point.
470+
* If close fails, the mount point is restored for rollback.
471+
*
472+
* @param fullPath the mount point path
473+
* @throws IOException if close fails
474+
* @throws IllegalArgumentException if path is not a mount point
475+
*/
476+
void unmount(String fullPath) throws IOException {
477+
if (fullPath.charAt(0) != File.separatorChar) {
478+
fullPath = File.separatorChar + fullPath;
479+
}
480+
481+
FileSystem<?> fs = mountPoints.remove(fullPath);
482+
if (fs == null) {
483+
throw new IllegalArgumentException("Not a mount point: " + fullPath);
484+
}
485+
486+
try {
487+
fs.close();
488+
vfs.unregisterFileSystem(fs.getDevice());
489+
} catch (IOException ex) {
490+
mountPoints.put(fullPath, fs);
491+
throw ex;
492+
}
465493
}
466494

467495
/**

fs/src/fs/org/jnode/fs/service/def/FileSystemPlugin.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ public void mount(String fullPath, FileSystem<?> fs, String fsPath) throws IOExc
147147
api.mount(VMFile.getNormalizedPath(fullPath), fs, fsPath);
148148
}
149149

150+
/**
151+
* Unmount the filesystem at the given path.
152+
* Flushes the filesystem, closes it, and removes the mount point.
153+
*
154+
* @param fullPath the mount point path
155+
* @throws IOException if flush or close fails
156+
* @throws IllegalArgumentException if path is not a mount point
157+
*/
158+
public void unmount(String fullPath) throws IOException {
159+
api.unmount(VMFile.getNormalizedPath(fullPath));
160+
}
161+
150162
/**
151163
* Return a map (fullPath -> FileSystem) of mount points
152164
* @return a copy of the internal map, sorted by fullPath
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* $Id$
3+
*
4+
* Copyright (C) 2003-2015 JNode.org
5+
*
6+
* This library is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU Lesser General Public License as published
8+
* by the Free Software Foundation; either version 2.1 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This library is distributed in the hope that it will be useful, but
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14+
* License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this library; If not, write to the Free Software Foundation, Inc.,
18+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*/
20+
21+
package org.jnode.fs.service.def;
22+
23+
import java.io.IOException;
24+
import java.util.Map;
25+
26+
import org.jnode.driver.Device;
27+
import org.jnode.fs.FileSystem;
28+
import org.junit.After;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
32+
import static junit.framework.Assert.assertEquals;
33+
import static junit.framework.Assert.assertFalse;
34+
import static junit.framework.Assert.assertTrue;
35+
import static junit.framework.Assert.fail;
36+
import static org.mockito.Mockito.doThrow;
37+
import static org.mockito.Mockito.mock;
38+
import static org.mockito.Mockito.verify;
39+
import static org.mockito.Mockito.when;
40+
41+
/**
42+
* Tests for FileSystemAPIImpl.unmount().
43+
*/
44+
public class FileSystemAPIImplTest {
45+
46+
private FileSystemAPIImpl api;
47+
private VirtualFS vfs;
48+
49+
@Before
50+
public void setUp() throws Exception {
51+
FileSystemManager fsm = new FileSystemManager();
52+
Device device = mock(Device.class);
53+
when(device.getShortDescription()).thenReturn("mock-device");
54+
vfs = new VirtualFS(device);
55+
api = new FileSystemAPIImpl(fsm, vfs);
56+
}
57+
58+
@After
59+
public void tearDown() throws Exception {
60+
api = null;
61+
vfs = null;
62+
}
63+
64+
@Test
65+
public void testUnmountNonExistentMountPointThrowsIllegalArgument() {
66+
try {
67+
api.unmount("/nonexistent");
68+
fail("Expected IllegalArgumentException");
69+
} catch (IllegalArgumentException e) {
70+
assertTrue(e.getMessage().contains("Not a mount point"));
71+
} catch (IOException e) {
72+
fail("Expected IllegalArgumentException, got IOException");
73+
}
74+
}
75+
76+
@Test
77+
public void testUnmountWithoutLeadingSlash() throws Exception {
78+
Device device = mock(Device.class);
79+
FileSystem<?> fs = mock(FileSystem.class);
80+
when(fs.getDevice()).thenReturn(device);
81+
when(fs.isReadOnly()).thenReturn(true);
82+
83+
api.mount("/mnt", fs, null);
84+
assertTrue(api.getMountPoints().containsKey("/mnt"));
85+
86+
api.unmount("mnt");
87+
assertFalse(api.getMountPoints().containsKey("/mnt"));
88+
}
89+
90+
@Test
91+
public void testUnmountReadOnlyFsClosesAndUnregisters() throws Exception {
92+
Device device = mock(Device.class);
93+
FileSystem<?> fs = mock(FileSystem.class);
94+
when(fs.getDevice()).thenReturn(device);
95+
when(fs.isReadOnly()).thenReturn(true);
96+
97+
api.mount("/mnt", fs, null);
98+
api.unmount("/mnt");
99+
100+
verify(fs).close();
101+
assertFalse(api.getMountPoints().containsKey("/mnt"));
102+
}
103+
104+
@Test
105+
public void testUnmountRollbackOnCloseFailure() throws Exception {
106+
Device device = mock(Device.class);
107+
FileSystem<?> fs = mock(FileSystem.class);
108+
when(fs.getDevice()).thenReturn(device);
109+
when(fs.isReadOnly()).thenReturn(false);
110+
111+
api.mount("/mnt", fs, null);
112+
assertTrue(api.getMountPoints().containsKey("/mnt"));
113+
114+
IOException closeException = new IOException("Close failed");
115+
doThrow(closeException).when(fs).close();
116+
117+
try {
118+
api.unmount("/mnt");
119+
fail("Expected IOException");
120+
} catch (IOException e) {
121+
assertEquals("Close failed", e.getMessage());
122+
}
123+
124+
Map<String, FileSystem<?>> mountPoints = api.getMountPoints();
125+
assertTrue("Mount point should be restored after rollback",
126+
mountPoints.containsKey("/mnt"));
127+
assertEquals(fs, mountPoints.get("/mnt"));
128+
}
129+
130+
@Test
131+
public void testUnmountRemovesMountPoint() throws Exception {
132+
Device device = mock(Device.class);
133+
FileSystem<?> fs = mock(FileSystem.class);
134+
when(fs.getDevice()).thenReturn(device);
135+
when(fs.isReadOnly()).thenReturn(true);
136+
137+
api.mount("/mnt", fs, null);
138+
assertTrue(api.getMountPoints().containsKey("/mnt"));
139+
140+
api.unmount("/mnt");
141+
assertFalse(api.getMountPoints().containsKey("/mnt"));
142+
assertEquals(0, api.getMountPoints().size());
143+
}
144+
145+
@Test
146+
public void testUnmountMultipleMountPoints() throws Exception {
147+
Device device1 = mock(Device.class);
148+
Device device2 = mock(Device.class);
149+
FileSystem<?> fs1 = mock(FileSystem.class);
150+
FileSystem<?> fs2 = mock(FileSystem.class);
151+
when(fs1.getDevice()).thenReturn(device1);
152+
when(fs2.getDevice()).thenReturn(device2);
153+
when(fs1.isReadOnly()).thenReturn(true);
154+
when(fs2.isReadOnly()).thenReturn(true);
155+
156+
api.mount("/mnt1", fs1, null);
157+
api.mount("/mnt2", fs2, null);
158+
assertEquals(2, api.getMountPoints().size());
159+
160+
api.unmount("/mnt1");
161+
assertEquals(1, api.getMountPoints().size());
162+
assertTrue(api.getMountPoints().containsKey("/mnt2"));
163+
164+
api.unmount("/mnt2");
165+
assertEquals(0, api.getMountPoints().size());
166+
}
167+
}

0 commit comments

Comments
 (0)