diff --git a/fs/build-tests.xml b/fs/build-tests.xml
index 9f79685b1e..c74941b0e4 100644
--- a/fs/build-tests.xml
+++ b/fs/build-tests.xml
@@ -40,6 +40,7 @@ help Output these messages
+
diff --git a/fs/descriptors/org.jnode.fs.command.xml b/fs/descriptors/org.jnode.fs.command.xml
index 3fc2ecc12c..dfd7443e1f 100644
--- a/fs/descriptors/org.jnode.fs.command.xml
+++ b/fs/descriptors/org.jnode.fs.command.xml
@@ -42,6 +42,7 @@
+
@@ -59,6 +60,12 @@
+
+
+
+
+
+
diff --git a/fs/src/commands/org/jnode/fs/command/SyncCommand.java b/fs/src/commands/org/jnode/fs/command/SyncCommand.java
new file mode 100644
index 0000000000..f92a7a740a
--- /dev/null
+++ b/fs/src/commands/org/jnode/fs/command/SyncCommand.java
@@ -0,0 +1,130 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2003-2015 JNode.org
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.jnode.fs.command;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import org.jnode.driver.ApiNotFoundException;
+import org.jnode.driver.Device;
+import org.jnode.driver.block.BlockDeviceAPI;
+import org.jnode.fs.FileSystem;
+import org.jnode.fs.service.FileSystemService;
+import org.jnode.fs.spi.AbstractFileSystem;
+import org.jnode.naming.InitialNaming;
+import org.jnode.shell.AbstractCommand;
+import org.jnode.shell.syntax.Argument;
+import org.jnode.shell.syntax.FileArgument;
+
+/**
+ * Flush filesystem buffers to disk.
+ */
+public class SyncCommand extends AbstractCommand {
+
+ private final FileArgument argPath =
+ new FileArgument("path", Argument.OPTIONAL,
+ "flush only the filesystem mounted at this path");
+
+ public SyncCommand() {
+ super("Flush filesystem buffers to disk");
+ registerArguments(argPath);
+ }
+
+ public static void main(String[] args) throws Exception {
+ new SyncCommand().execute(args);
+ }
+
+ public void execute() throws Exception {
+ FileSystemService fss = InitialNaming.lookup(FileSystemService.NAME);
+ PrintWriter out = getOutput().getPrintWriter();
+ PrintWriter err = getError().getPrintWriter();
+
+ if (argPath.isSet()) {
+ String path = argPath.getValue().getCanonicalPath();
+ Map.Entry> match =
+ findMountPoint(fss.getMountPoints(), path);
+ if (match == null) {
+ err.println("No filesystem mounted at " + path);
+ exit(1);
+ }
+ flushFileSystem(match.getValue(), match.getKey(), out, err);
+ } else {
+ int errors = 0;
+ for (Map.Entry> e : fss.getMountPoints().entrySet()) {
+ try {
+ flushFileSystem(e.getValue(), e.getKey(), out, err);
+ } catch (IOException ex) {
+ err.println("sync failed on " + e.getKey() + ": " + ex.getMessage());
+ errors++;
+ }
+ }
+ if (errors > 0) exit(1);
+ }
+ }
+
+ public static Map.Entry> findMountPoint(
+ Map> mountPoints, String path) {
+ FileSystem> target = null;
+ String mountPoint = null;
+ for (Map.Entry> e : mountPoints.entrySet()) {
+ String key = e.getKey();
+ if (path.equals(key) || path.startsWith(key + "/") ||
+ (key.length() == 1 && path.length() > 0)) {
+ if (target == null || e.getKey().length() > mountPoint.length()) {
+ target = e.getValue();
+ mountPoint = e.getKey();
+ }
+ }
+ }
+ if (target == null) {
+ return null;
+ }
+ final FileSystem> t = target;
+ final String mp = mountPoint;
+ return new Map.Entry>() {
+ public String getKey() { return mp; }
+ public FileSystem> getValue() { return t; }
+ public FileSystem> setValue(FileSystem> v) { throw new UnsupportedOperationException(); }
+ };
+ }
+
+ private void flushFileSystem(FileSystem> fs, String mountPoint,
+ PrintWriter out, PrintWriter err) throws IOException {
+ if (fs instanceof AbstractFileSystem) {
+ ((AbstractFileSystem>) fs).flush();
+ }
+ Device device = fs.getDevice();
+ boolean deviceFlushed = false;
+ if (device != null) {
+ try {
+ BlockDeviceAPI api = device.getAPI(BlockDeviceAPI.class);
+ api.flush();
+ deviceFlushed = true;
+ } catch (ApiNotFoundException ex) {
+ }
+ }
+ out.print("synced " + mountPoint);
+ if (!deviceFlushed) {
+ out.print(" (device flush skipped)");
+ }
+ out.println();
+ }
+}
diff --git a/fs/src/test/org/jnode/test/fs/command/SyncCommandTest.java b/fs/src/test/org/jnode/test/fs/command/SyncCommandTest.java
new file mode 100644
index 0000000000..31f9a5d300
--- /dev/null
+++ b/fs/src/test/org/jnode/test/fs/command/SyncCommandTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2003-2015 JNode.org
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.jnode.test.fs.command;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.jnode.driver.Device;
+import org.jnode.fs.FSEntry;
+import org.jnode.fs.FileSystem;
+import org.jnode.fs.FileSystemType;
+import org.jnode.fs.command.SyncCommand;
+
+public class SyncCommandTest extends TestCase {
+
+ private Map> mountPoints;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mountPoints = new HashMap>();
+ mountPoints.put("/", new StubFileSystem("/"));
+ mountPoints.put("/home", new StubFileSystem("/home"));
+ mountPoints.put("/home/user", new StubFileSystem("/home/user"));
+ }
+
+ public void testExactMountPointMatch() {
+ Map.Entry> result =
+ SyncCommand.findMountPoint(mountPoints, "/home");
+ assertNotNull(result);
+ assertEquals("/home", result.getKey());
+ }
+
+ public void testLongestPrefixMatch() {
+ Map.Entry> result =
+ SyncCommand.findMountPoint(mountPoints, "/home/user/docs");
+ assertNotNull(result);
+ assertEquals("/home/user", result.getKey());
+ }
+
+ public void testNoMatchingMountPoint() {
+ Map> noRoot = new HashMap>();
+ noRoot.put("/home", new StubFileSystem("/home"));
+ noRoot.put("/home/user", new StubFileSystem("/home/user"));
+ Map.Entry> result =
+ SyncCommand.findMountPoint(noRoot, "/var/log");
+ assertNull(result);
+ }
+
+ public void testRootMatch() {
+ Map.Entry> result =
+ SyncCommand.findMountPoint(mountPoints, "/var/log");
+ assertNotNull(result);
+ assertEquals("/", result.getKey());
+ }
+
+ public void testExactRootMatch() {
+ Map.Entry> result =
+ SyncCommand.findMountPoint(mountPoints, "/");
+ assertNotNull(result);
+ assertEquals("/", result.getKey());
+ }
+
+ private static class StubFileSystem implements FileSystem {
+ private final String mp;
+
+ StubFileSystem(String mp) {
+ this.mp = mp;
+ }
+
+ public FileSystemType extends FileSystem> getType() {
+ return null;
+ }
+
+ public Device getDevice() {
+ return null;
+ }
+
+ public FSEntry getRootEntry() throws IOException {
+ return null;
+ }
+
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public void close() throws IOException {
+ }
+
+ public boolean isClosed() {
+ return false;
+ }
+
+ public long getTotalSpace() throws IOException {
+ return 0;
+ }
+
+ public long getFreeSpace() throws IOException {
+ return 0;
+ }
+
+ public long getUsableSpace() throws IOException {
+ return 0;
+ }
+
+ public String getVolumeName() throws IOException {
+ return mp;
+ }
+ }
+}