1 /*
2 	compat: Some compatibility functions (basic memory & string stuff in separate file)
3 
4 	The mpg123 code is determined to keep it's legacy. A legacy of old, old UNIX.
5 	So anything possibly somewhat advanced should be considered to be put here, with proper #ifdef;-)
6 
7 	copyright 2007-2020 by the mpg123 project - free software under the terms of the LGPL 2.1
8 	see COPYING and AUTHORS files in distribution or http://mpg123.org
9 	initially written by Thomas Orgis, Windows Unicode stuff by JonY.
10 */
11 
12 #include "config.h"
13 /* This source file does need _POSIX_SOURCE to get some sigaction. */
14 #define _POSIX_SOURCE
15 #include "compat.h"
16 
17 #ifdef _MSC_VER
18 #include <io.h>
19 
20 #if(defined(WINAPI_FAMILY) && (WINAPI_FAMILY==WINAPI_FAMILY_APP))
21 #define WINDOWS_UWP
22 #endif
23 
24 #endif
25 #ifdef HAVE_SYS_STAT_H
26 #  include <sys/stat.h>
27 #endif
28 #ifdef HAVE_DIRENT_H
29 #  include <dirent.h>
30 #endif
31 
32 /* Win32 is only supported with unicode now. These headers also cover
33    module stuff. The WANT_WIN32_UNICODE macro is synonymous with
34    "want windows-specific API, and only the unicode variants of which". */
35 #ifdef WANT_WIN32_UNICODE
36 #include <wchar.h>
37 #include <windows.h>
38 #include <winnls.h>
39 #include <shlwapi.h>
40 #endif
41 
42 #include "debug.h"
43 
44 #ifndef WINDOWS_UWP
45 
compat_getenv(const char * name)46 char *compat_getenv(const char* name)
47 {
48 	char *ret = NULL;
49 #ifdef WANT_WIN32_UNICODE
50 	wchar_t *env;
51 	wchar_t *wname = NULL;
52 	if(win32_utf8_wide(name, &wname, NULL) > 0)
53 	{
54 		env = _wgetenv(wname);
55 		free(wname);
56 		if(env)
57 			win32_wide_utf8(env, &ret, NULL);
58 	}
59 #else
60 	ret = getenv(name);
61 	if(ret)
62 		ret = compat_strdup(ret);
63 #endif
64 	return ret;
65 }
66 
67 #endif
68 
69 #include "wpathconv.h"
70 
71 /* Always add a default permission mask in case of flags|O_CREAT. */
compat_open(const char * filename,int flags)72 int compat_open(const char *filename, int flags)
73 {
74 	int ret;
75 #if defined (WANT_WIN32_UNICODE)
76 	wchar_t *frag = NULL;
77 
78 	frag = u2wlongpath(filename);
79 	/* Fallback to plain open when ucs-2 conversion fails */
80 	if(!frag)
81 		goto open_fallback;
82 
83 	/*Try _wopen */
84 	ret = _wopen(frag, flags|_O_BINARY, _S_IREAD | _S_IWRITE);
85 	if(ret != -1 )
86 		goto open_ok; /* msdn says -1 means failure */
87 
88 open_fallback:
89 #endif
90 
91 #if (defined(WIN32) && !defined (__CYGWIN__))
92 	/* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */
93 	/* Try plain old _open(), if it fails, do nothing */
94 	ret = _open(filename, flags|_O_BINARY, _S_IREAD | _S_IWRITE);
95 #else
96 	ret = open(filename, flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
97 #endif
98 
99 #if defined (WANT_WIN32_UNICODE)
100 open_ok:
101 	free(frag);
102 #endif
103 
104 	return ret;
105 }
106 
107 /* Moved over from wav.c, logic with fallbacks added from the
108    example of compat_open(). */
compat_fopen(const char * filename,const char * mode)109 FILE* compat_fopen(const char *filename, const char *mode)
110 {
111 	FILE* stream = NULL;
112 #ifdef WANT_WIN32_UNICODE
113 	int cnt = 0;
114 	wchar_t *wname = NULL;
115 	wchar_t *wmode = NULL;
116 
117 	wname = u2wlongpath(filename);
118 	if(!wname)
119 		goto fopen_fallback;
120 	cnt = win32_utf8_wide(mode, &wmode, NULL);
121 	if( (wmode == NULL) || (cnt == 0))
122 		goto fopen_fallback;
123 
124 	stream = _wfopen(wname, wmode);
125 	if(stream) goto fopen_ok;
126 
127 fopen_fallback:
128 #endif
129 	stream = fopen(filename, mode);
130 #ifdef WANT_WIN32_UNICODE
131 
132 fopen_ok:
133 	free(wmode);
134 	free(wname);
135 #endif
136 	return stream;
137 }
138 
compat_fdopen(int fd,const char * mode)139 FILE* compat_fdopen(int fd, const char *mode)
140 {
141 	return fdopen(fd, mode);
142 }
143 
compat_close(int infd)144 int compat_close(int infd)
145 {
146 #if (defined(WIN32) && !defined (__CYGWIN__)) /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */
147 	return _close(infd);
148 #else
149 	return close(infd);
150 #endif
151 }
152 
compat_fclose(FILE * stream)153 int compat_fclose(FILE *stream)
154 {
155 	return fclose(stream);
156 }
157 
158 #ifndef WINDOWS_UWP
159 
160 /*
161 	The Windows file and path stuff is an extract of jon_y's win32 loader
162 	prototype from the loader_rework branch. It's been divided in to
163 	reusable functons by ThOr in the hope to work out some generic-looking
164 	loader code for both POSIX and Windows. The routines might be
165 	helpful for consistent path work in other parts of mpg123, too.
166 
167 	This all is about getting some working code on a wide range of
168 	systems while staying somewhat sane. If it does ridiculously inefficient
169 	things with extraneous copies and grabbing of functions that made
170 	it late to some official APIs, that's still fine with us.
171 */
172 
compat_catpath(const char * prefix,const char * path)173 char* compat_catpath(const char *prefix, const char* path)
174 {
175 	char *ret = NULL;
176 #ifdef WANT_WIN32_UNICODE
177 	wchar_t *wprefix = NULL; /* Wide windows versions of */
178 	wchar_t *wpath   = NULL; /* input arguments. */
179 	wchar_t *locwret = NULL; /* Tmp return value from LocalAlloc */
180 	/*
181 		This variation of combinepath can work with long and UNC paths, but
182 		is not officially exposed in any DLLs, It also allocates all its buffers
183 		internally via LocalAlloc, avoiding buffer overflow problems.
184 		ThOr: I presume this hack is for supporting pre-8 Windows, as
185 		from Windows 8 on, this is documented in the API.
186 	*/
187 	HRESULT (__stdcall *mypac)( const wchar_t *in, const wchar_t* more
188 	,	unsigned long flags, wchar_t **out ) = NULL;
189 	HMODULE pathcch = NULL;
190 
191 	if(!prefix && !path)
192 		goto catpath_end;
193 	wprefix = u2wpath(prefix);
194 	wpath   = u2wpath(path);
195 	if((prefix && !wprefix) || (path && !wpath))
196 		goto catpath_end;
197 
198 	/* Again: I presume this whole fun is to get at PathAllocCombine
199 	   even when pathcch.h is not available (like in MinGW32). */
200 	if( (pathcch = GetModuleHandleA("kernelbase")) )
201 		mypac = (void *)GetProcAddress(pathcch, "PathAllocCombine");
202 	if(mypac) /* PATHCCH_ALLOW_LONG_PATH = 1 per API docs */
203 	{
204 		debug("Actually calling PathAllocCombine!");
205 		mypac(wprefix, wpath, 1, &locwret);
206 	}
207 	else
208 	{
209 		/* Playing safe, if we'd care much about performance, this would be on
210 		   the stack. */
211 		locwret = LocalAlloc(LPTR, sizeof(wchar_t)*MAX_PATH);
212 		if(locwret)
213 			PathCombineW(locwret, wprefix, wpath);
214 	}
215 	ret = w2upath(locwret);
216 
217 catpath_end:
218 	LocalFree(locwret);
219 	free(wprefix);
220 	free(wpath);
221 #else
222 	size_t len, prelen, patlen;
223 
224 	if(path && path[0] == '/')
225 		prefix = NULL; /* Absolute path stays as it is. */
226 	prelen = prefix ? strlen(prefix) : 0;
227 	patlen = path   ? strlen(path)   : 0;
228 	/* Concatenate the two, put a / in between if both present. */
229 	len = ((prefix && path) ? 1 : 0) + prelen + patlen;
230 	ret = malloc(len+1);
231 	if(ret)
232 	{
233 		size_t off=0;
234 		memcpy(ret, prefix, prelen);
235 		if(prefix && path)
236 			ret[prelen+(off++)] = '/';
237 		memcpy(ret+prelen+off, path, patlen);
238 		ret[len] = 0;
239 	}
240 #endif
241 	return ret;
242 }
243 
compat_isdir(const char * path)244 int compat_isdir(const char *path)
245 {
246 	int ret = 0;
247 #ifdef WANT_WIN32_UNICODE
248 	wchar_t *wpath;
249 	wpath = u2wlongpath(path);
250 	if(wpath)
251 	{
252 		DWORD attr = GetFileAttributesW(wpath);
253 		if(attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY)
254 			ret=1;
255 		free(wpath);
256 	}
257 #else
258 	struct stat sb;
259 	if(path && !stat(path, &sb))
260 	{
261 		if(S_ISDIR(sb.st_mode))
262 			ret=1;
263 	}
264 #endif
265 	return ret;
266 }
267 
268 struct compat_dir
269 {
270 	char *path;
271 #ifdef WANT_WIN32_UNICODE
272 	int gotone; /* Got a result stored from FindFirstFileW. */
273 	WIN32_FIND_DATAW d;
274 	HANDLE ffn;
275 #else
276 	DIR* dir;
277 #endif
278 };
279 
compat_diropen(char * path)280 struct compat_dir* compat_diropen(char *path)
281 {
282 	struct compat_dir *cd;
283 	if(!path)
284 		return NULL;
285 	cd = malloc(sizeof(*cd));
286 	if(!cd)
287 		return NULL;
288 #ifdef WANT_WIN32_UNICODE
289 	cd->gotone = 0;
290 	{
291 		char *pattern;
292 		wchar_t *wpattern;
293 		pattern = compat_catpath(path, "*");
294 		wpattern = u2wlongpath(pattern);
295 		if(wpattern)
296 		{
297 			cd->ffn = FindFirstFileW(wpattern, &(cd->d));
298 			if(cd->ffn == INVALID_HANDLE_VALUE)
299 			{
300 				/* FindClose() only needed after successful first find, right? */
301 				free(cd);
302 				cd = NULL;
303 			}
304 			else
305 				cd->gotone = 1;
306 		}
307 		free(wpattern);
308 		free(pattern);
309 	}
310 #else
311 	cd->dir = opendir(path);
312 	if(!cd->dir)
313 	{
314 		free(cd);
315 		cd = NULL;
316 	}
317 #endif
318 	if(cd)
319 	{
320 		cd->path = compat_strdup(path);
321 		if(!cd->path)
322 		{
323 			compat_dirclose(cd);
324 			cd = NULL;
325 		}
326 	}
327 	return cd;
328 }
329 
compat_dirclose(struct compat_dir * cd)330 void compat_dirclose(struct compat_dir *cd)
331 {
332 	if(cd)
333 	{
334 		free(cd->path);
335 #ifdef WANT_WIN32_UNICODE
336 		FindClose(cd->ffn);
337 #else
338 		closedir(cd->dir);
339 #endif
340 		free(cd);
341 	}
342 }
343 
compat_nextfile(struct compat_dir * cd)344 char* compat_nextfile(struct compat_dir *cd)
345 {
346 	if(!cd)
347 		return NULL;
348 #ifdef WANT_WIN32_UNICODE
349 	while(cd->gotone || FindNextFileW(cd->ffn, &(cd->d)))
350 	{
351 		cd->gotone = 0;
352 		if(!(cd->d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
353 		{
354 			char *ret;
355 			win32_wide_utf8(cd->d.cFileName, &ret, NULL);
356 			return ret;
357 		}
358 	}
359 #else
360 	{
361 		struct dirent *dp;
362 		while((dp = readdir(cd->dir)))
363 		{
364 			struct stat fst;
365 			char *fullpath = compat_catpath(cd->path, dp->d_name);
366 			if(fullpath && !stat(fullpath, &fst) && S_ISREG(fst.st_mode))
367 			{
368 				free(fullpath);
369 				return compat_strdup(dp->d_name);
370 			}
371 			free(fullpath);
372 		}
373 	}
374 #endif
375 	return NULL;
376 }
377 
compat_nextdir(struct compat_dir * cd)378 char* compat_nextdir(struct compat_dir *cd)
379 {
380 	if(!cd)
381 		return NULL;
382 #ifdef WANT_WIN32_UNICODE
383 	while(cd->gotone || FindNextFileW(cd->ffn, &(cd->d)))
384 	{
385 		cd->gotone = 0;
386 		if(cd->d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
387 		{
388 			char *ret;
389 			win32_wide_utf8(cd->d.cFileName, &ret, NULL);
390 			return ret;
391 		}
392 	}
393 #else
394 	{
395 		struct dirent *dp;
396 		while((dp = readdir(cd->dir)))
397 		{
398 			struct stat fst;
399 			char *fullpath = compat_catpath(cd->path, dp->d_name);
400 			if(fullpath && !stat(fullpath, &fst) && S_ISDIR(fst.st_mode))
401 			{
402 				free(fullpath);
403 				return compat_strdup(dp->d_name);
404 			}
405 			free(fullpath);
406 		}
407 	}
408 #endif
409 	return NULL;
410 }
411 
412 #endif
413 
414 // Revisit logic of write():
415 // Return -1 if interrupted before any data was written,
416 // set errno to EINTR. Any other error value is serious
417 // for blocking I/O, which we assume here. EAGAIN should be
418 // handed through.
419 // Reaction to zero-sized write attempts could also be funky, so avoid that.
420 // May return short count for various reasons. I assume that
421 // any serious condition will show itself as return value -1
422 // eventually.
423 
424 // These uninterruptible write/read functions shall persist as long as
425 // possible to finish the desired operation. A short byte count is short
426 // because of a serious reason (maybe EOF, maybe out of disk space). You
427 // can inspect errno.
428 
unintr_write(int fd,void const * buffer,size_t bytes)429 size_t unintr_write(int fd, void const *buffer, size_t bytes)
430 {
431 	size_t written = 0;
432 	errno = 0;
433 	while(bytes)
434 	{
435 		errno = 0;
436 		ssize_t part = write(fd, (char*)buffer+written, bytes);
437 		// Just on short writes, we do not abort. Only when
438 		// there was no successful operation (even zero write) at all.
439 		// Any other error than EINTR ends things here.
440 		if(part >= 0)
441 		{
442 			bytes   -= part;
443 			written += part;
444 		} else if(errno != EINTR)
445 			break;
446 	}
447 	return written;
448 }
449 
450 /* Same for reading the data. */
unintr_read(int fd,void * buffer,size_t bytes)451 size_t unintr_read(int fd, void *buffer, size_t bytes)
452 {
453 	size_t got = 0;
454 	errno = 0;
455 	while(bytes)
456 	{
457 		errno = 0;
458 		ssize_t part = read(fd, (char*)buffer+got, bytes);
459 		if(part >= 0)
460 		{
461 			bytes -= part;
462 			got   += part;
463 		} else if(errno != EINTR)
464 			break;
465 	}
466 	return got;
467 }
468 
469 // and again for streams
unintr_fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream)470 size_t unintr_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
471 {
472 	size_t written = 0;
473 	errno = 0;
474 	while(size && nmemb)
475 	{
476 		errno = 0;
477 		size_t part = fwrite((char*)ptr+written*size, size, nmemb, stream);
478 		if(part > 0)
479 		{
480 			nmemb   -= part;
481 			written += part;
482 		} else if(errno != EINTR)
483 			break;
484 	}
485 	return written;
486 }
487 
488 #ifndef NO_CATCHSIGNAL
489 #if (!defined(WIN32) || defined (__CYGWIN__)) && defined(HAVE_SIGNAL_H)
catchsignal(int signum,void (* handler)())490 void (*catchsignal(int signum, void(*handler)()))()
491 {
492 	struct sigaction new_sa;
493 	struct sigaction old_sa;
494 
495 #ifdef DONT_CATCH_SIGNALS
496 	fprintf (stderr, "Not catching any signals.\n");
497 	return ((void (*)()) -1);
498 #endif
499 
500 	new_sa.sa_handler = handler;
501 	sigemptyset(&new_sa.sa_mask);
502 	new_sa.sa_flags = 0;
503 	if(sigaction(signum, &new_sa, &old_sa) == -1)
504 		return ((void (*)()) -1);
505 	return (old_sa.sa_handler);
506 }
507 #endif
508 #endif
509