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-2016 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 #ifdef USE_MODULES
43 # ifdef HAVE_DLFCN_H
44 # include <dlfcn.h>
45 # endif
46 #endif
47
48 #include "debug.h"
49
50 #ifndef WINDOWS_UWP
51
compat_getenv(const char * name)52 char *compat_getenv(const char* name)
53 {
54 char *ret = NULL;
55 #ifdef WANT_WIN32_UNICODE
56 wchar_t *env;
57 wchar_t *wname = NULL;
58 if(win32_utf8_wide(name, &wname, NULL) > 0)
59 {
60 env = _wgetenv(wname);
61 free(wname);
62 if(env)
63 win32_wide_utf8(env, &ret, NULL);
64 }
65 #else
66 ret = getenv(name);
67 if(ret)
68 ret = compat_strdup(ret);
69 #endif
70 return ret;
71 }
72
73 #ifdef WANT_WIN32_UNICODE
74
75 /* Convert unix UTF-8 (or ASCII) paths to Windows wide character paths. */
u2wpath(const char * upath)76 static wchar_t* u2wpath(const char *upath)
77 {
78 wchar_t* wpath, *p;
79 if(!upath || win32_utf8_wide(upath, &wpath, NULL) < 1)
80 return NULL;
81 for(p=wpath; *p; ++p)
82 if(*p == L'/')
83 *p = L'\\';
84 return wpath;
85 }
86
87 /* Convert Windows wide character paths to unix UTF-8. */
w2upath(const wchar_t * wpath)88 static char* w2upath(const wchar_t *wpath)
89 {
90 char* upath, *p;
91 if(!wpath || win32_wide_utf8(wpath, &upath, NULL) < 1)
92 return NULL;
93 for(p=upath; *p; ++p)
94 if(*p == '\\')
95 *p = '/';
96 return upath;
97 }
98
99 /* An absolute path that is too long and not already marked with
100 \\?\ can be marked as a long one and still work. */
wpath_need_elongation(wchar_t * wpath)101 static int wpath_need_elongation(wchar_t *wpath)
102 {
103 if( wpath && !PathIsRelativeW(wpath)
104 && wcslen(wpath) > MAX_PATH-1
105 && wcsncmp(L"\\\\?\\", wpath, 4) )
106 return 1;
107 else
108 return 0;
109 }
110
111 /* Take any wide windows path and turn it into a path that is allowed
112 to be longer than MAX_PATH, if it is not already. */
wlongpath(wchar_t * wpath)113 static wchar_t* wlongpath(wchar_t *wpath)
114 {
115 size_t len, plen;
116 const wchar_t *prefix = L"";
117 wchar_t *wlpath = NULL;
118 if(!wpath)
119 return NULL;
120
121 /* Absolute paths that do not start with \\?\ get that prepended
122 to allow them being long. */
123 if(!PathIsRelativeW(wpath) && wcsncmp(L"\\\\?\\", wpath, 4))
124 {
125 if(wcslen(wpath) >= 2 && PathIsUNCW(wpath))
126 {
127 /* \\server\path -> \\?\UNC\server\path */
128 prefix = L"\\\\?\\UNC";
129 ++wpath; /* Skip the first \. */
130 }
131 else /* c:\some/path -> \\?\c:\some\path */
132 prefix = L"\\\\?\\";
133 }
134 plen = wcslen(prefix);
135 len = plen + wcslen(wpath);
136 wlpath = malloc(len+1*sizeof(wchar_t));
137 if(wlpath)
138 {
139 /* Brute force memory copying, swprintf is too dandy. */
140 memcpy(wlpath, prefix, sizeof(wchar_t)*plen);
141 memcpy(wlpath+plen, wpath, sizeof(wchar_t)*(len-plen));
142 wlpath[len] = 0;
143 }
144 return wlpath;
145 }
146
147 /* Convert unix path to wide windows path, optionally marking
148 it as long path if necessary. */
u2wlongpath(const char * upath)149 static wchar_t* u2wlongpath(const char *upath)
150 {
151 wchar_t *wpath = NULL;
152 wchar_t *wlpath = NULL;
153 wpath = u2wpath(upath);
154 if(wpath_need_elongation(wpath))
155 {
156 wlpath = wlongpath(wpath);
157 free(wpath);
158 wpath = wlpath;
159 }
160 return wpath;
161 }
162
163 #endif
164
165 #else
166
u2wlongpath(const char * upath)167 static wchar_t* u2wlongpath(const char *upath)
168 {
169 wchar_t* wpath, *p;
170 if (!upath || win32_utf8_wide(upath, &wpath, NULL) < 1)
171 return NULL;
172 for (p = wpath; *p; ++p)
173 if (*p == L'/')
174 *p = L'\\';
175 return wpath;
176 }
177
178 #endif
179
180 /* Always add a default permission mask in case of flags|O_CREAT. */
compat_open(const char * filename,int flags)181 int compat_open(const char *filename, int flags)
182 {
183 int ret;
184 #if defined (WANT_WIN32_UNICODE)
185 wchar_t *frag = NULL;
186
187 frag = u2wlongpath(filename);
188 /* Fallback to plain open when ucs-2 conversion fails */
189 if(!frag)
190 goto open_fallback;
191
192 /*Try _wopen */
193 ret = _wopen(frag, flags|_O_BINARY, _S_IREAD | _S_IWRITE);
194 if(ret != -1 )
195 goto open_ok; /* msdn says -1 means failure */
196
197 open_fallback:
198 #endif
199
200 #if (defined(WIN32) && !defined (__CYGWIN__))
201 /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */
202 /* Try plain old _open(), if it fails, do nothing */
203 ret = _open(filename, flags|_O_BINARY, _S_IREAD | _S_IWRITE);
204 #else
205 ret = open(filename, flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
206 #endif
207
208 #if defined (WANT_WIN32_UNICODE)
209 open_ok:
210 free(frag);
211 #endif
212
213 return ret;
214 }
215
216 /* Moved over from wav.c, logic with fallbacks added from the
217 example of compat_open(). */
compat_fopen(const char * filename,const char * mode)218 FILE* compat_fopen(const char *filename, const char *mode)
219 {
220 FILE* stream = NULL;
221 #ifdef WANT_WIN32_UNICODE
222 int cnt = 0;
223 wchar_t *wname = NULL;
224 wchar_t *wmode = NULL;
225
226 wname = u2wlongpath(filename);
227 if(!wname)
228 goto fopen_fallback;
229 cnt = win32_utf8_wide(mode, &wmode, NULL);
230 if( (wmode == NULL) || (cnt == 0))
231 goto fopen_fallback;
232
233 stream = _wfopen(wname, wmode);
234 if(stream) goto fopen_ok;
235
236 fopen_fallback:
237 #endif
238 stream = fopen(filename, mode);
239 #ifdef WANT_WIN32_UNICODE
240
241 fopen_ok:
242 free(wmode);
243 free(wname);
244 #endif
245 return stream;
246 }
247
compat_fdopen(int fd,const char * mode)248 FILE* compat_fdopen(int fd, const char *mode)
249 {
250 return fdopen(fd, mode);
251 }
252
compat_close(int infd)253 int compat_close(int infd)
254 {
255 #if (defined(WIN32) && !defined (__CYGWIN__)) /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */
256 return _close(infd);
257 #else
258 return close(infd);
259 #endif
260 }
261
compat_fclose(FILE * stream)262 int compat_fclose(FILE *stream)
263 {
264 return fclose(stream);
265 }
266
267 /* Windows Unicode stuff */
268
269 #ifdef WANT_WIN32_UNICODE
win32_wide_utf8(const wchar_t * const wptr,char ** mbptr,size_t * buflen)270 int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen)
271 {
272 size_t len;
273 char *buf;
274 int ret = 0;
275
276 len = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, NULL, 0, NULL, NULL); /* Get utf-8 string length */
277 buf = calloc(len + 1, sizeof (char)); /* Can we assume sizeof char always = 1? */
278
279 if(!buf) len = 0;
280 else {
281 if (len != 0) ret = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, buf, len, NULL, NULL); /*Do actual conversion*/
282 buf[len] = '0'; /* Must terminate */
283 }
284 *mbptr = buf; /* Set string pointer to allocated buffer */
285 if(buflen != NULL) *buflen = (len) * sizeof (char); /* Give length of allocated memory if needed. */
286 return ret;
287 }
288
win32_utf8_wide(const char * const mbptr,wchar_t ** wptr,size_t * buflen)289 int win32_utf8_wide(const char *const mbptr, wchar_t **wptr, size_t *buflen)
290 {
291 size_t len;
292 wchar_t *buf;
293 int ret = 0;
294
295 len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, NULL, 0); /* Get converted size */
296 buf = calloc(len + 1, sizeof (wchar_t)); /* Allocate memory accordingly */
297
298 if(!buf) len = 0;
299 else {
300 if (len != 0) ret = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, buf, len); /* Do conversion */
301 buf[len] = L'0'; /* Must terminate */
302 }
303 *wptr = buf; /* Set string pointer to allocated buffer */
304 if (buflen != NULL) *buflen = len * sizeof (wchar_t); /* Give length of allocated memory if needed. */
305 return ret; /* Number of characters written */
306 }
307 #endif
308
309 #ifndef WINDOWS_UWP
310
311 /*
312 The Windows file and path stuff is an extract of jon_y's win32 loader
313 prototype from the loader_rework branch. It's been divided in to
314 reusable functons by ThOr in the hope to work out some generic-looking
315 loader code for both POSIX and Windows. The routines might be
316 helpful for consistent path work in other parts of mpg123, too.
317
318 This all is about getting some working code on a wide range of
319 systems while staying somewhat sane. If it does ridiculously inefficient
320 things with extraneous copies and grabbing of functions that made
321 it late to some official APIs, that's still fine with us.
322 */
323
compat_catpath(const char * prefix,const char * path)324 char* compat_catpath(const char *prefix, const char* path)
325 {
326 char *ret = NULL;
327 #ifdef WANT_WIN32_UNICODE
328 wchar_t *wprefix = NULL; /* Wide windows versions of */
329 wchar_t *wpath = NULL; /* input arguments. */
330 wchar_t *locwret = NULL; /* Tmp return value from LocalAlloc */
331 /*
332 This variation of combinepath can work with long and UNC paths, but
333 is not officially exposed in any DLLs, It also allocates all its buffers
334 internally via LocalAlloc, avoiding buffer overflow problems.
335 ThOr: I presume this hack is for supporting pre-8 Windows, as
336 from Windows 8 on, this is documented in the API.
337 */
338 HRESULT (__stdcall *mypac)( const wchar_t *in, const wchar_t* more
339 , unsigned long flags, wchar_t **out ) = NULL;
340 HMODULE pathcch = NULL;
341
342 if(!prefix && !path)
343 goto catpath_end;
344 wprefix = u2wpath(prefix);
345 wpath = u2wpath(path);
346 if((prefix && !wprefix) || (path && !wpath))
347 goto catpath_end;
348
349 /* Again: I presume this whole fun is to get at PathAllocCombine
350 even when pathcch.h is not available (like in MinGW32). */
351 if( (pathcch = GetModuleHandleA("kernelbase")) )
352 mypac = (void *)GetProcAddress(pathcch, "PathAllocCombine");
353 if(mypac) /* PATHCCH_ALLOW_LONG_PATH = 1 per API docs */
354 {
355 debug("Actually calling PathAllocCombine!");
356 mypac(wprefix, wpath, 1, &locwret);
357 }
358 else
359 {
360 /* Playing safe, if we'd care much about performance, this would be on
361 the stack. */
362 locwret = LocalAlloc(LPTR, sizeof(wchar_t)*MAX_PATH);
363 if(locwret)
364 PathCombineW(locwret, wprefix, wpath);
365 }
366 ret = w2upath(locwret);
367
368 catpath_end:
369 LocalFree(locwret);
370 free(wprefix);
371 free(wpath);
372 #else
373 size_t len, prelen, patlen;
374
375 if(path && path[0] == '/')
376 prefix = NULL; /* Absolute path stays as it is. */
377 prelen = prefix ? strlen(prefix) : 0;
378 patlen = path ? strlen(path) : 0;
379 /* Concatenate the two, put a / in between if both present. */
380 len = ((prefix && path) ? 1 : 0) + prelen + patlen;
381 ret = malloc(len+1);
382 if(ret)
383 {
384 size_t off=0;
385 memcpy(ret, prefix, prelen);
386 if(prefix && path)
387 ret[prelen+(off++)] = '/';
388 memcpy(ret+prelen+off, path, patlen);
389 ret[len] = 0;
390 }
391 #endif
392 return ret;
393 }
394
compat_isdir(const char * path)395 int compat_isdir(const char *path)
396 {
397 int ret = 0;
398 #ifdef WANT_WIN32_UNICODE
399 wchar_t *wpath;
400 wpath = u2wlongpath(path);
401 if(wpath)
402 {
403 DWORD attr = GetFileAttributesW(wpath);
404 if(attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY)
405 ret=1;
406 free(wpath);
407 }
408 #else
409 struct stat sb;
410 if(path && !stat(path, &sb))
411 {
412 if(S_ISDIR(sb.st_mode))
413 ret=1;
414 }
415 #endif
416 return ret;
417 }
418
419 struct compat_dir
420 {
421 char *path;
422 #ifdef WANT_WIN32_UNICODE
423 int gotone; /* Got a result stored from FindFirstFileW. */
424 WIN32_FIND_DATAW d;
425 HANDLE ffn;
426 #else
427 DIR* dir;
428 #endif
429 };
430
compat_diropen(char * path)431 struct compat_dir* compat_diropen(char *path)
432 {
433 struct compat_dir *cd;
434 if(!path)
435 return NULL;
436 cd = malloc(sizeof(*cd));
437 if(!cd)
438 return NULL;
439 #ifdef WANT_WIN32_UNICODE
440 cd->gotone = 0;
441 {
442 char *pattern;
443 wchar_t *wpattern;
444 pattern = compat_catpath(path, "*");
445 wpattern = u2wlongpath(pattern);
446 if(wpattern)
447 {
448 cd->ffn = FindFirstFileW(wpattern, &(cd->d));
449 if(cd->ffn == INVALID_HANDLE_VALUE)
450 {
451 /* FindClose() only needed after successful first find, right? */
452 free(cd);
453 cd = NULL;
454 }
455 else
456 cd->gotone = 1;
457 }
458 free(wpattern);
459 free(pattern);
460 }
461 #else
462 cd->dir = opendir(path);
463 if(!cd->dir)
464 {
465 free(cd);
466 cd = NULL;
467 }
468 #endif
469 if(cd)
470 {
471 cd->path = compat_strdup(path);
472 if(!cd->path)
473 {
474 compat_dirclose(cd);
475 cd = NULL;
476 }
477 }
478 return cd;
479 }
480
compat_dirclose(struct compat_dir * cd)481 void compat_dirclose(struct compat_dir *cd)
482 {
483 if(cd)
484 {
485 free(cd->path);
486 #ifdef WANT_WIN32_UNICODE
487 FindClose(cd->ffn);
488 #else
489 closedir(cd->dir);
490 #endif
491 free(cd);
492 }
493 }
494
compat_nextfile(struct compat_dir * cd)495 char* compat_nextfile(struct compat_dir *cd)
496 {
497 if(!cd)
498 return NULL;
499 #ifdef WANT_WIN32_UNICODE
500 while(cd->gotone || FindNextFileW(cd->ffn, &(cd->d)))
501 {
502 cd->gotone = 0;
503 if(!(cd->d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
504 {
505 char *ret;
506 win32_wide_utf8(cd->d.cFileName, &ret, NULL);
507 return ret;
508 }
509 }
510 #else
511 {
512 struct dirent *dp;
513 while((dp = readdir(cd->dir)))
514 {
515 struct stat fst;
516 char *fullpath = compat_catpath(cd->path, dp->d_name);
517 if(fullpath && !stat(fullpath, &fst) && S_ISREG(fst.st_mode))
518 {
519 free(fullpath);
520 return compat_strdup(dp->d_name);
521 }
522 free(fullpath);
523 }
524 }
525 #endif
526 return NULL;
527 }
528
compat_nextdir(struct compat_dir * cd)529 char* compat_nextdir(struct compat_dir *cd)
530 {
531 if(!cd)
532 return NULL;
533 #ifdef WANT_WIN32_UNICODE
534 while(cd->gotone || FindNextFileW(cd->ffn, &(cd->d)))
535 {
536 cd->gotone = 0;
537 if(cd->d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
538 {
539 char *ret;
540 win32_wide_utf8(cd->d.cFileName, &ret, NULL);
541 return ret;
542 }
543 }
544 #else
545 {
546 struct dirent *dp;
547 while((dp = readdir(cd->dir)))
548 {
549 struct stat fst;
550 char *fullpath = compat_catpath(cd->path, dp->d_name);
551 if(fullpath && !stat(fullpath, &fst) && S_ISDIR(fst.st_mode))
552 {
553 free(fullpath);
554 return compat_strdup(dp->d_name);
555 }
556 free(fullpath);
557 }
558 }
559 #endif
560 return NULL;
561 }
562
563 #endif
564
565 #ifdef USE_MODULES
566 /*
567 This is what I expected the platform-specific dance for dynamic module
568 support to be. Little did I know about the peculiarities of (long)
569 paths and directory/file search on Windows.
570 */
571
compat_dlopen(const char * path)572 void *compat_dlopen(const char *path)
573 {
574 void *handle = NULL;
575 #ifdef WANT_WIN32_UNICODE
576 wchar_t *wpath;
577 wpath = u2wlongpath(path);
578 if(wpath)
579 handle = LoadLibraryW(wpath);
580 free(wpath);
581 #else
582 handle = dlopen(path, RTLD_NOW);
583 #endif
584 return handle;
585 }
586
compat_dlsym(void * handle,const char * name)587 void *compat_dlsym(void *handle, const char *name)
588 {
589 void *sym = NULL;
590 if(!handle)
591 return NULL;
592 #ifdef WANT_WIN32_UNICODE
593 sym = GetProcAddress(handle, name);
594 #else
595 sym = dlsym(handle, name);
596 #endif
597 return sym;
598 }
599
compat_dlclose(void * handle)600 void compat_dlclose(void *handle)
601 {
602 if(!handle)
603 return;
604 #ifdef WANT_WIN32_UNICODE
605 FreeLibrary(handle);
606 #else
607 dlclose(handle);
608 #endif
609 }
610
611 #endif /* USE_MODULES */
612
613
614 /* This shall survive signals and any return value less than given byte count
615 is an error */
unintr_write(int fd,void const * buffer,size_t bytes)616 size_t unintr_write(int fd, void const *buffer, size_t bytes)
617 {
618 size_t written = 0;
619 while(bytes)
620 {
621 ssize_t part = write(fd, (char*)buffer+written, bytes);
622 if(part < 0 && errno != EINTR)
623 break;
624 bytes -= part;
625 written += part;
626 }
627 return written;
628 }
629
630 /* Same for reading the data. */
unintr_read(int fd,void * buffer,size_t bytes)631 size_t unintr_read(int fd, void *buffer, size_t bytes)
632 {
633 size_t got = 0;
634 while(bytes)
635 {
636 ssize_t part = read(fd, (char*)buffer+got, bytes);
637 if(part < 0 && errno != EINTR)
638 break;
639 bytes -= part;
640 got += part;
641 }
642 return got;
643 }
644
645 #ifndef NO_CATCHSIGNAL
646 #if (!defined(WIN32) || defined (__CYGWIN__)) && defined(HAVE_SIGNAL_H)
catchsignal(int signum,void (* handler)())647 void (*catchsignal(int signum, void(*handler)()))()
648 {
649 struct sigaction new_sa;
650 struct sigaction old_sa;
651
652 #ifdef DONT_CATCH_SIGNALS
653 fprintf (stderr, "Not catching any signals.\n");
654 return ((void (*)()) -1);
655 #endif
656
657 new_sa.sa_handler = handler;
658 sigemptyset(&new_sa.sa_mask);
659 new_sa.sa_flags = 0;
660 if(sigaction(signum, &new_sa, &old_sa) == -1)
661 return ((void (*)()) -1);
662 return (old_sa.sa_handler);
663 }
664 #endif
665 #endif
666