1 /*
2  * Copyright (C) 2020, 2021  Vladimir Panteleev <btdu@cy.md>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License v2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 021110-1307, USA.
17  */
18 
19 /// Main process / subprocess communication protocol
20 module btdu.proto;
21 
22 import ae.utils.array;
23 
24 import core.sys.posix.unistd;
25 
26 import std.exception;
27 import std.meta;
28 import std.traits;
29 
30 import btrfs.c.kerncompat : u64, __u64;
31 
32 struct Error
33 {
34 	const(char)[] msg;
35 	int errno;
36 	const(char)[] path;
37 }
38 
39 struct StartMessage
40 {
41 	ulong totalSize;
42 }
43 
44 struct NewRootMessage
45 {
46 	__u64 rootID, parentRootID;
47 	const(char)[] name;
48 }
49 
50 struct ResultStartMessage
51 {
52 	ulong chunkFlags;
53 	ulong logicalOffset;
54 }
55 
56 struct ResultInodeStartMessage
57 {
58 	u64 rootID;
59 	bool ignoringOffset;
60 }
61 
62 struct ResultInodeErrorMessage
63 {
64 	Error error;
65 }
66 
67 struct ResultInodeEndMessage
68 {
69 }
70 
71 struct ResultMessage
72 {
73 	const(char)[] path;
74 }
75 
76 struct ResultErrorMessage
77 {
78 	Error error;
79 }
80 
81 struct ResultEndMessage
82 {
83 	ulong duration;
84 }
85 
86 struct FatalErrorMessage
87 {
88 	const(char)[] msg;
89 }
90 
91 alias AllMessages = AliasSeq!(
92 	StartMessage,
93 	NewRootMessage,
94 	ResultStartMessage,
95 	ResultInodeStartMessage,
96 	ResultInodeErrorMessage,
97 	ResultInodeEndMessage,
98 	ResultMessage,
99 	ResultErrorMessage,
100 	ResultEndMessage,
101 	FatalErrorMessage,
102 );
103 
104 struct Header
105 {
106 	/// Even when the length is redundant (fixed-size messages),
107 	/// putting it up front allows simplifying deserialization and
108 	/// process entire messages in one go
109 	size_t length;
110 	/// Index into AllMessages
111 	size_t type;
112 }
113 
114 FastAppender!ubyte sendBuf;
115 
116 private void serialize(T)(ref T value)
117 {
118 	static if (!hasIndirections!T)
119 		sendBuf.put(value.bytes);
120 	else
121 	static if (is(T U : U[]))
122 	{
123 		size_t length = value.length;
124 		serialize(length);
125 		static if (!hasIndirections!U)
126 			sendBuf.put(value.bytes);
127 		else
128 			foreach (ref e; value)
129 				serialize(value);
130 	}
131 	else
132 	static if (is(T == struct))
133 	{
134 		foreach (ref f; value.tupleof)
135 			serialize(f);
136 	}
137 	else
138 		static assert(false, "Can't serialize" ~ T.stringof);
139 }
140 
141 private void sendRaw(const(void)[] data)
142 {
143 	auto written = write(STDOUT_FILENO, data.ptr, data.length);
144 	errnoEnforce(written > 0, "write");
145 	data.shift(written);
146 	if (!data.length)
147 		return;
148 	sendRaw(data);
149 }
150 
151 /// Send a message from a subprocess to the main process.
152 void send(T)(auto ref T message)
153 if (staticIndexOf!(T, AllMessages) >= 0)
154 {
155 	Header header;
156 	header.type = staticIndexOf!(T, AllMessages);
157 	sendBuf.clear();
158 	serialize(message);
159 	header.length = Header.sizeof + sendBuf.length;
160 	sendRaw(header.bytes);
161 	sendRaw(sendBuf.peek());
162 }
163 
164 private T deserialize(T)(ref ubyte[] buf)
165 {
166 	static if (!hasIndirections!T)
167 		return (cast(T[])buf.shift(T.sizeof))[0];
168 	else
169 	static if (is(T U : U[]))
170 	{
171 		size_t length = deserialize!size_t(buf);
172 		static if (!hasIndirections!U)
173 			return cast(U[])buf.shift(U.sizeof * length);
174 		else
175 			static assert(false, "Can't deserialize arrays of types with indirections without allocating");
176 	}
177 	else
178 	static if (is(T == struct))
179 	{
180 		T value;
181 		foreach (ref f; value.tupleof)
182 			f = deserialize!(typeof(f))(buf);
183 		return value;
184 	}
185 	else
186 		static assert(false, "Can't deserialize" ~ T.stringof);
187 }
188 
189 /// Decode received data.
190 /// Returns how many bytes should be read before calling this function again.
191 /// H should implement handleMessage(M) overloads for every M in AllMessages.
192 size_t parse(H)(ref ubyte[] buf, ref H handler)
193 {
194 	while (true)
195 	{
196 		if (buf.length < Header.sizeof)
197 			return Header.sizeof - buf.length;
198 
199 		auto header = (cast(Header*)buf.ptr);
200 		if (buf.length < header.length)
201 			return header.length - buf.length;
202 
203 		buf.shift(Header.sizeof);
204 
205 	typeSwitch:
206 		switch (header.type)
207 		{
208 			foreach (i, Message; AllMessages)
209 			{
210 				case i:
211 					handler.handleMessage(deserialize!Message(buf));
212 					break typeSwitch;
213 			}
214 			default:
215 				assert(false, "Unknown message");
216 		}
217 	}
218 	assert(false, "Unreachable");
219 }