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 }