1 /** 2 Copyright: Copyright (c) 2020, 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 my.file; 7 8 import logger = std.experimental.logger; 9 import std.algorithm : canFind; 10 import std.file : mkdirRecurse, exists, copy, dirEntries, SpanMode; 11 import std.path : relativePath, buildPath, dirName; 12 13 public import std.file : attrIsDir, attrIsFile, attrIsSymlink, isFile, isDir, isSymlink; 14 15 import my.path; 16 import my.optional; 17 18 /** A `nothrow` version of `getAttributes` in Phobos. 19 * 20 * man 7 inode search for S_IFMT 21 * S_ISUID 04000 set-user-ID bit 22 * S_ISGID 02000 set-group-ID bit (see below) 23 * S_ISVTX 01000 sticky bit (see below) 24 * 25 * S_IRWXU 00700 owner has read, write, and execute permission 26 * S_IRUSR 00400 owner has read permission 27 * S_IWUSR 00200 owner has write permission 28 * S_IXUSR 00100 owner has execute permission 29 * 30 * S_IRWXG 00070 group has read, write, and execute permission 31 * S_IRGRP 00040 group has read permission 32 * S_IWGRP 00020 group has write permission 33 * S_IXGRP 00010 group has execute permission 34 * 35 * S_IRWXO 00007 others (not in group) have read, write, and execute permission 36 * S_IROTH 00004 others have read permission 37 * S_IWOTH 00002 others have write permission 38 * S_IXOTH 00001 others have execute permission 39 * 40 * The idea of doing it like this is from WebFreaks001 pull request 41 * [DCD pullrequest](https://github.com/dlang-community/dsymbol/pull/151/files). 42 * 43 * Returns: true on success and thus `attributes` contains a valid value. 44 */ 45 bool getAttrs(const Path file, ref uint attributes) @safe nothrow { 46 import core.sys.posix.sys.stat : stat, stat_t; 47 import my.cstring; 48 49 static bool trustedAttrs(const Path file, ref stat_t st) @trusted { 50 return stat(file.toString.tempCString, &st) == 0; 51 } 52 53 stat_t st; 54 bool status = trustedAttrs(file, st); 55 attributes = st.st_mode; 56 return status; 57 } 58 59 /** A `nothrow` version of `getLinkAttributes` in Phobos. 60 * 61 * Returns: true on success and thus `attributes` contains a valid value. 62 */ 63 bool getLinkAttrs(const Path file, ref uint attributes) @safe nothrow { 64 import core.sys.posix.sys.stat : lstat, stat_t; 65 import my.cstring; 66 67 static bool trustedAttrs(const Path file, ref stat_t st) @trusted { 68 return lstat(file.toString.tempCString, &st) == 0; 69 } 70 71 stat_t st; 72 bool status = trustedAttrs(file, st); 73 attributes = st.st_mode; 74 return status; 75 } 76 77 /** A `nothrow` version of `setAttributes` in Phobos. 78 */ 79 bool setAttrs(const Path file, const uint attributes) @trusted nothrow { 80 import core.sys.posix.sys.stat : chmod; 81 import my.cstring; 82 83 return chmod(file.toString.tempCString, attributes) == 0; 84 } 85 86 /// Returns: true if `file` exists. 87 bool exists(const Path file) @safe nothrow { 88 uint attrs; 89 return getAttrs(file, attrs); 90 } 91 92 /** Returns: true if `file` exists and is a file. 93 * 94 * Source: [DCD](https://github.com/dlang-community/dsymbol/blob/master/src/dsymbol/modulecache.d) 95 */ 96 bool existsAnd(alias pred : isFile)(const Path file) @safe nothrow { 97 uint attrs; 98 if (!getAttrs(file, attrs)) 99 return false; 100 return attrIsFile(attrs); 101 } 102 103 /** Returns: true if `file` exists and is a directory. 104 * 105 * Source: [DCD](https://github.com/dlang-community/dsymbol/blob/master/src/dsymbol/modulecache.d) 106 */ 107 bool existsAnd(alias pred : isDir)(const Path file) @safe nothrow { 108 uint attrs; 109 if (!getAttrs(file, attrs)) 110 return false; 111 return attrIsDir(attrs); 112 } 113 114 /** Returns: true if `file` exists and is a symlink. 115 * 116 * Source: [DCD](https://github.com/dlang-community/dsymbol/blob/master/src/dsymbol/modulecache.d) 117 */ 118 bool existsAnd(alias pred : isSymlink)(const Path file) @safe nothrow { 119 uint attrs; 120 if (!getLinkAttrs(file, attrs)) 121 return false; 122 return attrIsSymlink(attrs); 123 } 124 125 /// Example: 126 unittest { 127 import std.file : remove, symlink, mkdir; 128 import std.format : format; 129 import std.stdio : File; 130 131 const base = Path(format!"%s_%s"(__FILE__, __LINE__)).baseName; 132 const Path fname = base ~ "_file"; 133 const Path dname = base ~ "_dir"; 134 const Path symname = base ~ "_symlink"; 135 scope (exit) 136 () { 137 foreach (f; [fname, dname, symname]) { 138 if (exists(f)) 139 remove(f); 140 } 141 }(); 142 143 File(fname, "w").write("foo"); 144 mkdir(dname); 145 symlink(fname, symname); 146 147 assert(exists(fname)); 148 assert(existsAnd!isFile(fname)); 149 assert(existsAnd!isDir(dname)); 150 assert(existsAnd!isSymlink(symname)); 151 } 152 153 /// Copy `src` into `dst` recursively. 154 void copyRecurse(Path src, Path dst) { 155 foreach (a; dirEntries(src.toString, SpanMode.depth)) { 156 const s = relativePath(a.name, src.toString); 157 const d = buildPath(dst.toString, s); 158 if (!exists(d.dirName)) { 159 mkdirRecurse(d.dirName); 160 } 161 if (!existsAnd!isDir(Path(a))) { 162 copy(a.name, d); 163 } 164 } 165 } 166 167 /// Make a file executable by all users on the system. 168 void setExecutable(Path p) nothrow { 169 import core.sys.posix.sys.stat; 170 import std.file : getAttributes, setAttributes; 171 172 uint attrs; 173 if (getAttrs(p, attrs)) { 174 setAttrs(p, attrs | S_IXUSR | S_IXGRP | S_IXOTH); 175 } 176 } 177 178 /// Check if a file is executable. 179 bool isExecutable(Path p) nothrow { 180 import core.sys.posix.sys.stat; 181 import std.file : getAttributes; 182 183 uint attrs; 184 if (getAttrs(p, attrs)) { 185 return (attrs & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 186 } 187 return false; 188 } 189 190 /** As the unix command `which` it searches in `dirs` for an executable `name`. 191 * 192 * The difference in the behavior is that this function returns all 193 * matches and supports globbing (use of `*`). 194 * 195 * Example: 196 * --- 197 * writeln(which([Path("/bin")], "ls")); 198 * writeln(which([Path("/bin")], "l*")); 199 * --- 200 */ 201 AbsolutePath[] which(Path[] dirs, string name) nothrow { 202 import std.algorithm : map, filter, joiner, copy; 203 import std.array : appender; 204 import std.exception : collectException; 205 import std.file : dirEntries, SpanMode; 206 import std.path : baseName, globMatch; 207 208 auto res = appender!(AbsolutePath[])(); 209 210 foreach (dir; dirs.filter!(a => existsAnd!isDir(a))) { 211 try { 212 dirEntries(dir, SpanMode.shallow).map!(a => Path(a)) 213 .filter!(a => isExecutable(a)) 214 .filter!(a => globMatch(a.baseName, name)) 215 .map!(a => AbsolutePath(a)) 216 .copy(res); 217 } catch (Exception e) { 218 logger.trace(e.msg).collectException; 219 } 220 } 221 222 return res.data; 223 } 224 225 @("shall return all locations of ls") 226 unittest { 227 assert(which([Path("/bin")], "mv") == [AbsolutePath("/bin/mv")]); 228 assert(which([Path("/bin")], "l*").length >= 1); 229 } 230 231 AbsolutePath[] whichFromEnv(string envKey, string name) { 232 import std.algorithm : splitter, map, filter; 233 import std.process : environment; 234 import std.array : empty, array; 235 236 auto dirs = environment.get(envKey, null).splitter(":").filter!(a => !a.empty) 237 .map!(a => Path(a)) 238 .array; 239 return which(dirs, name); 240 } 241 242 @("shall return all locations of ls by using the environment variable PATH") 243 unittest { 244 assert(canFind(whichFromEnv("PATH", "mv"), AbsolutePath("/bin/mv"))); 245 } 246 247 /** Follow a symlink until it reaches its target. 248 * 249 * Max depth guard against recursions or self referencing symlinks. 250 */ 251 Optional!Path followSymlink(Path p, int maxDepth = 100) @safe nothrow { 252 import std.file : readLink; 253 254 try { 255 int depth; 256 for (; depth < maxDepth && existsAnd!isSymlink(p); ++depth) { 257 p = Path(buildPath(p.dirName, readLink(p.toString))); 258 } 259 if (depth < maxDepth) 260 return some(p); 261 } catch (Exception e) { 262 } 263 264 return none!Path; 265 } 266 267 @("shall not infinite loop over a self referencing symlink") 268 unittest { 269 import std.file : remove, symlink; 270 import std.format : format; 271 import std.path : baseName; 272 273 immutable l = format!"%s_%s_link"(__FILE__.baseName, __LINE__); 274 scope (exit) 275 remove(l); 276 symlink(l, l); 277 assert(!followSymlink(Path(l)).hasValue, "should be no value"); 278 }