1 /*
2  * Copyright (C) 2020, 2021, 2022  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 /// Sampling subprocess implementation
20 module btdu.sample;
21 
22 import core.stdc.errno;
23 import core.sys.posix.fcntl;
24 import core.sys.posix.unistd;
25 
26 import std.algorithm.iteration;
27 import std.datetime.stopwatch;
28 import std.exception;
29 import std.random;
30 import std.string;
31 
32 import ae.sys.shutdown;
33 import ae.utils.appender;
34 import ae.utils.time : stdTime;
35 
36 import btrfs;
37 import btrfs.c.kerncompat;
38 import btrfs.c.kernel_shared.ctree;
39 
40 import btdu.proto;
41 
42 void subprocessMain(string fsPath)
43 {
44 	try
45 	{
46 		// Ignore SIGINT/SIGTERM, because the main process will handle it for us.
47 		// We want the main process to receive and process the signal before any child
48 		// processes do, otherwise the main process doesn't know if the child exited due to an
49 		// abrupt failure or simply because it received and processed the signal before it did.
50 		addShutdownHandler((reason) {});
51 
52 		// if (!quiet) stderr.writeln("Opening filesystem...");
53 		int fd = open(fsPath.toStringz, O_RDONLY);
54 		errnoEnforce(fd >= 0, "open");
55 
56 		// if (!quiet) stderr.writeln("Reading chunks...");
57 		static struct ChunkInfo
58 		{
59 			u64 offset;
60 			btrfs_chunk chunk; /// No stripes
61 		}
62 		ChunkInfo[] chunks;
63 		enumerateChunks(fd, (u64 offset, const ref btrfs_chunk chunk) {
64 			chunks ~= ChunkInfo(offset, chunk);
65 		});
66 
67 		ulong totalSize = chunks.map!((ref chunk) => chunk.chunk.length).sum;
68 		// if (!quiet) stderr.writefln("Found %d chunks with a total size of %d.", globalParams.chunks.length, globalParams.totalSize);
69 		send(StartMessage(totalSize));
70 
71 		while (true)
72 		{
73 			auto targetPos = uniform(0, totalSize);
74 			u64 pos = 0;
75 			foreach (ref chunk; chunks)
76 			{
77 				auto end = pos + chunk.chunk.length;
78 				if (end > targetPos)
79 				{
80 					auto offset = chunk.offset + (targetPos - pos);
81 					send(ResultStartMessage(chunk.chunk.type, offset));
82 					auto sw = StopWatch(AutoStart.yes);
83 
84 					if (chunk.chunk.type & BTRFS_BLOCK_GROUP_DATA)
85 					{
86 						foreach (ignoringOffset; [false, true])
87 						{
88 							try
89 							{
90 								bool called;
91 								logicalIno(fd, offset,
92 									(u64 inode, u64 offset, u64 rootID)
93 									{
94 										called = true;
95 
96 										// writeln("- ", inode, " ", offset, " ", root);
97 										cast(void) offset; // unused
98 
99 										// Send new roots before the inode start
100 										cast(void)getRoot(fd, rootID);
101 
102 										send(ResultInodeStartMessage(rootID, ignoringOffset));
103 
104 										try
105 										{
106 											static FastAppender!char pathBuf;
107 											pathBuf.clear();
108 											pathBuf.put(fsPath);
109 
110 											void putRoot(u64 rootID)
111 											{
112 												auto root = getRoot(fd, rootID);
113 												if (root is Root.init)
114 													enforce(rootID == BTRFS_FS_TREE_OBJECTID, "Unresolvable root");
115 												else
116 													putRoot(root.parent);
117 												if (root.path)
118 												{
119 													pathBuf.put('/');
120 													pathBuf.put(root.path);
121 												}
122 											}
123 											putRoot(rootID);
124 											pathBuf.put('\0');
125 
126 											int rootFD = open(pathBuf.get().ptr, O_RDONLY);
127 											if (rootFD < 0)
128 											{
129 												send(ResultInodeErrorMessage(btdu.proto.Error("open", errno, pathBuf.get()[0 .. $-1])));
130 												return;
131 											}
132 											scope(exit) close(rootFD);
133 
134 											inoPaths(rootFD, inode, (char[] fn) {
135 												send(ResultMessage(fn));
136 											});
137 											send(ResultInodeEndMessage());
138 										}
139 										catch (Exception e)
140 											send(ResultInodeErrorMessage(e.toError));
141 									},
142 									ignoringOffset,
143 								);
144 								if (!called && !ignoringOffset)
145 									continue;
146 							}
147 							catch (Exception e)
148 								send(ResultErrorMessage(e.toError));
149 							break;
150 						}
151 					}
152 					send(ResultEndMessage(sw.peek.stdTime));
153 					break;
154 				}
155 				pos = end;
156 			}
157 		}
158 	}
159 	catch (Throwable e)
160 	{
161 		debug
162 			send(FatalErrorMessage(e.toString()));
163 		else
164 			send(FatalErrorMessage(e.msg));
165 	}
166 }
167 
168 private:
169 
170 struct Root
171 {
172 	u64 parent;
173 	string path;
174 }
175 Root[u64] roots;
176 
177 /// Performs memoized resolution of the path for a btrfs root object.
178 Root getRoot(int fd, __u64 rootID)
179 {
180 	return roots.require(rootID, {
181 		Root result;
182 		findRootBackRef(
183 			fd,
184 			rootID,
185 			(
186 				__u64 parentRootID,
187 				__u64 dirID,
188 				__u64 sequence,
189 				char[] name,
190 			) {
191 				cast(void) sequence; // unused
192 
193 				inoLookup(
194 					fd,
195 					parentRootID,
196 					dirID,
197 					(char[] dirPath)
198 					{
199 						if (result !is Root.init)
200 							throw new Exception("Multiple root locations");
201 						result.path = cast(string)(dirPath ~ name);
202 						result.parent = parentRootID;
203 					}
204 				);
205 			}
206 		);
207 
208 		// Ensure parents are written first
209 		if (result !is Root.init)
210 			cast(void)getRoot(fd, result.parent);
211 
212 		send(NewRootMessage(rootID, result.parent, result.path));
213 
214 		return result;
215 	}());
216 }
217 
218 btdu.proto.Error toError(Exception e)
219 {
220 	btdu.proto.Error error;
221 	error.msg = e.msg;
222 	if (auto ex = cast(ErrnoException) e)
223 	{
224 		// Convert to errno + string
225 		import core.stdc.string : strlen, strerror_r;
226 		char[1024] buf = void;
227 		auto s = strerror_r(errno, buf.ptr, buf.length);
228 
229 		import std.range : chain;
230 		auto suffix = chain(" (".representation, s[0 .. s.strlen].representation, ")".representation);
231 		if (error.msg.endsWith(suffix))
232 		{
233 			error.msg = error.msg[0 .. $ - suffix.length];
234 			error.errno = ex.errno;
235 		}
236 		else
237 			debug assert(false, "Unexpected ErrnoException message: " ~ error.msg);
238 	}
239 	return error;
240 }