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 }