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