1 /** 2 Copyright: Copyright (c) 2019, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module dsnapshot.backend; 7 8 import logger = std.experimental.logger; 9 import std.algorithm : map, filter; 10 import std.datetime : SysTime; 11 12 import dsnapshot.backend.crypt; 13 import dsnapshot.backend.rsync; 14 import dsnapshot.config; 15 import dsnapshot.exception; 16 import dsnapshot.from; 17 import dsnapshot.process; 18 public import dsnapshot.layout : Layout; 19 public import dsnapshot.types; 20 21 @safe: 22 23 /** 24 * Error handling is via exceptions. 25 */ 26 interface SyncBackend { 27 /// Execute a command on the host that is the destination of the snapshots. 28 void remoteCmd(const RemoteHost host, const RemoteSubCmd cmd, const string path); 29 30 /// Update layout of the snapshots at the destination. 31 Layout update(Layout layout); 32 33 /// Publish the snapshot in dst. 34 void publishSnapshot(const string newSnapshot); 35 36 /// Remove discarded snapshots. 37 void removeDiscarded(const Layout layout); 38 39 /// Sync from src to dst. 40 void sync(const Layout layout, const SnapshotConfig snapshot, const string nameOfNewSnapshot); 41 42 /// Restore dst to src. 43 void restore(const Layout layout, const SnapshotConfig snapshot, 44 const SysTime time, const string restoreTo); 45 46 /// The flow of data that the backend handles. 47 Flow flow(); 48 } 49 50 SyncBackend makeSyncBackend(SnapshotConfig s) { 51 auto rval = s.syncCmd.match!((None a) { return null; }, 52 (RsyncConfig a) => new RsyncBackend(a, s.remoteCmd, null)); 53 54 if (rval is null) { 55 logger.infof("No backend specified for %s. Supported are: rsync", s.name); 56 throw new Exception(null); 57 } 58 return rval; 59 } 60 61 SyncBackend makeSyncBackend(SnapshotConfig s, const dsnapshot.config.Config.Backup backup) { 62 auto rval = s.syncCmd.match!((None a) { return null; }, 63 (RsyncConfig a) => new RsyncBackend(a, s.remoteCmd, backup.ignoreRsyncErrorCodes)); 64 65 if (rval is null) { 66 logger.infof("No backend specified for %s. Supported are: rsync", s.name); 67 throw new Exception(null); 68 } 69 return rval; 70 } 71 72 /** 73 */ 74 class CryptException : Exception { 75 enum Kind { 76 generic, 77 wrongPassword, 78 errorWhenOpening, 79 errorWhenClosing, 80 noEncryptedSrc, 81 } 82 83 Kind kind; 84 85 this(string msg, Kind kind = Kind.generic, string file = __FILE__, int line = __LINE__) @safe pure nothrow { 86 super(msg, file, line); 87 this.kind = kind; 88 } 89 } 90 91 /** Encryption of the snapshot destination. 92 * 93 * One backend only hold at most one target open at a time. 94 * 95 * Exceptions are used to signal errors. 96 * 97 * The API excepts this call sequence: 98 * One open followed by multiple close. 99 * or 100 * failed open followed by close. 101 */ 102 interface CryptBackend { 103 /** Open the encrypted destination. 104 * 105 * Params: 106 * decrypted = where to make the decrypted data visible (mount it). 107 */ 108 void open(const string decrypted); 109 110 /// Close the encrypted destination. 111 void close(); 112 113 /// If the crypto backend supports hard links and thus --link-dest with e.g. rsync works. 114 bool supportHardLinks(); 115 116 /// If the crypto backend supports that the encrypted data is on a remote host. 117 bool supportRemoteEncryption(); 118 } 119 120 // TODO: root and mountPoint is probably not "generic" but until another crypt backend is added this will have to do. 121 CryptBackend makeCrypBackend(const CryptConfig c) { 122 return c.match!((const None a) => cast(CryptBackend) new PlainText, 123 (const EncFsConfig a) => new EncFs(a.configFile, a.passwd, a.encryptedPath, 124 a.mountCmd, a.mountFuseOpts, a.unmountCmd, a.unmountFuseOpts)); 125 } 126 127 /** Open the destination that flow point to. 128 * 129 * Throws an exception if the destination is not local. 130 */ 131 void open(CryptBackend be, const Flow flow) { 132 flow.match!((None a) {}, (FlowLocal a) { be.open(a.dst.value); }, (FlowRsyncToLocal a) { 133 be.open(a.dst.value); 134 }, (FlowLocalToRsync a) { 135 if (be.supportRemoteEncryption) 136 be.open(null); // TODO for now not implemented 137 else 138 throw new CryptException("Opening an encryption on a remote host is not supported", 139 CryptException.Kind.errorWhenOpening); 140 }); 141 }