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