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