1 /*
2   Copyright 2007-2016 David Robillard <http://drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 #define _POSIX_C_SOURCE 200809L  /* for fileno */
18 #define _BSD_SOURCE     1        /* for realpath, symlink */
19 #define _DEFAULT_SOURCE 1        /* for realpath, symlink */
20 
21 #ifdef __APPLE__
22 #    define _DARWIN_C_SOURCE 1  /* for flock */
23 #endif
24 
25 #include <ctype.h>
26 #include <errno.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <stddef.h>
32 
33 #ifdef _WIN32
34 #ifndef _WIN32_WINNT
35 #    define _WIN32_WINNT 0x0600  /* for CreateSymbolicLink */
36 #endif
37 #    include <windows.h>
38 #    include <direct.h>
39 #    include <io.h>
40 #    define F_OK 0
41 #    define mkdir(path, flags) _mkdir(path)
42 /** Implement 'CreateSymbolicLink()' for MSVC 8 or earlier */
43 BOOLEAN WINAPI
CreateSymbolicLink(LPCTSTR linkpath,LPCTSTR targetpath,DWORD flags)44 CreateSymbolicLink(LPCTSTR linkpath, LPCTSTR targetpath, DWORD flags)
45 {
46 	typedef BOOLEAN (WINAPI* PFUNC)(LPCTSTR, LPCTSTR, DWORD);
47 
48 	PFUNC pfn = (PFUNC)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
49 	                                  "CreateSymbolicLinkA");
50 	return pfn ? pfn(linkpath, targetpath, flags) : 0;
51 }
52 #else
53 #    include <dirent.h>
54 #    include <limits.h>
55 #    include <unistd.h>
56 #endif
57 
58 #include <sys/stat.h>
59 #include <sys/types.h>
60 
61 #include "lilv_internal.h"
62 
63 #if defined(HAVE_FLOCK) && defined(HAVE_FILENO)
64 #    include <sys/file.h>
65 #endif
66 
67 #ifndef PAGE_SIZE
68 #    define PAGE_SIZE 4096
69 #endif
70 
71 void
lilv_free(void * ptr)72 lilv_free(void* ptr)
73 {
74 	free(ptr);
75 }
76 
77 char*
lilv_strjoin(const char * first,...)78 lilv_strjoin(const char* first, ...)
79 {
80 	size_t  len    = strlen(first);
81 	char*   result = (char*)malloc(len + 1);
82 
83 	memcpy(result, first, len);
84 
85 	va_list args;
86 	va_start(args, first);
87 	while (1) {
88 		const char* const s = va_arg(args, const char *);
89 		if (s == NULL)
90 			break;
91 
92 		const size_t this_len   = strlen(s);
93 		char*        new_result = (char*)realloc(result, len + this_len + 1);
94 		if (!new_result) {
95 			free(result);
96 			return NULL;
97 		}
98 
99 		result = new_result;
100 		memcpy(result + len, s, this_len);
101 		len += this_len;
102 	}
103 	va_end(args);
104 
105 	result[len] = '\0';
106 
107 	return result;
108 }
109 
110 char*
lilv_strdup(const char * str)111 lilv_strdup(const char* str)
112 {
113 	if (!str) {
114 		return NULL;
115 	}
116 
117 	const size_t len  = strlen(str);
118 	char*        copy = (char*)malloc(len + 1);
119 	memcpy(copy, str, len + 1);
120 	return copy;
121 }
122 
123 const char*
lilv_uri_to_path(const char * uri)124 lilv_uri_to_path(const char* uri)
125 {
126 	return (const char*)serd_uri_to_path((const uint8_t*)uri);
127 }
128 
129 char*
lilv_file_uri_parse(const char * uri,char ** hostname)130 lilv_file_uri_parse(const char* uri, char** hostname)
131 {
132 	return (char*)serd_file_uri_parse((const uint8_t*)uri, (uint8_t**)hostname);
133 }
134 
135 /** Return the current LANG converted to Turtle (i.e. RFC3066) style.
136  * For example, if LANG is set to "en_CA.utf-8", this returns "en-ca".
137  */
138 char*
lilv_get_lang(void)139 lilv_get_lang(void)
140 {
141 	const char* const env_lang = getenv("LANG");
142 	if (!env_lang || !strcmp(env_lang, "")
143 	    || !strcmp(env_lang, "C") || !strcmp(env_lang, "POSIX")) {
144 		return NULL;
145 	}
146 
147 	const size_t env_lang_len = strlen(env_lang);
148 	char* const  lang         = (char*)malloc(env_lang_len + 1);
149 	for (size_t i = 0; i < env_lang_len + 1; ++i) {
150 		if (env_lang[i] == '_') {
151 			lang[i] = '-';  // Convert _ to -
152 		} else if (env_lang[i] >= 'A' && env_lang[i] <= 'Z') {
153 			lang[i] = env_lang[i] + ('a' - 'A');  // Convert to lowercase
154 		} else if (env_lang[i] >= 'a' && env_lang[i] <= 'z') {
155 			lang[i] = env_lang[i];  // Lowercase letter, copy verbatim
156 		} else if (env_lang[i] >= '0' && env_lang[i] <= '9') {
157 			lang[i] = env_lang[i];  // Digit, copy verbatim
158 		} else if (env_lang[i] == '\0' || env_lang[i] == '.') {
159 			// End, or start of suffix (e.g. en_CA.utf-8), finished
160 			lang[i] = '\0';
161 			break;
162 		} else {
163 			LILV_ERRORF("Illegal LANG `%s' ignored\n", env_lang);
164 			free(lang);
165 			return NULL;
166 		}
167 	}
168 
169 	return lang;
170 }
171 
172 /** Append suffix to dst, update dst_len, and return the realloc'd result. */
173 static char*
strappend(char * dst,size_t * dst_len,const char * suffix,size_t suffix_len)174 strappend(char* dst, size_t* dst_len, const char* suffix, size_t suffix_len)
175 {
176 	dst = (char*)realloc(dst, *dst_len + suffix_len + 1);
177 	memcpy(dst + *dst_len, suffix, suffix_len);
178 	dst[(*dst_len += suffix_len)] = '\0';
179 	return dst;
180 }
181 
182 /** Append the value of the environment variable var to dst. */
183 static char*
append_var(char * dst,size_t * dst_len,const char * var)184 append_var(char* dst, size_t* dst_len, const char* var)
185 {
186 	// Get value from environment
187 	const char* val = getenv(var);
188 	if (val) {  // Value found, append it
189 		return strappend(dst, dst_len, val, strlen(val));
190 	} else {  // No value found, append variable reference as-is
191 		return strappend(strappend(dst, dst_len, "$", 1),
192 		                 dst_len, var, strlen(var));
193 	}
194 }
195 
196 /** Expand variables (e.g. POSIX ~ or $FOO, Windows %FOO%) in `path`. */
197 char*
lilv_expand(const char * path)198 lilv_expand(const char* path)
199 {
200 #ifdef _WIN32
201 	char* out = (char*)malloc(MAX_PATH);
202 	ExpandEnvironmentStrings(path, out, MAX_PATH);
203 #else
204 	char*  out = NULL;
205 	size_t len = 0;
206 
207 	const char* start = path;  // Start of current chunk to copy
208 	for (const char* s = path; *s;) {
209 		if (*s == '$') {
210 			// Hit $ (variable reference, e.g. $VAR_NAME)
211 			for (const char* t = s + 1; ; ++t) {
212 				if (!*t || (!isupper(*t) && !isdigit(*t) && *t != '_')) {
213 					// Append preceding chunk
214 					out = strappend(out, &len, start, s - start);
215 
216 					// Append variable value (or $VAR_NAME if not found)
217 					char* var = (char*)calloc(t - s, 1);
218 					memcpy(var, s + 1, t - s - 1);
219 					out = append_var(out, &len, var);
220 					free(var);
221 
222 					// Continue after variable reference
223 					start = s = t;
224 					break;
225 				}
226 			}
227 		} else if (*s == '~' && (*(s + 1) == '/' || !*(s + 1))) {
228 			// Hit ~ before slash or end of string (home directory reference)
229 			out = strappend(out, &len, start, s - start);
230 			out = append_var(out, &len, "HOME");
231 			start = ++s;
232 		} else {
233 			++s;
234 		}
235 	}
236 
237 	if (*start) {
238 		out = strappend(out, &len, start, strlen(start));
239 	}
240 #endif
241 
242 	return out;
243 }
244 
245 static bool
lilv_is_dir_sep(const char c)246 lilv_is_dir_sep(const char c)
247 {
248 	return c == '/' || c == LILV_DIR_SEP[0];
249 }
250 
251 char*
lilv_dirname(const char * path)252 lilv_dirname(const char* path)
253 {
254 	const char* s = path + strlen(path) - 1;  // Last character
255 	for (; s > path && lilv_is_dir_sep(*s); --s) {}  // Last non-slash
256 	for (; s > path && !lilv_is_dir_sep(*s); --s) {}  // Last internal slash
257 	for (; s > path && lilv_is_dir_sep(*s); --s) {}  // Skip duplicates
258 
259 	if (s == path) {  // Hit beginning
260 		return lilv_is_dir_sep(*s) ? lilv_strdup("/") : lilv_strdup(".");
261 	} else {  // Pointing to the last character of the result (inclusive)
262 		char* dirname = (char*)malloc(s - path + 2);
263 		memcpy(dirname, path, s - path + 1);
264 		dirname[s - path + 1] = '\0';
265 		return dirname;
266 	}
267 }
268 
269 bool
lilv_path_exists(const char * path,void * ignored)270 lilv_path_exists(const char* path, void* ignored)
271 {
272 	return !access(path, F_OK);
273 }
274 
275 char*
lilv_find_free_path(const char * in_path,bool (* exists)(const char *,void *),void * user_data)276 lilv_find_free_path(const char* in_path,
277                     bool (*exists)(const char*, void*), void* user_data)
278 {
279 	const size_t in_path_len = strlen(in_path);
280 	char*        path        = (char*)malloc(in_path_len + 7);
281 	memcpy(path, in_path, in_path_len + 1);
282 
283 	for (int i = 2; i < 1000000; ++i) {
284 		if (!exists(path, user_data)) {
285 			return path;
286 		}
287 		snprintf(path, in_path_len + 7, "%s.%u", in_path, i);
288 	}
289 
290 	return NULL;
291 }
292 
293 int
lilv_copy_file(const char * src,const char * dst)294 lilv_copy_file(const char* src, const char* dst)
295 {
296 	FILE* in = fopen(src, "r");
297 	if (!in) {
298 		return errno;
299 	}
300 
301 	FILE* out = fopen(dst, "w");
302 	if (!out) {
303 		fclose(in);
304 		return errno;
305 	}
306 
307 	char*  page   = (char*)malloc(PAGE_SIZE);
308 	size_t n_read = 0;
309 	int    st     = 0;
310 	while ((n_read = fread(page, 1, PAGE_SIZE, in)) > 0) {
311 		if (fwrite(page, 1, n_read, out) != n_read) {
312 			st = errno;
313 			break;
314 		}
315 	}
316 
317 	if (!st && (ferror(in) || ferror(out))) {
318 		st = EBADF;
319 	}
320 
321 	free(page);
322 	fclose(in);
323 	fclose(out);
324 
325 	return st;
326 }
327 
328 bool
lilv_path_is_absolute(const char * path)329 lilv_path_is_absolute(const char* path)
330 {
331 	if (lilv_is_dir_sep(path[0])) {
332 		return true;
333 	}
334 
335 #ifdef _WIN32
336 	if (isalpha(path[0]) && path[1] == ':' && lilv_is_dir_sep(path[2])) {
337 		return true;
338 	}
339 #endif
340 
341 	return false;
342 }
343 
344 char*
lilv_path_absolute(const char * path)345 lilv_path_absolute(const char* path)
346 {
347 	if (lilv_path_is_absolute(path)) {
348 		return lilv_strdup(path);
349 	} else {
350 		char* cwd      = getcwd(NULL, 0);
351 		char* abs_path = lilv_path_join(cwd, path);
352 		free(cwd);
353 		return abs_path;
354 	}
355 }
356 
357 char*
lilv_path_join(const char * a,const char * b)358 lilv_path_join(const char* a, const char* b)
359 {
360 	if (!a) {
361 		return lilv_strdup(b);
362 	}
363 
364 	const size_t a_len   = strlen(a);
365 	const size_t b_len   = b ? strlen(b) : 0;
366 	const size_t pre_len = a_len - (lilv_is_dir_sep(a[a_len - 1]) ? 1 : 0);
367 	char*        path    = (char*)calloc(1, a_len + b_len + 2);
368 	memcpy(path, a, pre_len);
369 	path[pre_len] = '/';
370 	if (b) {
371 		memcpy(path + pre_len + 1,
372 		       b + (lilv_is_dir_sep(b[0]) ? 1 : 0),
373 		       lilv_is_dir_sep(b[0]) ? b_len - 1 : b_len);
374 	}
375 	return path;
376 }
377 
378 typedef struct {
379 	char*  pattern;
380 	time_t time;
381 	char*  latest;
382 } Latest;
383 
384 static void
update_latest(const char * path,const char * name,void * data)385 update_latest(const char* path, const char* name, void* data)
386 {
387 	Latest*  latest     = (Latest*)data;
388 	char*    entry_path = lilv_path_join(path, name);
389 	unsigned num;
390 	if (sscanf(entry_path, latest->pattern, &num) == 1) {
391 		struct stat st;
392 		if (!stat(entry_path, &st)) {
393 			if (st.st_mtime >= latest->time) {
394 				free(latest->latest);
395 				latest->latest = entry_path;
396 			}
397 		} else {
398 			LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno));
399 		}
400 	}
401 	if (entry_path != latest->latest) {
402 		free(entry_path);
403 	}
404 }
405 
406 /** Return the latest copy of the file at `path` that is newer. */
407 char*
lilv_get_latest_copy(const char * path,const char * copy_path)408 lilv_get_latest_copy(const char* path, const char* copy_path)
409 {
410 	char*  copy_dir = lilv_dirname(copy_path);
411 	Latest latest   = { lilv_strjoin(copy_path, ".%u", NULL), 0, NULL };
412 
413 	struct stat st;
414 	if (!stat(path, &st)) {
415 		latest.time = st.st_mtime;
416 	} else {
417 		LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno));
418 	}
419 
420 	lilv_dir_for_each(copy_dir, &latest, update_latest);
421 
422 	free(latest.pattern);
423 	free(copy_dir);
424 	return latest.latest;
425 }
426 
427 char*
lilv_realpath(const char * path)428 lilv_realpath(const char* path)
429 {
430 #if defined(_WIN32)
431 	char* out = (char*)malloc(MAX_PATH);
432 	GetFullPathName(path, MAX_PATH, out, NULL);
433 	return out;
434 #elif _POSIX_VERSION >= 200809L
435 	char* real_path = realpath(path, NULL);
436 	return real_path ? real_path : lilv_strdup(path);
437 #else
438 	// OSX <= 10.5, if anyone cares.  I sure don't.
439 	char* out       = (char*)malloc(PATH_MAX);
440 	char* real_path = realpath(path, out);
441 	if (!real_path) {
442 		free(out);
443 		return lilv_strdup(path);
444 	} else {
445 		return real_path;
446 	}
447 #endif
448 }
449 
450 int
lilv_symlink(const char * oldpath,const char * newpath)451 lilv_symlink(const char* oldpath, const char* newpath)
452 {
453 	int ret = 0;
454 	if (strcmp(oldpath, newpath)) {
455 #ifdef _WIN32
456 		ret = 1;
457 #else
458 		ret = symlink(oldpath, newpath);
459 #endif
460 	}
461 	if (ret) {
462 		LILV_ERRORF("Failed to link %s => %s (%s)\n",
463 		            newpath, oldpath, strerror(errno));
464 	}
465 	return ret;
466 }
467 
468 char*
lilv_path_relative_to(const char * path,const char * base)469 lilv_path_relative_to(const char* path, const char* base)
470 {
471 	const size_t path_len = strlen(path);
472 	const size_t base_len = strlen(base);
473 	const size_t min_len  = (path_len < base_len) ? path_len : base_len;
474 
475 	// Find the last separator common to both paths
476 	size_t last_shared_sep = 0;
477 	for (size_t i = 0; i < min_len && path[i] == base[i]; ++i) {
478 		if (lilv_is_dir_sep(path[i])) {
479 			last_shared_sep = i;
480 		}
481 	}
482 
483 	if (last_shared_sep == 0) {
484 		// No common components, return path
485 		return lilv_strdup(path);
486 	}
487 
488 	// Find the number of up references ("..") required
489 	size_t up = 0;
490 	for (size_t i = last_shared_sep + 1; i < base_len; ++i) {
491 		if (lilv_is_dir_sep(base[i])) {
492 			++up;
493 		}
494 	}
495 
496 	// Write up references
497 	const size_t suffix_len = path_len - last_shared_sep;
498 	char*        rel        = (char*)calloc(1, suffix_len + (up * 3) + 1);
499 	for (size_t i = 0; i < up; ++i) {
500 		memcpy(rel + (i * 3), "../", 3);
501 	}
502 
503 	// Write suffix
504 	memcpy(rel + (up * 3), path + last_shared_sep + 1, suffix_len);
505 	return rel;
506 }
507 
508 bool
lilv_path_is_child(const char * path,const char * dir)509 lilv_path_is_child(const char* path, const char* dir)
510 {
511 	if (path && dir) {
512 		const size_t path_len = strlen(path);
513 		const size_t dir_len  = strlen(dir);
514 		return dir && path_len >= dir_len && !strncmp(path, dir, dir_len);
515 	}
516 	return false;
517 }
518 
519 int
lilv_flock(FILE * file,bool lock)520 lilv_flock(FILE* file, bool lock)
521 {
522 #if defined(HAVE_FLOCK) && defined(HAVE_FILENO)
523 	return flock(fileno(file), lock ? LOCK_EX : LOCK_UN);
524 #else
525 	return 0;
526 #endif
527 }
528 
529 void
lilv_dir_for_each(const char * path,void * data,void (* f)(const char * path,const char * name,void * data))530 lilv_dir_for_each(const char* path,
531                   void*       data,
532                   void (*f)(const char* path, const char* name, void* data))
533 {
534 #ifdef _WIN32
535 	char*           pat = lilv_path_join(path, "*");
536 	WIN32_FIND_DATA fd;
537 	HANDLE          fh  = FindFirstFile(pat, &fd);
538 	if (fh != INVALID_HANDLE_VALUE) {
539 		do {
540 			f(path, fd.cFileName, data);
541 		} while (FindNextFile(fh, &fd));
542 	}
543 	free(pat);
544 #else
545 	DIR* dir = opendir(path);
546 	if (dir) {
547 		long name_max = pathconf(path, _PC_NAME_MAX);
548 		if (name_max == -1) {
549 			name_max = 255;   // Limit not defined, or error
550 		}
551 
552 		const size_t   len    = offsetof(struct dirent, d_name) + name_max + 1;
553 		struct dirent* entry = (struct dirent*)malloc(len);
554 		struct dirent* result;
555 		while (!readdir_r(dir, entry, &result) && result) {
556 			f(path, entry->d_name, data);
557 		}
558 		free(entry);
559 		closedir(dir);
560 	}
561 #endif
562 }
563 
564 int
lilv_mkdir_p(const char * dir_path)565 lilv_mkdir_p(const char* dir_path)
566 {
567 	char*        path     = lilv_strdup(dir_path);
568 	const size_t path_len = strlen(path);
569 	for (size_t i = 1; i <= path_len; ++i) {
570 		if (path[i] == LILV_DIR_SEP[0] || path[i] == '\0') {
571 			path[i] = '\0';
572 			if (mkdir(path, 0755) && errno != EEXIST) {
573 				free(path);
574 				return errno;
575 			}
576 			path[i] = LILV_DIR_SEP[0];
577 		}
578 	}
579 
580 	free(path);
581 	return 0;
582 }
583 
584 static off_t
lilv_file_size(const char * path)585 lilv_file_size(const char* path)
586 {
587 	struct stat buf;
588 	if (stat(path, &buf)) {
589 		LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno));
590 		return 0;
591 	}
592 	return buf.st_size;
593 }
594 
595 bool
lilv_file_equals(const char * a_path,const char * b_path)596 lilv_file_equals(const char* a_path, const char* b_path)
597 {
598 	if (!strcmp(a_path, b_path)) {
599 		return true;  // Paths match
600 	}
601 
602 	bool        match  = false;
603 	FILE*       a_file = NULL;
604 	FILE*       b_file = NULL;
605 	char* const a_real = lilv_realpath(a_path);
606 	char* const b_real = lilv_realpath(b_path);
607 	if (!strcmp(a_real, b_real)) {
608 		match = true;  // Real paths match
609 	} else if (lilv_file_size(a_path) != lilv_file_size(b_path)) {
610 		match = false;  // Sizes differ
611 	} else if (!(a_file = fopen(a_real, "rb")) ||
612 	           !(b_file = fopen(b_real, "rb"))) {
613 		match = false;  // Missing file matches nothing
614 	} else {
615 		// TODO: Improve performance by reading chunks
616 		match = true;
617 		while (!feof(a_file) && !feof(b_file)) {
618 			if (fgetc(a_file) != fgetc(b_file)) {
619 				match = false;
620 				break;
621 			}
622 		}
623 	}
624 
625 	if (a_file) {
626 		fclose(a_file);
627 	}
628 	if (b_file) {
629 		fclose(b_file);
630 	}
631 	free(a_real);
632 	free(b_real);
633 	return match;
634 }
635