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