1 /*
2  *  Copyright (C) 2002-2021  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along
15  *  with this program; if not, write to the Free Software Foundation, Inc.,
16  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "cross.h"
20 
21 #include <cerrno>
22 #include <string>
23 #include <vector>
24 
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 
30 #ifdef WIN32
31 #ifndef _WIN32_IE
32 #define _WIN32_IE 0x0400
33 #endif
34 #include <shlobj.h>
35 #else
36 #include <libgen.h>
37 #endif
38 
39 #if defined HAVE_PWD_H
40 #include <pwd.h>
41 #endif
42 
43 #include "fs_utils.h"
44 #include "string_utils.h"
45 #include "support.h"
46 
GetConfigName()47 static std::string GetConfigName()
48 {
49 	return "dosbox-staging.conf";
50 }
51 
52 #ifndef WIN32
53 
54 std::string cached_conf_path;
55 
56 #if defined(MACOSX)
57 
DetermineConfigPath()58 static std::string DetermineConfigPath()
59 {
60 	const std::string conf_path = CROSS_ResolveHome("~/Library/Preferences/DOSBox");
61 	mkdir(conf_path.c_str(), 0700);
62 	return conf_path;
63 }
64 
65 #else
66 
CreateDirectories(const std::string & path)67 static bool CreateDirectories(const std::string &path)
68 {
69 	struct stat sb;
70 	if (stat(path.c_str(), &sb) == 0) {
71 		const bool is_dir = ((sb.st_mode & S_IFMT) == S_IFDIR);
72 		return is_dir;
73 	}
74 
75 	std::vector<char> tmp(path.begin(), path.end());
76 	std::string dname = dirname(tmp.data());
77 
78 	// Create parent directories recursively
79 	if (!CreateDirectories(dname))
80 		return false;
81 
82 	return (mkdir(path.c_str(), 0700) == 0);
83 }
84 
DetermineConfigPath()85 static std::string DetermineConfigPath()
86 {
87 	const char *xdg_conf_home = getenv("XDG_CONFIG_HOME");
88 	const std::string conf_home = xdg_conf_home ? xdg_conf_home : "~/.config";
89 	const std::string conf_path = CROSS_ResolveHome(conf_home + "/dosbox");
90 	const std::string old_conf_path = CROSS_ResolveHome("~/.dosbox");
91 
92 	if (path_exists(conf_path + "/" + GetConfigName())) {
93 		return conf_path;
94 	}
95 
96 	if (path_exists(old_conf_path + "/" + GetConfigName())) {
97 		LOG_MSG("WARNING: Config file found in deprecated path! (~/.dosbox)\n"
98 		        "Backup/remove this dir and restart to generate updated config file.\n"
99 		        "---");
100 		return old_conf_path;
101 	}
102 
103 	if (!CreateDirectories(conf_path)) {
104 		LOG_MSG("ERROR: Directory '%s' cannot be created",
105 		        conf_path.c_str());
106 		return old_conf_path;
107 	}
108 
109 	return conf_path;
110 }
111 
112 #endif // !MACOSX
113 
CROSS_DetermineConfigPaths()114 void CROSS_DetermineConfigPaths()
115 {
116 	if (cached_conf_path.empty())
117 		cached_conf_path = DetermineConfigPath();
118 }
119 
120 #endif // !WIN32
121 
122 #ifdef WIN32
123 
CROSS_DetermineConfigPaths()124 void CROSS_DetermineConfigPaths() {}
125 
W32_ConfDir(std::string & in,bool create)126 static void W32_ConfDir(std::string& in,bool create) {
127 	int c = create?1:0;
128 	char result[MAX_PATH] = { 0 };
129 	BOOL r = SHGetSpecialFolderPath(NULL,result,CSIDL_LOCAL_APPDATA,c);
130 	if(!r || result[0] == 0) r = SHGetSpecialFolderPath(NULL,result,CSIDL_APPDATA,c);
131 	if(!r || result[0] == 0) {
132 		char const * windir = getenv("windir");
133 		if(!windir) windir = "c:\\windows";
134 		safe_strcpy(result, windir);
135 		char const* appdata = "\\Application Data";
136 		size_t len = safe_strlen(result);
137 		if (len + strlen(appdata) < MAX_PATH)
138 			safe_strcat(result, appdata);
139 		if (create)
140 			mkdir(result);
141 	}
142 	in = result;
143 }
144 #endif
145 
CROSS_GetPlatformConfigDir()146 std::string CROSS_GetPlatformConfigDir()
147 {
148 	// Cache the result, as this doesn't change
149 	static std::string conf_dir = {};
150 	if (conf_dir.length())
151 		return conf_dir;
152 
153 	// Check if a portable layout exists
154 	std::string config_file;
155 	Cross::GetPlatformConfigName(config_file);
156 	const auto portable_conf_path = GetExecutablePath() / config_file;
157 	if (std_fs::is_regular_file(portable_conf_path)) {
158 		conf_dir = portable_conf_path.parent_path().string();
159 		LOG_MSG("CONFIG: Using portable configuration layout in %s",
160 		        conf_dir.c_str());
161 		conf_dir += CROSS_FILESPLIT;
162 		return conf_dir;
163 	}
164 
165 	// Otherwise get the OS-specific configuration directory
166 #ifdef WIN32
167 	W32_ConfDir(conf_dir, false);
168 	conf_dir += "\\DOSBox\\";
169 #else
170 	assert(!cached_conf_path.empty());
171 	conf_dir = cached_conf_path;
172 	if (conf_dir.back() != CROSS_FILESPLIT)
173 		conf_dir += CROSS_FILESPLIT;
174 #endif
175 	return conf_dir;
176 }
177 
GetPlatformConfigDir(std::string & in)178 void Cross::GetPlatformConfigDir(std::string &in)
179 {
180 	in = CROSS_GetPlatformConfigDir();
181 }
182 
GetPlatformConfigName(std::string & in)183 void Cross::GetPlatformConfigName(std::string &in)
184 {
185 	in = GetConfigName();
186 }
187 
ResolveHomedir(std::string & in)188 void Cross::ResolveHomedir(std::string &in)
189 {
190 	in = CROSS_ResolveHome(in);
191 }
192 
CreatePlatformConfigDir(std::string & in)193 void Cross::CreatePlatformConfigDir(std::string &in)
194 {
195 #ifdef WIN32
196 	W32_ConfDir(in,true);
197 	in += "\\DOSBox";
198 #else
199 	assert(!cached_conf_path.empty());
200 	in = cached_conf_path.c_str();
201 #endif
202 	if (in.back() != CROSS_FILESPLIT)
203 		in += CROSS_FILESPLIT;
204 
205 	if (create_dir(in.c_str(), 0700, OK_IF_EXISTS) != 0) {
206 		LOG_MSG("ERROR: Creation of config directory '%s' failed: %s",
207 		        in.c_str(), safe_strerror(errno).c_str());
208 	}
209 }
210 
CROSS_ResolveHome(const std::string & str)211 std::string CROSS_ResolveHome(const std::string &str)
212 {
213 	if (!str.size() || str[0] != '~') // No ~
214 		return str;
215 
216 	std::string temp_line = str;
217 	if(temp_line.size() == 1 || temp_line[1] == CROSS_FILESPLIT) { //The ~ and ~/ variant
218 		char * home = getenv("HOME");
219 		if(home) temp_line.replace(0,1,std::string(home));
220 #if defined HAVE_SYS_TYPES_H && defined HAVE_PWD_H
221 	} else { // The ~username variant
222 		std::string::size_type namelen = temp_line.find(CROSS_FILESPLIT);
223 		if(namelen == std::string::npos) namelen = temp_line.size();
224 		std::string username = temp_line.substr(1,namelen - 1);
225 		struct passwd* pass = getpwnam(username.c_str());
226 		if(pass) temp_line.replace(0,namelen,pass->pw_dir); //namelen -1 +1(for the ~)
227 #endif // USERNAME lookup code
228 	}
229 	return temp_line;
230 }
231 
IsPathAbsolute(std::string const & in)232 bool Cross::IsPathAbsolute(std::string const& in) {
233 	// Absolute paths
234 #if defined (WIN32)
235 	// drive letter
236 	if (in.size() > 2 && in[1] == ':' ) return true;
237 	// UNC path
238 	else if (in.size() > 2 && in[0]=='\\' && in[1]=='\\') return true;
239 #else
240 	if (in.size() > 1 && in[0] == '/' ) return true;
241 #endif
242 	return false;
243 }
244 
245 #if defined (WIN32)
246 
open_directory(const char * dirname)247 dir_information* open_directory(const char* dirname) {
248 	if (dirname == NULL) return NULL;
249 
250 	size_t len = strlen(dirname);
251 	if (len == 0) return NULL;
252 
253 	static dir_information dir;
254 
255 	safe_strncpy(dir.base_path,dirname,MAX_PATH);
256 
257 	if (dirname[len - 1] == '\\')
258 		safe_strcat(dir.base_path, "*.*");
259 	else
260 		safe_strcat(dir.base_path, "\\*.*");
261 
262 	dir.handle = INVALID_HANDLE_VALUE;
263 
264 	return (path_exists(dirname) ? &dir : nullptr);
265 }
266 
read_directory_first(dir_information * dirp,char * entry_name,bool & is_directory)267 bool read_directory_first(dir_information* dirp, char* entry_name, bool& is_directory) {
268 	if (!dirp) return false;
269 	dirp->handle = FindFirstFile(dirp->base_path, &dirp->search_data);
270 	if (INVALID_HANDLE_VALUE == dirp->handle) {
271 		return false;
272 	}
273 
274 	safe_strncpy(entry_name,dirp->search_data.cFileName,(MAX_PATH<CROSS_LEN)?MAX_PATH:CROSS_LEN);
275 
276 	if (dirp->search_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) is_directory = true;
277 	else is_directory = false;
278 
279 	return true;
280 }
281 
read_directory_next(dir_information * dirp,char * entry_name,bool & is_directory)282 bool read_directory_next(dir_information* dirp, char* entry_name, bool& is_directory) {
283 	if (!dirp) return false;
284 	int result = FindNextFile(dirp->handle, &dirp->search_data);
285 	if (result==0) return false;
286 
287 	safe_strncpy(entry_name,dirp->search_data.cFileName,(MAX_PATH<CROSS_LEN)?MAX_PATH:CROSS_LEN);
288 
289 	if (dirp->search_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) is_directory = true;
290 	else is_directory = false;
291 
292 	return true;
293 }
294 
close_directory(dir_information * dirp)295 void close_directory(dir_information* dirp) {
296 	if (dirp && dirp->handle != INVALID_HANDLE_VALUE) {
297 		FindClose(dirp->handle);
298 		dirp->handle = INVALID_HANDLE_VALUE;
299 	}
300 }
301 
302 #else
303 
open_directory(const char * dirname)304 dir_information* open_directory(const char* dirname) {
305 	static dir_information dir;
306 	dir.dir=opendir(dirname);
307 	safe_strcpy(dir.base_path, dirname);
308 	return dir.dir?&dir:NULL;
309 }
310 
read_directory_first(dir_information * dirp,char * entry_name,bool & is_directory)311 bool read_directory_first(dir_information* dirp, char* entry_name, bool& is_directory) {
312 	if (!dirp) return false;
313 	return read_directory_next(dirp,entry_name,is_directory);
314 }
315 
read_directory_next(dir_information * dirp,char * entry_name,bool & is_directory)316 bool read_directory_next(dir_information* dirp, char* entry_name, bool& is_directory) {
317 	if (!dirp) return false;
318 	struct dirent* dentry = readdir(dirp->dir);
319 	if (dentry==NULL) {
320 		return false;
321 	}
322 
323 //	safe_strncpy(entry_name,dentry->d_name,(FILENAME_MAX<MAX_PATH)?FILENAME_MAX:MAX_PATH);	// [include stdio.h], maybe pathconf()
324 	safe_strncpy(entry_name,dentry->d_name,CROSS_LEN);
325 
326 	// TODO check if this check can be replaced with glibc-defined
327 	// _DIRENT_HAVE_D_TYPE. Non-GNU systems (BSD) provide d_type field as
328 	// well, but do they provide define?
329 	// Alternatively, maybe we can replace whole directory listing with
330 	// C++17 std::filesystem::directory_iterator.
331 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
332 	if (dentry->d_type == DT_DIR) {
333 		is_directory = true;
334 		return true;
335 	} else if (dentry->d_type == DT_REG) {
336 		is_directory = false;
337 		return true;
338 	}
339 #endif
340 
341 	// Maybe only for DT_UNKNOWN if HAVE_STRUCT_DIRENT_D_TYPE
342 	static char buffer[2 * CROSS_LEN + 1] = { 0 };
343 	static char split[2] = { CROSS_FILESPLIT , 0 };
344 	buffer[0] = 0;
345 	safe_strcpy(buffer, dirp->base_path);
346 	size_t buflen = safe_strlen(buffer);
347 	if (buflen && buffer[buflen - 1] != CROSS_FILESPLIT)
348 		safe_strcat(buffer, split);
349 	safe_strcat(buffer, entry_name);
350 	struct stat status;
351 
352 	if (stat(buffer,&status) == 0) is_directory = (S_ISDIR(status.st_mode)>0);
353 	else is_directory = false;
354 
355 	return true;
356 }
357 
close_directory(dir_information * dirp)358 void close_directory(dir_information* dirp) {
359 	if (dirp) closedir(dirp->dir);
360 }
361 
362 #endif
363 
fopen_wrap(const char * path,const char * mode)364 FILE *fopen_wrap(const char *path, const char *mode) {
365 #if defined(WIN32)
366 	;
367 #elif defined (MACOSX)
368 	;
369 #else
370 #if defined (HAVE_REALPATH)
371 	char work[CROSS_LEN] = {0};
372 	strncpy(work,path,CROSS_LEN-1);
373 	char* last = strrchr(work,'/');
374 
375 	if (last) {
376 		if (last != work) {
377 			*last = 0;
378 			//If this compare fails, then we are dealing with files in /
379 			//Which is outside the scope, but test anyway.
380 			//However as realpath only works for exising files. The testing is
381 			//in that case not done against new files.
382 		}
383 		char* check = realpath(work,NULL);
384 		if (check) {
385 			if ( ( strlen(check) == 5 && strcmp(check,"/proc") == 0) || strncmp(check,"/proc/",6) == 0) {
386 //				LOG_MSG("lst hit %s blocking!",path);
387 				free(check);
388 				return NULL;
389 			}
390 			free(check);
391 		}
392 	}
393 
394 #if 0
395 //Lightweight version, but then existing files can still be read, which is not ideal
396 	if (strpbrk(mode,"aw+") != NULL) {
397 		LOG_MSG("pbrk ok");
398 		char* check = realpath(path,NULL);
399 		//Will be null if file doesn't exist.... ENOENT
400 		//TODO What about unlink /proc/self/mem and then create it ?
401 		//Should be safe for what we want..
402 		if (check) {
403 			if (strncmp(check,"/proc/",6) == 0) {
404 				free(check);
405 				return NULL;
406 			}
407 			free(check);
408 		}
409 	}
410 */
411 #endif //0
412 
413 #endif //HAVE_REALPATH
414 #endif
415 
416 	return fopen(path,mode);
417 }
418 
419 namespace cross {
420 
421 #if defined(WIN32)
localtime_r(const time_t * timep,struct tm * result)422 struct tm *localtime_r(const time_t *timep, struct tm *result)
423 {
424 	const errno_t err = localtime_s(result, timep);
425 	return (err == 0 ? result : nullptr);
426 }
427 #endif
428 
429 } // namespace cross
430