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