1 /******************************************************************************* 2 3 copyright: Copyright (c) 2006-2009 Lars Ivar Igesund, Thomas Kühne, 4 Grzegorz Adam Hankiewicz, sleek 5 6 license: BSD style: $(LICENSE) 7 8 version: Initial release: December 2006 9 Updated and readded: August 2009 10 11 author: Lars Ivar Igesund, Thomas Kühne, 12 Grzegorz Adam Hankiewicz, sleek 13 14 Since: 0.99.9 15 16 *******************************************************************************/ 17 18 module tango.sys.HomeFolder; 19 20 import TextUtil = tango.text.Util; 21 import Path = tango.io.Path; 22 import tango.sys.Environment; 23 24 version (Posix) 25 { 26 import tango.core.Exception; 27 import tango.stdc.stdlib; 28 import tango.stdc.posix.pwd; 29 import tango.stdc.errno; 30 31 private extern (C) size_t strlen (in char *); 32 } 33 34 35 /****************************************************************************** 36 37 Returns the home folder set in the current environment. 38 39 ******************************************************************************/ 40 41 @property char[] homeFolder() 42 { 43 version (Windows) 44 return Path.standard(Environment.get("USERPROFILE")); 45 else 46 return Path.standard(Environment.get("HOME")); 47 } 48 49 version (Posix) 50 { 51 52 /****************************************************************************** 53 54 Performs tilde expansion in paths. 55 56 There are two ways of using tilde expansion in a path. One 57 involves using the tilde alone or followed by a path separator. In 58 this case, the tilde will be expanded with the value of the 59 environment variable <i>HOME</i>. The second way is putting 60 a username after the tilde (i.e. <tt>~john/Mail</tt>). Here, 61 the username will be searched for in the user database 62 (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to 63 whatever path is stored there. The username is considered the 64 string after the tilde ending at the first instance of a path 65 separator. 66 67 Note that using the <i>~user</i> syntax may give different 68 values from just <i>~</i> if the environment variable doesn't 69 match the value stored in the user database. 70 71 When the environment variable version is used, the path won't 72 be modified if the environment variable doesn't exist or it 73 is empty. When the database version is used, the path won't be 74 modified if the user doesn't exist in the database or there is 75 not enough memory to perform the query. 76 77 Returns: inputPath with the tilde expanded, or just inputPath 78 if it could not be expanded. 79 80 Throws: OutOfMemoryException if there is not enough memory to 81 perform the database lookup for the <i>~user</i> syntax. 82 83 Examples: 84 ----- 85 import tango.sys.HomeFolder; 86 87 void processFile(char[] filename) 88 { 89 char[] path = expandTilde(filename); 90 ... 91 } 92 ----- 93 94 ----- 95 import tango.sys.HomeFolder; 96 97 const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc"; 98 char[] RESOURCE_DIR; // This gets expanded below. 99 100 static this() 101 { 102 RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE); 103 } 104 ----- 105 ******************************************************************************/ 106 107 inout(char)[] expandTilde (inout(char)[] inputPath) 108 { 109 // Return early if there is no tilde in path. 110 if (inputPath.length < 1 || inputPath[0] != '~') 111 return inputPath; 112 113 if (inputPath.length == 1 || inputPath[1] == '/') 114 return expandFromEnvironment(inputPath); 115 else 116 return expandFromDatabase(inputPath); 117 } 118 119 /******************************************************************************* 120 121 Replaces the tilde from path with the environment variable 122 HOME. 123 124 ******************************************************************************/ 125 126 private inout(char)[] expandFromEnvironment(inout(char)[] path) 127 in 128 { 129 assert(path.length >= 1); 130 assert(path[0] == '~'); 131 } 132 body 133 { 134 // Get HOME and use that to replace the tilde. 135 char[] home = homeFolder; 136 if (home is null) 137 return cast(inout)path; 138 139 if (home[$-1] == '/') 140 home = home[0..$-1]; 141 142 return cast(inout)Path.join(home, path[1..$]); 143 144 } 145 146 /******************************************************************************* 147 148 Replaces the tilde from path with the path from the user 149 database. 150 151 ******************************************************************************/ 152 153 private inout(char)[] expandFromDatabase(inout(char)[] path) 154 { 155 assert(path.length > 2 || (path.length == 2 && path[1] != '/')); 156 assert(path[0] == '~'); 157 158 // Extract username, searching for path separator. 159 char[] username; 160 auto last_char = TextUtil.locate(path, '/'); 161 162 if (last_char == path.length) 163 { 164 username = path[1..$].dup ~ '\0'; 165 } 166 else 167 { 168 username = path[1..last_char].dup ~ '\0'; 169 } 170 171 assert(last_char > 1); 172 173 // Reserve C memory for the getpwnam_r() function. 174 passwd result; 175 int extra_memory_size = 5 * 1024; 176 void* extra_memory; 177 178 scope (exit) if(extra_memory) tango.stdc.stdlib.free(extra_memory); 179 180 while (1) 181 { 182 extra_memory = tango.stdc.stdlib.malloc(extra_memory_size); 183 if (extra_memory is null) 184 throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__); 185 186 // Obtain info from database. 187 passwd *verify; 188 tango.stdc.errno.errno(0); 189 if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size, 190 &verify) == 0) 191 { 192 // Failure if verify doesn't point at result. 193 if (verify == &result) 194 { 195 auto pwdirlen = strlen(result.pw_dir); 196 197 path = cast(inout)Path.join(result.pw_dir[0..pwdirlen].dup, path[last_char..$]); 198 } 199 200 return cast(inout)path; 201 } 202 203 if (tango.stdc.errno.errno() != ERANGE) 204 throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__); 205 206 // extra_memory isn't large enough 207 tango.stdc.stdlib.free(extra_memory); 208 extra_memory_size *= 2; 209 } 210 } 211 212 } 213 214 version (Windows) 215 { 216 217 /****************************************************************************** 218 219 Performs tilde expansion in paths. 220 221 There are two ways of using tilde expansion in a path. One 222 involves using the tilde alone or followed by a path separator. In 223 this case, the tilde will be expanded with the value of the 224 environment variable <i>HOME</i>. The second way is putting 225 a username after the tilde (i.e. <tt>~john/Mail</tt>). Here, 226 the username will be searched for in the user database 227 (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to 228 whatever path is stored there. The username is considered the 229 string after the tilde ending at the first instance of a path 230 separator. 231 232 Note that using the <i>~user</i> syntax may give different 233 values from just <i>~</i> if the environment variable doesn't 234 match the value stored in the user database. 235 236 When the environment variable version is used, the path won't 237 be modified if the environment variable doesn't exist or it 238 is empty. When the database version is used, the path won't be 239 modified if the user doesn't exist in the database or there is 240 not enough memory to perform the query. 241 242 Returns: inputPath with the tilde expanded, or just inputPath 243 if it could not be expanded. 244 245 Throws: OutOfMemoryException if there is not enough memory to 246 perform the database lookup for the <i>~user</i> syntax. 247 248 Examples: 249 ----- 250 import tango.sys.HomeFolder; 251 252 void processFile(char[] filename) 253 { 254 char[] path = expandTilde(filename); 255 ... 256 } 257 ----- 258 259 ----- 260 import tango.sys.HomeFolder; 261 262 const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc"; 263 char[] RESOURCE_DIR; // This gets expanded below. 264 265 static this() 266 { 267 RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE); 268 } 269 ----- 270 ******************************************************************************/ 271 272 inout(char)[] expandTilde(inout(char)[] inputPath) 273 { 274 inputPath = cast(inout(char)[])Path.standard(inputPath.dup); 275 276 if (inputPath.length < 1 || inputPath[0] != '~') { 277 return cast(inout)inputPath; 278 } 279 280 if (inputPath.length == 1 || inputPath[1] == '/') { 281 return expandCurrentUser(inputPath); 282 } 283 284 return expandOtherUser(inputPath); 285 } 286 287 private inout(char)[] expandCurrentUser(inout(char)[] path) 288 { 289 auto userProfileDir = homeFolder; 290 auto offset = TextUtil.locate(path, '/'); 291 292 if (offset == path.length) { 293 return cast(inout(char)[])userProfileDir; 294 } 295 296 return cast(inout)Path.join(userProfileDir, path[offset+1..$]); 297 } 298 299 private inout(char)[] expandOtherUser(inout(char)[] path) 300 { 301 auto profileDir = Path.parse(homeFolder).parent; 302 return cast(inout)Path.join(profileDir, path[1..$]); 303 } 304 } 305 306 /******************************************************************************* 307 308 *******************************************************************************/ 309 310 debug(UnitTest) { 311 312 unittest 313 { 314 version (Posix) 315 { 316 // Retrieve the current home variable. 317 char[] home = Environment.get("HOME"); 318 319 // Testing when there is no environment variable. 320 Environment.set("HOME", null); 321 assert(expandTilde("~/") == "~/"); 322 assert(expandTilde("~") == "~"); 323 324 // Testing when an environment variable is set. 325 Environment.set("HOME", "tango/test"); 326 assert (Environment.get("HOME") == "tango/test"); 327 328 assert(expandTilde("~/") == "tango/test/"); 329 assert(expandTilde("~") == "tango/test"); 330 331 // The same, but with a variable ending in a slash. 332 Environment.set("HOME", "tango/test/"); 333 assert(expandTilde("~/") == "tango/test/"); 334 assert(expandTilde("~") == "tango/test"); 335 336 // Recover original HOME variable before continuing. 337 if (home) 338 Environment.set("HOME", home); 339 else 340 Environment.set("HOME", null); 341 342 // Test user expansion for root. Are there unices without /root? 343 assert(expandTilde("~root") == "/root" || expandTilde("~root") == "/var/root"); 344 assert(expandTilde("~root/") == "/root/" || expandTilde("~root") == "/var/root"); 345 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey"); 346 } 347 } 348 } 349