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.types;
7 
8 public import sumtype;
9 
10 @safe:
11 
12 /// Snapshots that are in the progress of being transfered have this suffix.
13 immutable snapshotInProgressSuffix = "-in-progress";
14 /// The actual rsync'ed data is in this directory.
15 immutable snapshotData = "data";
16 /// name of the fakeroot environment.
17 immutable snapshotFakerootEnv = "fakeroot.env";
18 /// User id that is replaced by the actual path to the file to save the env in
19 immutable snapshotFakerootSaveEnvId = "$$SAVE_ENV_FILE$$";
20 /// The name of the symlink pointing to the latest snapshot.
21 immutable snapshotLatest = "latest";
22 
23 /// Tag a string as a path and make it absolute+normalized.
24 struct Path {
25     import std.path : absolutePath, buildNormalizedPath, buildPath;
26 
27     private string value_;
28 
29     this(Path p) @safe pure nothrow @nogc {
30         value_ = p.value_;
31     }
32 
33     this(string p) @safe {
34         value_ = p.absolutePath.buildNormalizedPath;
35     }
36 
37     Path dirName() @safe const {
38         import std.path : dirName;
39 
40         return Path(value_.dirName);
41     }
42 
43     string baseName() @safe const {
44         import std.path : baseName;
45 
46         return value_.baseName;
47     }
48 
49     void opAssign(string rhs) @safe pure {
50         value_ = rhs.absolutePath.buildNormalizedPath;
51     }
52 
53     void opAssign(typeof(this) rhs) @safe pure nothrow {
54         value_ = rhs.value_;
55     }
56 
57     Path opBinary(string op)(string rhs) @safe const {
58         static if (op == "~") {
59             return Path(buildPath(value_, rhs));
60         } else
61             static assert(false, typeof(this).stringof ~ " does not have operator " ~ op);
62     }
63 
64     void opOpAssign(string op)(string rhs) @safe nothrow {
65         static if (op == "~=") {
66             value_ = buildNormalizedPath(value_, rhs);
67         } else
68             static assert(false, typeof(this).stringof ~ " does not have operator " ~ op);
69     }
70 
71     T opCast(T : string)() {
72         return value_;
73     }
74 
75     string toString() @safe pure nothrow const @nogc {
76         return value_;
77     }
78 
79     import std.range : isOutputRange;
80 
81     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
82         import std.range : put;
83 
84         put(w, value_);
85     }
86 }
87 
88 // TODO: maybe rename to SnapshotName?
89 /// Name of an existing snapshot.
90 struct Name {
91     string value;
92 }
93 
94 struct SnapshotConfig {
95     import dsnapshot.layout : Layout;
96 
97     /// Name of this snapshot
98     string name;
99 
100     SyncCmd syncCmd;
101 
102     // TODO: change to a generic "execute commmand".
103     /// How to interact with the destination for e.g. list snapshots.
104     RemoteCmd remoteCmd;
105 
106     /// The snapshot layout to use.
107     Layout layout;
108 
109     /// Hooks to run when taking a new snapshot.
110     Hooks hooks;
111 
112     /// Crypt config.
113     CryptConfig crypt;
114 }
115 
116 struct Hooks {
117     string[] preExec;
118     string[] postExec;
119 }
120 
121 alias CryptConfig = SumType!(None, EncFsConfig);
122 
123 struct EncFsConfig {
124     /// The xml config for encfs
125     string configFile;
126     /// Where the encrypted encfs is. The SyncCmd, via Flow, determines where it is mounted.
127     string encryptedPath;
128     string passwd;
129     string[] mountCmd = ["encfs", "-i", "1"];
130     string[] mountFuseOpts;
131     string[] unmountCmd = ["encfs", "-u"];
132     string[] unmountFuseOpts;
133 }
134 
135 alias RemoteCmd = SumType!(SshRemoteCmd);
136 
137 enum RemoteSubCmd {
138     none,
139     lsDirs,
140     mkdirRecurse,
141     rmdirRecurse,
142     /// Change the status of a snapshot from "in progress" to available.
143     publishSnapshot,
144     /// Transfer the remote fakeroot.env to a local representation.
145     fakerootStats,
146 }
147 
148 /// Info of how to execute dsnapshot on the remote host.
149 struct SshRemoteCmd {
150     /// Path/lookup to use to find dsnapshot on the remote host.
151     string dsnapshot = "dsnapshot";
152 
153     /// dsnapshot is executed via ssh or equivalent command.
154     string[] rsh = ["ssh"];
155 
156     /// Returns: a cmd to execute with `std.process`.
157     string[] toCmd(RemoteSubCmd subCmd, string addr, string path) @safe pure const {
158         import std.conv : to;
159 
160         return rsh ~ [
161             addr, dsnapshot, "remotecmd", "--cmd", subCmd.to!string, "--path",
162             path
163         ];
164     }
165 }
166 
167 alias SyncCmd = SumType!(None, RsyncConfig);
168 
169 struct None {
170 }
171 
172 struct LocalAddr {
173     string value;
174 
175     this(string v) {
176         import std.path : expandTilde;
177 
178         value = v.expandTilde;
179     }
180 }
181 
182 struct RemoteHost {
183     string addr;
184     string path;
185 }
186 
187 string fixRemteHostForRsync(const string a) {
188     if (a.length != 0 && a[$ - 1] != '/')
189         return a ~ "/";
190     return a;
191 }
192 
193 string makeRsyncAddr(string addr, string path) {
194     import std.format : format;
195 
196     return format("%s:%s", addr, path);
197 }
198 
199 /// Local flow of data.
200 struct FlowLocal {
201     LocalAddr src;
202     LocalAddr dst;
203 }
204 
205 /// Flow of data using a remote rsync address to a local destination.
206 struct FlowRsyncToLocal {
207     RemoteHost src;
208     LocalAddr dst;
209 }
210 
211 struct FlowLocalToRsync {
212     LocalAddr src;
213     RemoteHost dst;
214 }
215 
216 alias Flow = SumType!(None, FlowLocal, FlowRsyncToLocal, FlowLocalToRsync);
217 
218 struct RsyncConfig {
219     Flow flow;
220 
221     /// If --link-dest should be used with rsync
222     bool useLinkDest = true;
223 
224     /// One filesystem, don't cross partitions within a backup point.
225     bool oneFs = true;
226 
227     /// If fakeroot should be used for this snapshot
228     bool useFakeRoot = false;
229 
230     /// Arguments to use with fakeroot
231     string[] rsyncFakerootArgs = ["--rsync-path"];
232     string[] fakerootArgs = [
233         "fakeroot", "-u", "-i", snapshotFakerootSaveEnvId, "-s",
234         snapshotFakerootSaveEnvId
235     ];
236 
237     /// Low process and io priority
238     bool lowPrio = true;
239 
240     /// Patterns to exclude from rsync.
241     string[] exclude;
242 
243     /// Rsync command to use.
244     string cmdRsync = "rsync";
245 
246     /// disk usage command to use.
247     string[] cmdDiskUsage = ["du", "-hcs"];
248 
249     /// rsh argument for rsync, --rsh=<rsh>.
250     string rsh;
251 
252     /// Configure how to print the progress bar when in interactive shell, if any.
253     string[] progress = ["--info=stats1", "--info=progress2"];
254 
255     // -a archive mode; equals -rlptgoD (no -H,-A,-X)
256     // -r recursive
257     // -l copy symlinks as symlinks
258     // -p preserve permissions
259     // -t preserve modification times
260     // -g preserve groups permissions
261     // -o preserve owner permission
262     // -D preserve devices
263     // --delete delete files from dest if they are removed in src
264     // --partial keep partially transferred files
265     // --delete-excluded also delete excluded files from dest dirs
266     // --chmod change permission on transfered files
267     // --numeric-ids don't map uid/gid values by user/group name
268     // --modify-window set the accuracy for mod-time comparisons
269     string[] backupArgs = [
270         "-ahv", "--numeric-ids", "--modify-window", "1", "--delete",
271         "--delete-excluded", "--partial"
272     ];
273 
274     string[] restoreArgs = ["-ahv", "--numeric-ids", "--modify-window", "1"];
275 }