From b340f4a17c05b6ca61876a17a85901d0a5cd922c Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 12 May 2017 17:16:25 +0000 Subject: [PATCH] i2psnark: Better handling of read-only i2psnark dir (ticket #1990) Prevent add/create/remove/delete More handling of RuntimeException via Snark.fatal() in SnarkManager --- .../java/src/org/klomp/snark/Snark.java | 7 +++- .../src/org/klomp/snark/SnarkManager.java | 41 ++++++++++++------- .../org/klomp/snark/web/I2PSnarkServlet.java | 41 +++++++++++++++---- 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 203f601957..2a43b6cebb 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -1243,8 +1243,13 @@ public class Snark } catch (IOException ioe) { if (storage != null) { try { storage.close(); } catch (IOException ioee) {} + // clear storage, we have a mess if we have non-null storage and null metainfo, + // as on restart, Storage.reopen() will throw an ioe + storage = null; } - fatal("Could not check or create storage", ioe); + // TODO we're still in an inconsistent state, won't work if restarted + // (PeerState "disconnecting seed that connects to seeds" + fatal("Could not create data files", ioe); } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index cc379357e1..77964cb6b4 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -731,7 +731,7 @@ public class SnarkManager implements CompleteListener, ClientApp { return new SHA1Hash(ih); } - /** null to set initial defaults */ + /** @param filename null to set initial defaults */ public void loadConfig(String filename) { synchronized(_configLock) { locked_loadConfig(filename); @@ -896,7 +896,14 @@ public class SnarkManager implements CompleteListener, ClientApp { _util.setRatingsEnabled(Boolean.parseBoolean(_config.getProperty(PROP_RATINGS, "true"))); _util.setCommentsEnabled(Boolean.parseBoolean(_config.getProperty(PROP_COMMENTS, "true"))); _util.setCommentsName(_config.getProperty(PROP_COMMENTS_NAME, "")); - getDataDir().mkdirs(); + File dd = getDataDir(); + if (dd.isDirectory()) { + if (!dd.canWrite()) + addMessage(_t("No write permissions for data directory") + ": " + dd); + } else { + if (!dd.mkdirs()) + addMessage(_t("Data directory cannot be created") + ": " + dd); + } initTrackerMap(); } @@ -1031,6 +1038,8 @@ public class SnarkManager implements CompleteListener, ClientApp { } else if (!dd.canRead()) { addMessage(_t("Unreadable") + ": " + dataDir); } else { + if (!dd.canWrite()) + addMessage(_t("No write permissions for data directory") + ": " + dataDir); changed = true; interruptMonitor = true; _config.setProperty(PROP_DIR, dataDir); @@ -2640,6 +2649,7 @@ public class SnarkManager implements CompleteListener, ClientApp { /** * If not connected, thread it, otherwise inline + * @throws RuntimeException via Snark.fatal() * @since 0.9.1 */ public void startTorrent(byte[] infoHash) { @@ -2654,6 +2664,7 @@ public class SnarkManager implements CompleteListener, ClientApp { /** * If not connected, thread it, otherwise inline + * @throws RuntimeException via Snark.fatal() * @since 0.9.23 */ public void startTorrent(Snark snark) { @@ -2701,17 +2712,14 @@ public class SnarkManager implements CompleteListener, ClientApp { private final Snark snark; public ThreadedStarter(Snark s) { snark = s; } public void run() { - try { - run2(); - } catch (RuntimeException e) { - _log.error("Error starting", e); - } - } - - private void run2() { if (snark != null) { - if (snark.isStopped()) - snark.startTorrent(); + if (snark.isStopped()) { + try { + snark.startTorrent(); + } catch (RuntimeException re) { + // Snark.fatal() will log and call fatal() here for user message before throwing + } + } } else { startAll(); } @@ -2724,8 +2732,13 @@ public class SnarkManager implements CompleteListener, ClientApp { */ private void startAll() { for (Snark snark : _snarks.values()) { - if (snark.isStopped()) - snark.startTorrent(); + if (snark.isStopped()) { + try { + snark.startTorrent(); + } catch (RuntimeException re) { + // Snark.fatal() will log and call fatal() here for user message before throwing + } + } } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 4a73cb27bf..1612ff2fab 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -987,6 +987,11 @@ public class I2PSnarkServlet extends BasicServlet { } } } + File dd = _manager.getDataDir(); + if (!dd.canWrite()) { + _manager.addMessage(_t("No write permissions for data directory") + ": " + dd); + return; + } if (newURL.startsWith("http://")) { FetchAndAdd fetch = new FetchAndAdd(_context, _manager, newURL, dir); _manager.addDownloader(fetch); @@ -1045,12 +1050,18 @@ public class I2PSnarkServlet extends BasicServlet { _manager.addMessage(_t("Magnet deleted: {0}", name)); return; } - _manager.stopTorrent(snark, true); - // should we delete the torrent file? - // yeah, need to, otherwise it'll get autoadded again (at the moment File f = new File(name); - f.delete(); - _manager.addMessage(_t("Torrent file deleted: {0}", f.getAbsolutePath())); + File dd = _manager.getDataDir(); + boolean canDelete = dd.canWrite() || !f.exists(); + _manager.stopTorrent(snark, canDelete); + // TODO race here with the DirMonitor, could get re-added + if (f.delete()) { + _manager.addMessage(_t("Torrent file deleted: {0}", f.getAbsolutePath())); + } else if (f.exists()) { + if (!canDelete) + _manager.addMessage(_t("No write permissions for data directory") + ": " + dd); + _manager.addMessage(_t("Torrent file could not be deleted: {0}", f.getAbsolutePath())); + } break; } } @@ -1074,10 +1085,19 @@ public class I2PSnarkServlet extends BasicServlet { _manager.addMessage(_t("Magnet deleted: {0}", name)); return; } - _manager.stopTorrent(snark, true); File f = new File(name); - f.delete(); - _manager.addMessage(_t("Torrent file deleted: {0}", f.getAbsolutePath())); + File dd = _manager.getDataDir(); + boolean canDelete = dd.canWrite() || !f.exists(); + _manager.stopTorrent(snark, canDelete); + // TODO race here with the DirMonitor, could get re-added + if (f.delete()) { + _manager.addMessage(_t("Torrent file deleted: {0}", f.getAbsolutePath())); + } else if (f.exists()) { + if (!canDelete) + _manager.addMessage(_t("No write permissions for data directory") + ": " + dd); + _manager.addMessage(_t("Torrent file could not be deleted: {0}", f.getAbsolutePath())); + return; + } Storage storage = snark.getStorage(); if (storage == null) break; @@ -1178,6 +1198,11 @@ public class I2PSnarkServlet extends BasicServlet { // announceURL = announceURLOther; if (baseFile.exists()) { + File dd = _manager.getDataDir(); + if (!dd.canWrite()) { + _manager.addMessage(_t("No write permissions for data directory") + ": " + dd); + return; + } String torrentName = baseFile.getName(); if (torrentName.toLowerCase(Locale.US).endsWith(".torrent")) { _manager.addMessage(_t("Cannot add a torrent ending in \".torrent\": {0}", baseFile.getAbsolutePath())); -- GitLab