1 /******************************************************************************* 2 3 Copyright: 4 Copyright (c) 2006-2009 Lars Ivar Igesund, Thomas Kühne, 5 Grzegorz Adam Hankiewicz, sleek 6 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 11 See LICENSE_TANGO.txt for details. 12 13 Version: 14 Initial release: December 2006 15 Updated and readded: August 2009 16 17 Authors: 18 Lars Ivar Igesund, Thomas Kühne, 19 Grzegorz Adam Hankiewicz, sleek 20 21 *******************************************************************************/ 22 23 module ocean.sys.HomeFolder; 24 25 import ocean.meta.types.Qualifiers; 26 import ocean.core.Verify; 27 import TextUtil = ocean.text.Util; 28 import Path = ocean.io.Path; 29 import ocean.sys.Environment; 30 version (unittest) import ocean.core.Test; 31 32 import core.exception : onOutOfMemoryError; 33 import core.stdc.errno; 34 import core.stdc.stdlib; 35 import core.stdc.string; 36 import core.sys.posix.pwd; 37 38 39 /****************************************************************************** 40 41 Returns the home folder set in the current environment. 42 43 ******************************************************************************/ 44 45 char[] homeFolder() 46 { 47 return Path.standard(Environment.get("HOME").dup); 48 } 49 50 /******************************************************************************* 51 52 Performs tilde expansion in paths. 53 54 There are two ways of using tilde expansion in a path. One 55 involves using the tilde alone or followed by a path separator. In 56 this case, the tilde will be expanded with the value of the 57 environment variable <i>HOME</i>. The second way is putting 58 a username after the tilde (i.e. <tt>~john/Mail</tt>). Here, 59 the username will be searched for in the user database 60 (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to 61 whatever path is stored there. The username is considered the 62 string after the tilde ending at the first instance of a path 63 separator. 64 65 Note that using the <i>~user</i> syntax may give different 66 values from just <i>~</i> if the environment variable doesn't 67 match the value stored in the user database. 68 69 When the environment variable version is used, the path won't 70 be modified if the environment variable doesn't exist or it 71 is empty. When the database version is used, the path won't be 72 modified if the user doesn't exist in the database or there is 73 not enough memory to perform the query. 74 75 Returns: inputPath with the tilde expanded, or just inputPath 76 if it could not be expanded. 77 78 Throws: OutOfMemoryException if there is not enough memory to 79 perform the database lookup for the <i>~user</i> syntax. 80 81 Examples: 82 ----- 83 import ocean.sys.HomeFolder; 84 85 void processFile(char[] filename) 86 { 87 char[] path = expandTilde(filename); 88 ... 89 } 90 ----- 91 92 ----- 93 import ocean.sys.HomeFolder; 94 95 const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc"; 96 char[] RESOURCE_DIR; // This gets expanded below. 97 98 static this() 99 { 100 RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE); 101 } 102 ----- 103 ******************************************************************************/ 104 105 cstring expandTilde (cstring inputPath) 106 { 107 // Return early if there is no tilde in path. 108 if (inputPath.length < 1 || inputPath[0] != '~') 109 return inputPath; 110 111 if (inputPath.length == 1 || inputPath[1] == '/') 112 return expandFromEnvironment(inputPath); 113 else 114 return expandFromDatabase(inputPath); 115 } 116 117 /******************************************************************************* 118 119 Replaces the tilde from path with the environment variable HOME. 120 121 *******************************************************************************/ 122 123 private cstring expandFromEnvironment(cstring path) 124 { 125 verify(path.length >= 1); 126 verify(path[0] == '~'); 127 128 // Get HOME and use that to replace the tilde. 129 char[] home = homeFolder; 130 if (home is null) 131 return path; 132 133 if (home[$-1] == '/') 134 home = home[0..$-1]; 135 136 return Path.join(home, path[1..$]); 137 138 } 139 140 /******************************************************************************* 141 142 Replaces the tilde from path with the path from the user database. 143 144 *******************************************************************************/ 145 146 private cstring expandFromDatabase(cstring path) 147 { 148 verify(path.length > 2 || (path.length == 2 && path[1] != '/')); 149 verify(path[0] == '~'); 150 151 // Extract username, searching for path separator. 152 cstring username; 153 auto last_char = TextUtil.locate(path, '/'); 154 155 if (last_char == path.length) 156 { 157 username = path[1..$] ~ '\0'; 158 } 159 else 160 { 161 username = path[1..last_char] ~ '\0'; 162 } 163 164 verify(last_char > 1); 165 166 // Reserve C memory for the getpwnam_r() function. 167 passwd result; 168 int extra_memory_size = 5 * 1024; 169 void* extra_memory; 170 171 scope (exit) if(extra_memory) core.stdc.stdlib.free(extra_memory); 172 173 while (1) 174 { 175 extra_memory = core.stdc.stdlib.malloc(extra_memory_size); 176 if (extra_memory is null) 177 onOutOfMemoryError(); 178 179 // Obtain info from database. 180 passwd *verify; 181 core.stdc.errno.errno = 0; 182 if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size, 183 &verify) == 0) 184 { 185 // Failure if verify doesn't point at result. 186 if (verify == &result) 187 { 188 auto pwdirlen = strlen(result.pw_dir); 189 190 path = Path.join(result.pw_dir[0..pwdirlen].dup, path[last_char..$]); 191 } 192 193 return path; 194 } 195 196 if (core.stdc.errno.errno() != ERANGE) 197 onOutOfMemoryError(); 198 199 // extra_memory isn't large enough 200 core.stdc.stdlib.free(extra_memory); 201 extra_memory_size *= 2; 202 } 203 } 204 205 /// 206 unittest 207 { 208 // Retrieve the current home variable. 209 auto home = Environment.get("HOME"); 210 211 // Testing when there is no environment variable. 212 Environment.set("HOME", null); 213 test(expandTilde("~/") == "~/"); 214 test(expandTilde("~") == "~"); 215 216 // Testing when an environment variable is set. 217 Environment.set("HOME", "ocean/test"); 218 test (Environment.get("HOME") == "ocean/test"); 219 220 test(expandTilde("~/") == "ocean/test/"); 221 test(expandTilde("~") == "ocean/test"); 222 223 // The same, but with a variable ending in a slash. 224 Environment.set("HOME", "ocean/test/"); 225 test(expandTilde("~/") == "ocean/test/"); 226 test(expandTilde("~") == "ocean/test"); 227 228 // Recover original HOME variable before continuing. 229 if (home) 230 Environment.set("HOME", home); 231 else 232 Environment.set("HOME", null); 233 234 // Test user expansion for root. Are there unices without /root? 235 test(expandTilde("~root") == "/root" || expandTilde("~root") == "/var/root"); 236 test(expandTilde("~root/") == "/root/" || expandTilde("~root") == "/var/root"); 237 test(expandTilde("~Idontexist/hey") == "~Idontexist/hey"); 238 }