1 /** 2 Copyright: Copyright (c) 2019, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module dsnapshot.stats; 7 8 import logger = std.experimental.logger; 9 10 import dsnapshot.types : Path; 11 12 version (unittest) { 13 import unit_threaded.assertions; 14 } 15 16 @safe: 17 18 /// Stats for a dev+inode as it is in a fakeroot environment. 19 struct FakerootStat { 20 static struct Node { 21 // ID of device containing file 22 ulong dev; 23 // Inode number 24 ulong inode; 25 26 size_t toHash() @safe pure nothrow const @nogc scope { 27 auto a = dev.hashOf(); 28 return inode.hashOf(a); // mixing two hash values 29 } 30 31 bool opEquals()(auto ref const S s) const { 32 return s.dev == dev && s.inode == inode; 33 } 34 } 35 36 Node node; 37 alias node this; 38 39 // File type and mode 40 ulong mode; 41 // User ID of owner 42 ulong uid; 43 // Group ID of owner 44 ulong gid; 45 // Device ID (if special file) 46 ulong rdev; 47 48 import std.range : isOutputRange; 49 50 string toString() @safe pure const { 51 import std.array : appender; 52 53 auto buf = appender!string; 54 toString(buf); 55 return buf.data; 56 } 57 58 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 59 import std.format : formattedWrite; 60 import std.range : put; 61 62 put(w, typeof(this).stringof); 63 put(w, "("); 64 formattedWrite(w, "dev=%s", node.dev); 65 formattedWrite(w, ",inode=%s", node.inode); 66 formattedWrite(w, ",hash=%s", node.toHash); 67 formattedWrite(w, ",mode=%s", mode); 68 formattedWrite(w, ",uid=%s", uid); 69 formattedWrite(w, ",gid=%s", gid); 70 formattedWrite(w, ",rdev=%s", rdev); 71 put(w, ")"); 72 } 73 } 74 75 /** Parse a fakeroot configuration line. 76 * 77 * Example: 78 * dev=37,ino=15312816,mode=100664,uid=1000,gid=1000,rdev=0 79 */ 80 FakerootStat fromFakeroot(const(char)[] s) { 81 import std.format : formattedRead; 82 83 typeof(return) rval; 84 ulong nlink; 85 s.formattedRead!"dev=%x,ino=%d,mode=%o,uid=%d,gid=%d,nlink=%d,rdev=%d"(rval.dev, 86 rval.inode, rval.mode, rval.uid, rval.gid, nlink, rval.rdev); 87 return rval; 88 } 89 90 @("shall convert a fakeroot line to FakerootStat") 91 unittest { 92 auto res = fromFakeroot("dev=37,ino=15312816,mode=100664,uid=1000,gid=1000,nlink=1,rdev=0"); 93 res.dev.shouldEqual(55); 94 res.inode.shouldEqual(15312816); 95 res.mode.shouldEqual(33204); 96 res.uid.shouldEqual(1000); 97 res.gid.shouldEqual(1000); 98 res.rdev.shouldEqual(0); 99 } 100 101 struct FakerootDb { 102 import std.typecons : Nullable; 103 104 alias Set = FakerootStat[FakerootStat.Node]; 105 Set db; 106 107 void put(FakerootStat v) { 108 db[v.node] = v; 109 } 110 111 Nullable!FakerootStat get(FakerootStat.Node node) { 112 typeof(return) rval; 113 if (auto v = node in db) { 114 rval = *v; 115 } 116 return rval; 117 } 118 119 import std.range : isOutputRange; 120 121 string toString() @safe pure const { 122 import std.array : appender; 123 124 auto buf = appender!string; 125 toString(buf); 126 return buf.data; 127 } 128 129 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 130 import std.range : put; 131 132 put(w, typeof(this).stringof); 133 put(w, "("); 134 135 foreach (const a; db.byValue) { 136 a.toString(w); 137 put(w, ","); 138 } 139 140 put(w, ")"); 141 } 142 } 143 144 /// Stats for a path. 145 struct PathStat { 146 /// Path relative to the destination. 147 string path; 148 149 /// File type and mode 150 ulong mode; 151 /// User ID of owner 152 ulong uid; 153 /// Group ID of owner 154 ulong gid; 155 /// Device ID (if special file) 156 ulong rdev; 157 158 import std.range : isOutputRange; 159 160 string toString() @safe pure const { 161 import std.array : appender; 162 163 auto buf = appender!string; 164 toString(buf); 165 return buf.data; 166 } 167 168 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 169 import std.format : formattedWrite; 170 import std.range : put; 171 172 formattedWrite(w, "mode=%o", mode); 173 formattedWrite(w, ",uid=%s", uid); 174 formattedWrite(w, ",gid=%s", gid); 175 formattedWrite(w, ",rdev=%s", rdev); 176 formattedWrite(w, ",path=%s", path); 177 } 178 } 179 180 @("shall convert PathStat to a string") 181 unittest { 182 import std.conv : octal; 183 184 PathStat("foo/bar", octal!100664, 1000, 1001, 0).toString.shouldEqual( 185 "mode=100664,uid=1000,gid=1001,rdev=0,path=foo/bar"); 186 } 187 188 PathStat fromPathStat(const(char)[] s) { 189 import std.format : formattedRead; 190 191 typeof(return) rval; 192 s.formattedRead!"mode=%o,uid=%d,gid=%d,rdev=%d,path=%s"(rval.mode, 193 rval.uid, rval.gid, rval.rdev, rval.path); 194 return rval; 195 } 196 197 @("shall convert from string to pathstat") 198 unittest { 199 import std.conv : octal; 200 201 auto p = fromPathStat("mode=100664,uid=1000,gid=1001,rdev=0,path=foo/bar"); 202 p.shouldEqual(PathStat("foo/bar", octal!100664, 1000, 1001, 0)); 203 } 204 205 PathStat[] fromFakeroot(ref FakerootDb db, const string root, const string relRoot) @trusted { 206 import std.algorithm : filter; 207 import std.array : array; 208 import std.file : dirEntries, SpanMode; 209 import std.path : relativePath; 210 211 PathStat[string] rval; 212 213 foreach (f; dirEntries(root, SpanMode.depth).filter!(a => a.name !in rval)) { 214 const st = f.statBuf; 215 auto stdb = db.get(FakerootStat.Node(st.st_dev, st.st_ino)); 216 if (!stdb.isNull) 217 rval[f.name] = PathStat(relativePath(f.name, relRoot), 218 stdb.get.mode, stdb.get.uid, stdb.get.gid, stdb.get.rdev); 219 } 220 221 return rval.byValue.array; 222 } 223 224 FakerootDb fromFakerootEnv(const Path p) @trusted { 225 import std.stdio : File; 226 227 FakerootDb fkdb; 228 foreach (const l; File(p.toString).byLine) 229 fkdb.put(fromFakeroot(l)); 230 return fkdb; 231 }