1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2004-2005 Timo Hirvonen
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "misc.h"
20 #include "prog.h"
21 #include "xmalloc.h"
22 #include "xstrjoin.h"
23 #include "ui_curses.h"
24 #include "config/libdir.h"
25 #include "config/datadir.h"
26 
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <dirent.h>
35 #include <stdarg.h>
36 #include <pwd.h>
37 
38 const char *cmus_config_dir = NULL;
39 const char *cmus_playlist_dir = NULL;
40 const char *cmus_socket_path = NULL;
41 const char *cmus_data_dir = NULL;
42 const char *cmus_lib_dir = NULL;
43 const char *home_dir = NULL;
44 
get_words(const char * text)45 char **get_words(const char *text)
46 {
47 	char **words;
48 	int i, j, count;
49 
50 	while (*text == ' ')
51 		text++;
52 
53 	count = 0;
54 	i = 0;
55 	while (text[i]) {
56 		count++;
57 		while (text[i] && text[i] != ' ')
58 			i++;
59 		while (text[i] == ' ')
60 			i++;
61 	}
62 	words = xnew(char *, count + 1);
63 
64 	i = 0;
65 	j = 0;
66 	while (text[i]) {
67 		int start = i;
68 
69 		while (text[i] && text[i] != ' ')
70 			i++;
71 		words[j++] = xstrndup(text + start, i - start);
72 		while (text[i] == ' ')
73 			i++;
74 	}
75 	words[j] = NULL;
76 	return words;
77 }
78 
strptrcmp(const void * a,const void * b)79 int strptrcmp(const void *a, const void *b)
80 {
81 	const char *as = *(char **)a;
82 	const char *bs = *(char **)b;
83 
84 	return strcmp(as, bs);
85 }
86 
strptrcoll(const void * a,const void * b)87 int strptrcoll(const void *a, const void *b)
88 {
89 	const char *as = *(char **)a;
90 	const char *bs = *(char **)b;
91 
92 	return strcoll(as, bs);
93 }
94 
escape(const char * str)95 const char *escape(const char *str)
96 {
97 	static char *buf = NULL;
98 	static size_t alloc = 0;
99 	size_t len = strlen(str);
100 	size_t need = len * 2 + 1;
101 	int s, d;
102 
103 	if (need > alloc) {
104 		alloc = (need + 16) & ~(16 - 1);
105 		buf = xrealloc(buf, alloc);
106 	}
107 
108 	d = 0;
109 	for (s = 0; str[s]; s++) {
110 		if (str[s] == '\\') {
111 			buf[d++] = '\\';
112 			buf[d++] = '\\';
113 			continue;
114 		}
115 		if (str[s] == '\n') {
116 			buf[d++] = '\\';
117 			buf[d++] = 'n';
118 			continue;
119 		}
120 		buf[d++] = str[s];
121 	}
122 	buf[d] = 0;
123 	return buf;
124 }
125 
unescape(const char * str)126 const char *unescape(const char *str)
127 {
128 	static char *buf = NULL;
129 	static size_t alloc = 0;
130 	size_t need = strlen(str) + 1;
131 	int do_escape = 0;
132 	int s, d;
133 
134 	if (need > alloc) {
135 		alloc = (need + 16) & ~(16 - 1);
136 		buf = xrealloc(buf, alloc);
137 	}
138 
139 	d = 0;
140 	for (s = 0; str[s]; s++) {
141 		if (!do_escape && str[s] == '\\')
142 			do_escape = 1;
143 		else {
144 			buf[d++] = (do_escape && str[s] == 'n') ? '\n' : str[s];
145 			do_escape = 0;
146 		}
147 	}
148 	buf[d] = 0;
149 	return buf;
150 }
151 
dir_exists(const char * dirname)152 static int dir_exists(const char *dirname)
153 {
154 	DIR *dir;
155 
156 	dir = opendir(dirname);
157 	if (dir == NULL) {
158 		if (errno == ENOENT)
159 			return 0;
160 		return -1;
161 	}
162 	closedir(dir);
163 	return 1;
164 }
165 
make_dir(const char * dirname)166 static void make_dir(const char *dirname)
167 {
168 	int rc;
169 
170 	rc = dir_exists(dirname);
171 	if (rc == 1)
172 		return;
173 	if (rc == -1)
174 		die_errno("error: opening `%s'", dirname);
175 	rc = mkdir(dirname, 0700);
176 	if (rc == -1)
177 		die_errno("error: creating directory `%s'", dirname);
178 }
179 
get_non_empty_env(const char * name)180 static char *get_non_empty_env(const char *name)
181 {
182 	const char *val;
183 
184 	val = getenv(name);
185 	if (val == NULL || val[0] == 0)
186 		return NULL;
187 	return xstrdup(val);
188 }
189 
get_filename(const char * path)190 const char *get_filename(const char *path)
191 {
192 	const char *file = strrchr(path, '/');
193 	if (!file)
194 		file = path;
195 	else
196 		file++;
197 	if (!*file)
198 		return NULL;
199 	return file;
200 }
201 
move_old_playlist(void)202 static void move_old_playlist(void)
203 {
204 	char *default_playlist = xstrjoin(cmus_playlist_dir, "/default");
205 	char *old_playlist = xstrjoin(cmus_config_dir, "/playlist.pl");
206 	int rc = rename(old_playlist, default_playlist);
207 	if (rc && errno != ENOENT)
208 		die_errno("error: unable to move %s to playlist directory",
209 				old_playlist);
210 	free(default_playlist);
211 	free(old_playlist);
212 }
213 
misc_init(void)214 int misc_init(void)
215 {
216 	char *xdg_runtime_dir = get_non_empty_env("XDG_RUNTIME_DIR");
217 
218 	home_dir = get_non_empty_env("HOME");
219 	if (home_dir == NULL)
220 		die("error: environment variable HOME not set\n");
221 
222 	cmus_config_dir = get_non_empty_env("CMUS_HOME");
223 	if (cmus_config_dir == NULL) {
224 		char *cmus_home = xstrjoin(home_dir, "/.cmus");
225 		int cmus_home_exists = dir_exists(cmus_home);
226 
227 		if (cmus_home_exists == 1) {
228 			cmus_config_dir = xstrdup(cmus_home);
229 		} else if (cmus_home_exists == -1) {
230 			die_errno("error: opening `%s'", cmus_home);
231 		} else {
232 			char *xdg_config_home = get_non_empty_env("XDG_CONFIG_HOME");
233 			if (xdg_config_home == NULL) {
234 				xdg_config_home = xstrjoin(home_dir, "/.config");
235 			}
236 
237 			make_dir(xdg_config_home);
238 			cmus_config_dir = xstrjoin(xdg_config_home, "/cmus");
239 
240 			free(xdg_config_home);
241 		}
242 
243 		free(cmus_home);
244 	}
245 	make_dir(cmus_config_dir);
246 
247 	cmus_playlist_dir = get_non_empty_env("CMUS_PLAYLIST_DIR");
248 	if (!cmus_playlist_dir)
249 		cmus_playlist_dir = xstrjoin(cmus_config_dir, "/playlists");
250 
251 	int playlist_dir_is_new = dir_exists(cmus_playlist_dir) == 0;
252 	make_dir(cmus_playlist_dir);
253 	if (playlist_dir_is_new)
254 		move_old_playlist();
255 
256 	cmus_socket_path = get_non_empty_env("CMUS_SOCKET");
257 	if (cmus_socket_path == NULL) {
258 		if (xdg_runtime_dir == NULL) {
259 			cmus_socket_path = xstrjoin(cmus_config_dir, "/socket");
260 		} else {
261 			cmus_socket_path = xstrjoin(xdg_runtime_dir, "/cmus-socket");
262 		}
263 	}
264 
265 	cmus_lib_dir = getenv("CMUS_LIB_DIR");
266 	if (!cmus_lib_dir)
267 		cmus_lib_dir = LIBDIR "/cmus";
268 
269 	cmus_data_dir = getenv("CMUS_DATA_DIR");
270 	if (!cmus_data_dir)
271 		cmus_data_dir = DATADIR "/cmus";
272 
273 	free(xdg_runtime_dir);
274 	return 0;
275 }
276 
replaygain_decode(unsigned int field,int * gain)277 int replaygain_decode(unsigned int field, int *gain)
278 {
279 	unsigned int name_code, originator_code, sign_bit, val;
280 
281 	name_code = (field >> 13) & 0x7;
282 	if (!name_code || name_code > 2)
283 		return 0;
284 	originator_code = (field >> 10) & 0x7;
285 	if (!originator_code)
286 		return 0;
287 	sign_bit = (field >> 9) & 0x1;
288 	val = field & 0x1ff;
289 	if (sign_bit && !val)
290 		return 0;
291 	*gain = (sign_bit ? -1 : 1) * val;
292 	return name_code;
293 }
294 
get_home_dir(const char * username)295 static char *get_home_dir(const char *username)
296 {
297 	struct passwd *passwd;
298 
299 	if (username == NULL)
300 		return xstrdup(home_dir);
301 	passwd = getpwnam(username);
302 	if (passwd == NULL)
303 		return NULL;
304 	/* don't free passwd */
305 	return xstrdup(passwd->pw_dir);
306 }
307 
expand_filename(const char * name)308 char *expand_filename(const char *name)
309 {
310 	if (name[0] == '~') {
311 		char *slash;
312 
313 		slash = strchr(name, '/');
314 		if (slash) {
315 			char *username, *home;
316 
317 			if (slash - name - 1 > 0) {
318 				/* ~user/... */
319 				username = xstrndup(name + 1, slash - name - 1);
320 			} else {
321 				/* ~/... */
322 				username = NULL;
323 			}
324 			home = get_home_dir(username);
325 			free(username);
326 			if (home) {
327 				char *expanded;
328 
329 				expanded = xstrjoin(home, slash);
330 				free(home);
331 				return expanded;
332 			} else {
333 				return xstrdup(name);
334 			}
335 		} else {
336 			if (name[1] == 0) {
337 				return xstrdup(home_dir);
338 			} else {
339 				char *home;
340 
341 				home = get_home_dir(name + 1);
342 				if (home)
343 					return home;
344 				return xstrdup(name);
345 			}
346 		}
347 	} else {
348 		return xstrdup(name);
349 	}
350 }
351 
shuffle_array(void * array,size_t n,size_t size)352 void shuffle_array(void *array, size_t n, size_t size)
353 {
354 	char tmp[size];
355 	char *arr = array;
356 	for (ssize_t i = 0; i < (ssize_t)n - 1; ++i) {
357 		size_t rnd = (size_t) rand();
358 		size_t j = i + rnd / (RAND_MAX / (n - i) + 1);
359 		memcpy(tmp, arr + j * size, size);
360 		memcpy(arr + j * size, arr + i * size, size);
361 		memcpy(arr + i * size, tmp, size);
362 	}
363 }
364