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 }