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