1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17  * Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 /* support.c - (non-GUI) useful routines */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <netdb.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <sys/param.h>
31 #include <pwd.h>
32 #include <grp.h>
33 #include <fcntl.h>
34 #include <sys/wait.h>
35 #include <string.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include <libxml/parser.h>
39 #include <math.h>
40 #include <sys/mman.h>
41 
42 #include "global.h"
43 
44 #include "choices.h"
45 #include "main.h"
46 #include "options.h"
47 #include "support.h"
48 #include "fscache.h"
49 #include "main.h"
50 #include "xml.h"
51 
52 static GHashTable *uid_hash = NULL;	/* UID -> User name */
53 static GHashTable *gid_hash = NULL;	/* GID -> Group name */
54 
55 /* Static prototypes */
56 static void MD5Transform(guint32 buf[4], guint32 const in[16]);
57 
58 /****************************************************************
59  *			EXTERNAL INTERFACE			*
60  ****************************************************************/
61 
62 /* g_object_unref() the result! */
xml_cache_load(const gchar * pathname)63 XMLwrapper *xml_cache_load(const gchar *pathname)
64 {
65 	static GFSCache *xml_cache = NULL;
66 
67 	if (!xml_cache)
68 		xml_cache = g_fscache_new((GFSLoadFunc) xml_new, NULL, NULL);
69 	return g_fscache_lookup(xml_cache, pathname);
70 }
71 
72 /* Save doc as XML as filename, 0 on success or 1 on failure */
save_xml_file(xmlDocPtr doc,const gchar * filename)73 int save_xml_file(xmlDocPtr doc, const gchar *filename)
74 {
75 #if LIBXML_VERSION > 20400
76 	if (xmlSaveFormatFileEnc(filename, doc, NULL, 1) < 0)
77 		return 1;
78 #else
79 	FILE *out;
80 
81 	out = fopen(filename, "w");
82 	if (!out)
83 		return 1;
84 
85 	xmlDocDump(out, doc);  /* Some versions return void */
86 
87 	if (fclose(out))
88 		return 1;
89 #endif
90 
91 	return 0;
92 }
93 
94 /* Create a new SOAP message and return the document and the (empty)
95  * body node.
96  */
soap_new(xmlNodePtr * ret_body)97 xmlDocPtr soap_new(xmlNodePtr *ret_body)
98 {
99 	xmlDocPtr  doc;
100 	xmlNodePtr root;
101 	xmlNs	   *env_ns;
102 
103 	doc = xmlNewDoc("1.0");
104 	root = xmlNewDocNode(doc, NULL, "Envelope", NULL);
105 	xmlDocSetRootElement(doc, root);
106 
107 	env_ns = xmlNewNs(root, SOAP_ENV_NS, "env");
108 	xmlSetNs(root, env_ns);
109 
110 	*ret_body = xmlNewTextChild(root, env_ns, "Body", NULL);
111 	xmlNewNs(*ret_body, ROX_NS, "rox");
112 
113 	return doc;
114 }
115 
116 /* Like g_strdup, but does realpath() too (if possible) */
pathdup(const char * path)117 char *pathdup(const char *path)
118 {
119 	char real[MAXPATHLEN];
120 
121 	g_return_val_if_fail(path != NULL, NULL);
122 
123 	if (realpath(path, real))
124 		return g_strdup(real);
125 
126 	return g_strdup(path);
127 }
128 
129 /* Join the path to the leaf (adding a / between them) and
130  * return a pointer to a static buffer with the result. Buffer is valid
131  * until the next call to make_path.
132  * The return value may be used as 'dir' for the next call.
133  */
make_path(const char * dir,const char * leaf)134 const guchar *make_path(const char *dir, const char *leaf)
135 {
136 	static GString *buffer = NULL;
137 
138 	if (!buffer)
139 		buffer = g_string_new(NULL);
140 
141 	g_return_val_if_fail(dir != NULL, buffer->str);
142 	g_return_val_if_fail(leaf != NULL, buffer->str);
143 
144 	if (buffer->str != dir)
145 		g_string_assign(buffer, dir);
146 
147 	if (dir[0] != '/' || dir[1] != '\0')
148 		g_string_append_c(buffer, '/');	/* For anything except "/" */
149 
150 	g_string_append(buffer, leaf);
151 
152 	return buffer->str;
153 }
154 
155 /* Return our complete host name for DND */
our_host_name_for_dnd(void)156 const char *our_host_name_for_dnd(void)
157 {
158 	if (o_dnd_no_hostnames.int_value)
159 		return "";
160 	return our_host_name();
161 }
162 
163 /* Return our complete host name, unconditionally */
our_host_name(void)164 const char *our_host_name(void)
165 {
166 	static char *name = NULL;
167 
168 	if (!name)
169 	{
170 		char buffer[4096];
171 
172 		if (gethostname(buffer, 4096) == 0)
173 		{
174 			/* gethostname doesn't always return the full name... */
175 			struct hostent *ent;
176 
177 			buffer[4095] = '\0';
178 			ent = gethostbyname(buffer);
179 			name = g_strdup(ent ? ent->h_name : buffer);
180 		}
181 		else
182 		{
183 			g_warning("gethostname() failed - using localhost\n");
184 			name = g_strdup("localhost");
185 		}
186 	}
187 
188 	return name;
189 }
190 
debug_free_string(void * data)191 void debug_free_string(void *data)
192 {
193 	g_print("Freeing string '%s'\n", (char *) data);
194 	g_free(data);
195 }
196 
user_name(uid_t uid)197 const char *user_name(uid_t uid)
198 {
199 	const char *retval;
200 
201 	if (!uid_hash)
202 		uid_hash = g_hash_table_new(NULL, NULL);
203 
204 	retval = g_hash_table_lookup(uid_hash, GINT_TO_POINTER(uid));
205 
206 	if (!retval)
207 	{
208 		struct passwd *passwd;
209 
210 		passwd = getpwuid(uid);
211 		retval = passwd ? g_strdup(passwd->pw_name)
212 			       : g_strdup_printf("[%d]", (int) uid);
213 		g_hash_table_insert(uid_hash, GINT_TO_POINTER(uid),
214 				(gchar *) retval);
215 	}
216 
217 	return retval;
218 }
219 
group_name(gid_t gid)220 const char *group_name(gid_t gid)
221 {
222 	const char *retval;
223 
224 	if (!gid_hash)
225 		gid_hash = g_hash_table_new(NULL, NULL);
226 
227 	retval = g_hash_table_lookup(gid_hash, GINT_TO_POINTER(gid));
228 
229 	if (!retval)
230 	{
231 		struct group *group;
232 
233 		group = getgrgid(gid);
234 		retval = group ? g_strdup(group->gr_name)
235 			       : g_strdup_printf("[%d]", (int) gid);
236 		g_hash_table_insert(gid_hash, GINT_TO_POINTER(gid),
237 				(gchar *) retval);
238 	}
239 
240 	return retval;
241 }
242 
243 /* Return a string in the form '23 M' in a static buffer valid until
244  * the next call.
245  */
format_size(off_t size)246 const char *format_size(off_t size)
247 {
248 	static	char *buffer = NULL;
249 	const char	*units;
250 
251 	if (size >= PRETTY_SIZE_LIMIT)
252 	{
253 		size += 1023;
254 		size >>= 10;
255 		if (size >= PRETTY_SIZE_LIMIT)
256 		{
257 			size += 1023;
258 			size >>= 10;
259 			if (size >= PRETTY_SIZE_LIMIT)
260 			{
261 				size += 1023;
262 				size >>= 10;
263 				units = "G";
264 			}
265 			else
266 				units = "M";
267 		}
268 		else
269 			units = "K";
270 	}
271 	else
272 		units = _("B");
273 
274 	g_free(buffer);
275 	buffer = g_strdup_printf("%" SIZE_FMT " %s", size, units);
276 
277 	return buffer;
278 }
279 
280 /* Return a string in the form '23M' in a static buffer valid until
281  * the next call. Aligned to the right (5 chars).
282  */
format_size_aligned(off_t size)283 const char *format_size_aligned(off_t size)
284 {
285 	static	char *buffer = NULL;
286 	char	units;
287 
288 	if (size >= PRETTY_SIZE_LIMIT)
289 	{
290 		size += 1023;
291 		size >>= 10;
292 		if (size >= PRETTY_SIZE_LIMIT)
293 		{
294 			size += 1023;
295 			size >>= 10;
296 			if (size >= PRETTY_SIZE_LIMIT)
297 			{
298 				size += 1023;
299 				size >>= 10;
300 				units = 'G';
301 			}
302 			else
303 				units = 'M';
304 		}
305 		else
306 			units = 'K';
307 	}
308 	else
309 		units = ' ';
310 
311 	g_free(buffer);
312 	buffer = g_strdup_printf("%4" SIZE_FMT "%c", size, units);
313 
314 	return buffer;
315 }
316 
317 /*
318  * Similar to format_size(), but this one uses a double argument since
319  * unsigned long isn't wide enough on all platforms and we must be able to
320  * sum sizes above 4 GB.
321  */
format_double_size(double size)322 const gchar *format_double_size(double size)
323 {
324 	static gchar	*buf = NULL;
325 	const char	*units;
326 
327 	if (size >= PRETTY_SIZE_LIMIT)
328 	{
329 		size += 1023;
330 		size /= 1024;
331 		if (size >= PRETTY_SIZE_LIMIT)
332 		{
333 			size += 1023;
334 			size /= 1024;
335 			if (size >= PRETTY_SIZE_LIMIT)
336 			{
337 				size += 1023;
338 				size /= 1024;
339 				units = "G";
340 			}
341 			else
342 				units = "M";
343 		}
344 		else
345 			units = "K";
346 
347 	}
348 	else if (size != 1)
349 		units = _("bytes");
350 	else
351 		units = _("byte");
352 
353 	g_free(buf);
354 	buf = g_strdup_printf("%.0f %s", floor(size), units);
355 
356 	return buf;
357 }
358 
359 /* Fork and exec argv. Wait and return the child's exit status.
360  * -1 if spawn fails.
361  * Returns the error string from the command if any, or NULL on success.
362  * If the process returns a non-zero exit status without producing a message,
363  * a suitable message is created.
364  * g_free() the result.
365  */
fork_exec_wait(const char ** argv)366 char *fork_exec_wait(const char **argv)
367 {
368 	int	status;
369 	gchar	*errors = NULL;
370 	GError	*error = NULL;
371 
372 	if (!g_spawn_sync(NULL, (char **) argv, NULL,
373 		     G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
374 		     NULL, NULL,
375 		     NULL, &errors, &status, &error))
376 	{
377 		char *msg;
378 
379 		msg = g_strdup(error->message);
380 		g_error_free(error);
381 		return msg;
382 	}
383 
384 	if (errors && !*errors)
385 		null_g_free(&errors);
386 
387 	if (!WIFEXITED(status))
388 	{
389 		if (!errors)
390 			errors = g_strdup("(Subprocess crashed?)");
391 	}
392 	else if (WEXITSTATUS(status))
393 	{
394 		if (!errors)
395 			errors = g_strdup(_("ERROR"));
396 	}
397 
398 	if (errors)
399 		g_strstrip(errors);
400 
401 	return errors;
402 }
403 
404 /* If a file has this UID and GID, which permissions apply to us?
405  * 0 = User, 1 = Group, 2 = World
406  */
applicable(uid_t uid,gid_t gid)407 gint applicable(uid_t uid, gid_t gid)
408 {
409 	int	i;
410 
411 	if (uid == euid)
412 		return 0;
413 
414 	if (gid == egid)
415 		return 1;
416 
417 	for (i = 0; i < ngroups; i++)
418 	{
419 		if (supplemental_groups[i] == gid)
420 			return 1;
421 	}
422 
423 	return 2;
424 }
425 
426 /* Converts a file's mode to a string. Result is a pointer
427  * to a static buffer, valid until the next call.
428  */
pretty_permissions(mode_t m)429 const char *pretty_permissions(mode_t m)
430 {
431 	static char buffer[] = "rwx,rwx,rwx/UG"
432 #ifdef S_ISVTX
433 	     "T"
434 #endif
435 	     ;
436 
437 	buffer[0]  = m & S_IRUSR ? 'r' : '-';
438 	buffer[1]  = m & S_IWUSR ? 'w' : '-';
439 	buffer[2]  = m & S_IXUSR ? 'x' : '-';
440 
441 	buffer[4]  = m & S_IRGRP ? 'r' : '-';
442 	buffer[5]  = m & S_IWGRP ? 'w' : '-';
443 	buffer[6]  = m & S_IXGRP ? 'x' : '-';
444 
445 	buffer[8]  = m & S_IROTH ? 'r' : '-';
446 	buffer[9]  = m & S_IWOTH ? 'w' : '-';
447 	buffer[10] = m & S_IXOTH ? 'x' : '-';
448 
449 	buffer[12] = m & S_ISUID ? 'U' : '-';
450 	buffer[13] = m & S_ISGID ? 'G' : '-';
451 #ifdef S_ISVTX
452         buffer[14] = m & S_ISVTX ? 'T' : '-';
453 #endif
454 
455 	return buffer;
456 }
457 
458 /* Gets the canonical name for address and compares to our_host_name() */
is_local_address(char * address)459 static gboolean is_local_address(char *address)
460 {
461 	struct hostent *ent;
462 
463 	ent = gethostbyname(address);
464 
465 	return strcmp(our_host_name(), ent ? ent->h_name : address) == 0;
466 }
467 
468 /* Convert a URI to a local pathname (or NULL if it isn't local).
469  * The returned pointer needs to be passed to g_free() when done (if not NULL).
470  * THIS IS A CHANGE. The return path has been processed by unescape_uri().
471  * Possible formats:
472  *	/path
473  *	///path
474  *	//host/path
475  *	file://host/path
476  */
get_local_path(const EscapedPath * escaped_uri)477 char *get_local_path(const EscapedPath *escaped_uri)
478 {
479 	const char *uri = (char *) escaped_uri;
480 
481 	if (*uri == '/')
482 	{
483 		char    *path, *uri_host;
484 
485 		/* Just a local path - no host part */
486 		if (uri[1] != '/')
487 			return unescape_uri((EscapedPath *) uri);
488 
489 		path = strchr(uri + 2, '/');
490 		if (!path)
491 			return NULL;	    /* //something */
492 
493 		if (path - uri == 2)
494 		{
495 			/* ///path */
496 			return unescape_uri((EscapedPath *) path);
497 		}
498 
499 		uri_host = g_strndup(uri + 2, path - uri - 2);
500 		if (is_local_address(uri_host))
501 		{
502 			g_free(uri_host);
503 			/* //myhost/path */
504 			return unescape_uri((EscapedPath *) path);
505 		}
506 		g_free(uri_host);
507 
508 		return NULL;	    /* From a different host */
509 	}
510 	else
511 	{
512 		if (strncasecmp(uri, "file:", 5))
513 			return NULL;	    /* Don't know this format */
514 
515 		uri += 5;
516 
517 		if (*uri == '/')
518 			return get_local_path((EscapedPath *) uri);
519 
520 		return NULL;
521 	}
522 }
523 
524 /* Return the scheme part of a URI, or NULL if not a valid URI (see
525  * RFC 2396).  g_free() the returned value.
526  */
527 #define SCHEME_CHARS "+-."  /* In addition to alpha-numerics */
get_uri_scheme(const EscapedPath * uri)528 gchar *get_uri_scheme(const EscapedPath *uri)
529 {
530 	const gchar *start=(const gchar *) uri;
531 	const gchar *p=start;
532 
533 	if(!g_ascii_isalpha(p[0]))
534 		return NULL;
535 
536 	while(g_ascii_isalnum(p[0]) ||
537 	      strchr(SCHEME_CHARS, p[0]))
538 		p++;
539 
540 	if(p[0]!=':')
541 		return NULL;
542 
543 	return g_strndup(start, p-start);
544 }
545 
546 /* Set the close-on-exec flag for this FD.
547  * TRUE means that an exec()'d process will not get the FD.
548  */
close_on_exec(int fd,gboolean close)549 void close_on_exec(int fd, gboolean close)
550 {
551 	if (fcntl(fd, F_SETFD, close))
552 		g_warning("fcntl() failed: %s\n", g_strerror(errno));
553 }
554 
set_blocking(int fd,gboolean blocking)555 void set_blocking(int fd, gboolean blocking)
556 {
557 	if (fcntl(fd, F_SETFL, blocking ? 0 : O_NONBLOCK))
558 		g_warning("fcntl() failed: %s\n", g_strerror(errno));
559 }
560 
561 /* Format this time nicely.
562  * g_free() the result.
563  */
pretty_time(const time_t * time)564 char *pretty_time(const time_t *time)
565 {
566         char time_buf[32];
567 	struct tm *tms;
568 
569 	if (time == NULL)
570 		return g_strdup("(null)");
571 
572 	tms = localtime(time);
573 	if (tms == NULL)
574 		return g_strdup("(invalid time)");
575 
576         if (strftime(time_buf, sizeof(time_buf), TIME_FORMAT, tms) == 0)
577 		time_buf[0]= 0;
578 
579 	return to_utf8(time_buf);
580 }
581 
582 #ifndef O_NOFOLLOW
583 #  define O_NOFOLLOW 0x0
584 #endif
585 
586 /* 'from' and 'to' are complete pathnames of files (not dirs or symlinks).
587  * This spawns 'cp' to do the copy if lstat() succeeds, otherwise we
588  * do the copy manually using vfs.
589  *
590  * Returns an error string, or NULL on success. g_free() the result.
591  *
592  * XXX: This was only used for libvfs...
593  */
copy_file(const guchar * from,const guchar * to)594 guchar *copy_file(const guchar *from, const guchar *to)
595 {
596 	const char *argv[] = {"cp", "-pRf", NULL, NULL, NULL};
597 
598 	argv[2] = from;
599 	argv[3] = to;
600 
601 	return fork_exec_wait(argv);
602 }
603 
604 /* 'word' has all special characters escaped so that it may be inserted
605  * into a shell command.
606  * Eg: 'My Dir?' becomes 'My\ Dir\?'. g_free() the result.
607  */
shell_escape(const guchar * word)608 guchar *shell_escape(const guchar *word)
609 {
610 	GString	*tmp;
611 	guchar	*retval;
612 
613 	tmp = g_string_new(NULL);
614 
615 	while (*word)
616 	{
617 		if (strchr(" ?*['\"$~\\|();!`&", *word))
618 			g_string_append_c(tmp, '\\');
619 		g_string_append_c(tmp, *word);
620 		word++;
621 	}
622 
623 	retval = tmp->str;
624 	g_string_free(tmp, FALSE);
625 	return retval;
626 }
627 
628 /* TRUE iff `sub' is (or would be) an object inside the directory `parent',
629  * (or the two are the same item/directory).
630  * FALSE if parent doesn't exist.
631  */
is_sub_dir(const char * sub_obj,const char * parent)632 gboolean is_sub_dir(const char *sub_obj, const char *parent)
633 {
634 	struct stat parent_info;
635 	char *sub;
636 
637 	if (mc_lstat(parent, &parent_info))
638 		return FALSE;		/* Parent doesn't exist */
639 
640 	/* For checking Copy/Move operations do a realpath first on sub
641 	 * (the destination), since copying into a symlink is the same as
642 	 * copying into the thing it points to. Don't realpath 'parent' though;
643 	 * copying a symlink just makes a new symlink.
644 	 *
645 	 * When checking if an icon depends on a file (parent), use realpath on
646 	 * sub (the icon) too.
647 	 */
648 	sub = pathdup(sub_obj);
649 
650 	while (1)
651 	{
652 		char	    *slash;
653 		struct stat info;
654 
655 		if (mc_lstat(sub, &info) == 0)
656 		{
657 			if (info.st_dev == parent_info.st_dev &&
658 				info.st_ino == parent_info.st_ino)
659 			{
660 				g_free(sub);
661 				return TRUE;
662 			}
663 		}
664 
665 		slash = strrchr(sub, '/');
666 		if (!slash)
667 			break;
668 		if (slash == sub)
669 		{
670 			if (sub[1])
671 				sub[1] = '\0';
672 			else
673 				break;
674 		}
675 		else
676 			*slash = '\0';
677 	}
678 
679 	g_free(sub);
680 
681 	return FALSE;
682 }
683 
684 /* True if the string 'list' contains 'item'.
685  * Eg ("close", "close, help") -> TRUE
686  */
in_list(const guchar * item,const guchar * list)687 gboolean in_list(const guchar *item, const guchar *list)
688 {
689 	int	len;
690 
691 	len = strlen(item);
692 
693 	while (*list)
694 	{
695 		if (strncmp(item, list, len) == 0 &&
696 		    !g_ascii_isalpha(list[len]))
697 			return TRUE;
698 		list = strchr(list, ',');
699 		if (!list)
700 			return FALSE;
701 		while (g_ascii_isspace(*++list))
702 			;
703 	}
704 
705 	return FALSE;
706 }
707 
708 /* Split a path into its components. Eg:
709  *
710  * /bob/fred -> ["bob", "fred"]
711  * ///a//b// -> ["a", "b"]
712  * /	     -> []
713  *
714  * The array and the strings in it must be freed after use.
715  */
split_path(const guchar * path)716 GPtrArray *split_path(const guchar *path)
717 {
718 	GPtrArray *array;
719 	guchar	*slash;
720 
721 	g_return_val_if_fail(path != NULL, NULL);
722 
723 	array = g_ptr_array_new();
724 
725 	while (1)
726 	{
727 		while (path[0] == '/')
728 			path++;
729 		if (path[0] == '\0')
730 			break;
731 
732 		slash = strchr(path, '/');
733 		if (slash)
734 		{
735 			g_ptr_array_add(array, g_strndup(path, slash - path));
736 			path = slash + 1;
737 			continue;
738 		}
739 		g_ptr_array_add(array, g_strdup(path));
740 		break;
741 	}
742 
743 	return array;
744 }
745 
746 /* Return the shortest path from 'from' to 'to'.
747  * Eg: get_relative_path("/a/b/c", "a/d/e") -> "../d/e"
748  */
get_relative_path(const guchar * from,const guchar * to)749 guchar *get_relative_path(const guchar *from, const guchar *to)
750 {
751 	GString  *path;
752 	guchar	 *retval;
753 	GPtrArray *src, *dst;
754 	int	i, j;
755 
756 	src = split_path(from);
757 	dst = split_path(to);
758 
759 	/* The last component of src doesn't matter... */
760 	if (src->len)
761 	{
762 		g_free(src->pdata[src->len - 1]);
763 		g_ptr_array_remove_index(src, src->len - 1);
764 	}
765 
766 	/* Strip off common path elements... */
767 	i = 0;
768 	while (i < src->len && i < dst->len)
769 	{
770 		guchar	*a = (guchar *) src->pdata[i];
771 		guchar	*b = (guchar *) dst->pdata[i];
772 
773 		if (strcmp(a, b) != 0)
774 			break;
775 		i++;
776 	}
777 
778 	/* Go up one dir for each element remaining in src */
779 	path = g_string_new(NULL);
780 	for (j = i; j < src->len; j++)
781 		g_string_append(path, "../");
782 
783 	/* Go down one dir for each element remaining in dst */
784 	for (j = i; j < dst->len; j++)
785 	{
786 		g_string_append(path, (guchar *) dst->pdata[j]);
787 		g_string_append_c(path, '/');
788 	}
789 
790 	if (path->str[path->len - 1] == '/')
791 		g_string_truncate(path, path->len - 1);
792 	if (path->len == 0)
793 		g_string_assign(path, ".");
794 
795 	/* Free the arrays */
796 	for (i = 0; i < src->len; i++)
797 		g_free(src->pdata[i]);
798 	g_ptr_array_free(src, TRUE);
799 	for (i = 0; i < dst->len; i++)
800 		g_free(dst->pdata[i]);
801 	g_ptr_array_free(dst, TRUE);
802 
803 	retval = path->str;
804 	g_string_free(path, FALSE);
805 
806 	return retval;
807 }
808 
809 /*
810  * Interperet text as a boolean value.  Return defvalue if we don't
811  * recognise it
812  */
text_to_boolean(const char * text,int defvalue)813 int text_to_boolean(const char *text, int defvalue)
814 {
815 	if (g_strcasecmp(text, "true")==0)
816 	        return TRUE;
817 	else if (g_strcasecmp(text, "false")==0)
818 	        return FALSE;
819 	else if (g_strcasecmp(text, "yes")==0)
820 	        return TRUE;
821 	else if (g_strcasecmp(text, "no")==0)
822 	        return FALSE;
823 	else if (g_ascii_isdigit(text[0]))
824 	        return !!atoi(text);
825 
826 	return defvalue;
827 }
828 
829 /* Return the pathname that this symlink points to.
830  * NULL on error (not a symlink, path too long) and errno set.
831  * g_free() the result.
832  */
readlink_dup(const char * source)833 char *readlink_dup(const char *source)
834 {
835 	char	path[MAXPATHLEN + 1];
836 	int	got;
837 
838 	got = readlink(source, path, MAXPATHLEN);
839 	if (got < 0 || got > MAXPATHLEN)
840 		return NULL;
841 
842 	return g_strndup(path, got);
843 }
844 
845 /*
846  * This code implements the MD5 message-digest algorithm.
847  * The algorithm is due to Ron Rivest. The original code was
848  * written by Colin Plumb in 1993, and put in the public domain.
849  *
850  * Modified to use glib datatypes. Put under GPL to simplify
851  * licensing for ROX-Filer. Taken from Debian's dpkg package.
852  */
853 
854 #define md5byte unsigned char
855 
856 typedef struct _MD5Context MD5Context;
857 
858 struct _MD5Context {
859 	guint32 buf[4];
860 	guint32 bytes[2];
861 	guint32 in[16];
862 };
863 
864 #if G_BYTE_ORDER == G_BIG_ENDIAN
byteSwap(guint32 * buf,unsigned words)865 static void byteSwap(guint32 *buf, unsigned words)
866 {
867 	md5byte *p = (md5byte *)buf;
868 
869 	do {
870 		*buf++ = (guint32)((unsigned)p[3] << 8 | p[2]) << 16 |
871 			((unsigned)p[1] << 8 | p[0]);
872 		p += 4;
873 	} while (--words);
874 }
875 #else
876 #define byteSwap(buf,words)
877 #endif
878 
879 /*
880  * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
881  * initialization constants.
882  */
MD5Init(MD5Context * ctx)883 static void MD5Init(MD5Context *ctx)
884 {
885 	ctx->buf[0] = 0x67452301;
886 	ctx->buf[1] = 0xefcdab89;
887 	ctx->buf[2] = 0x98badcfe;
888 	ctx->buf[3] = 0x10325476;
889 
890 	ctx->bytes[0] = 0;
891 	ctx->bytes[1] = 0;
892 }
893 
894 /*
895  * Update context to reflect the concatenation of another buffer full
896  * of bytes.
897  */
MD5Update(MD5Context * ctx,md5byte const * buf,unsigned len)898 static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len)
899 {
900 	guint32 t;
901 
902 	/* Update byte count */
903 
904 	t = ctx->bytes[0];
905 	if ((ctx->bytes[0] = t + len) < t)
906 		ctx->bytes[1]++;	/* Carry from low to high */
907 
908 	t = 64 - (t & 0x3f);	/* Space available in ctx->in (at least 1) */
909 	if (t > len) {
910 		memcpy((md5byte *)ctx->in + 64 - t, buf, len);
911 		return;
912 	}
913 	/* First chunk is an odd size */
914 	memcpy((md5byte *)ctx->in + 64 - t, buf, t);
915 	byteSwap(ctx->in, 16);
916 	MD5Transform(ctx->buf, ctx->in);
917 	buf += t;
918 	len -= t;
919 
920 	/* Process data in 64-byte chunks */
921 	while (len >= 64) {
922 		memcpy(ctx->in, buf, 64);
923 		byteSwap(ctx->in, 16);
924 		MD5Transform(ctx->buf, ctx->in);
925 		buf += 64;
926 		len -= 64;
927 	}
928 
929 	/* Handle any remaining bytes of data. */
930 	memcpy(ctx->in, buf, len);
931 }
932 
933 /*
934  * Final wrapup - pad to 64-byte boundary with the bit pattern
935  * 1 0* (64-bit count of bits processed, MSB-first)
936  * Returns the newly allocated string of the hash.
937  */
MD5Final(MD5Context * ctx)938 static char *MD5Final(MD5Context *ctx)
939 {
940 	char *retval;
941 	int i;
942 	int count = ctx->bytes[0] & 0x3f;	/* Number of bytes in ctx->in */
943 	md5byte *p = (md5byte *)ctx->in + count;
944 	guint8	*bytes;
945 
946 	/* Set the first char of padding to 0x80.  There is always room. */
947 	*p++ = 0x80;
948 
949 	/* Bytes of padding needed to make 56 bytes (-8..55) */
950 	count = 56 - 1 - count;
951 
952 	if (count < 0) {	/* Padding forces an extra block */
953 		memset(p, 0, count + 8);
954 		byteSwap(ctx->in, 16);
955 		MD5Transform(ctx->buf, ctx->in);
956 		p = (md5byte *)ctx->in;
957 		count = 56;
958 	}
959 	memset(p, 0, count);
960 	byteSwap(ctx->in, 14);
961 
962 	/* Append length in bits and transform */
963 	ctx->in[14] = ctx->bytes[0] << 3;
964 	ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
965 	MD5Transform(ctx->buf, ctx->in);
966 
967 	byteSwap(ctx->buf, 4);
968 
969 	retval = g_malloc(33);
970 	bytes = (guint8 *) ctx->buf;
971 	for (i = 0; i < 16; i++)
972 		sprintf(retval + (i * 2), "%02x", bytes[i]);
973 	retval[32] = '\0';
974 
975 	return retval;
976 }
977 
978 # ifndef ASM_MD5
979 
980 /* The four core functions - F1 is optimized somewhat */
981 
982 /* #define F1(x, y, z) (x & y | ~x & z) */
983 #define F1(x, y, z) (z ^ (x & (y ^ z)))
984 #define F2(x, y, z) F1(z, x, y)
985 #define F3(x, y, z) (x ^ y ^ z)
986 #define F4(x, y, z) (y ^ (x | ~z))
987 
988 /* This is the central step in the MD5 algorithm. */
989 #define MD5STEP(f,w,x,y,z,in,s) \
990 	 (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
991 
992 /*
993  * The core of the MD5 algorithm, this alters an existing MD5 hash to
994  * reflect the addition of 16 longwords of new data.  MD5Update blocks
995  * the data and converts bytes into longwords for this routine.
996  */
MD5Transform(guint32 buf[4],guint32 const in[16])997 static void MD5Transform(guint32 buf[4], guint32 const in[16])
998 {
999 	register guint32 a, b, c, d;
1000 
1001 	a = buf[0];
1002 	b = buf[1];
1003 	c = buf[2];
1004 	d = buf[3];
1005 
1006 	MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
1007 	MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
1008 	MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
1009 	MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
1010 	MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
1011 	MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
1012 	MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
1013 	MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
1014 	MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
1015 	MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
1016 	MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
1017 	MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
1018 	MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
1019 	MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
1020 	MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
1021 	MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
1022 
1023 	MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
1024 	MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
1025 	MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
1026 	MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
1027 	MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
1028 	MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
1029 	MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
1030 	MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
1031 	MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
1032 	MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
1033 	MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
1034 	MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
1035 	MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
1036 	MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
1037 	MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
1038 	MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
1039 
1040 	MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
1041 	MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
1042 	MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
1043 	MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
1044 	MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
1045 	MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
1046 	MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
1047 	MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
1048 	MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
1049 	MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
1050 	MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
1051 	MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
1052 	MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
1053 	MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
1054 	MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
1055 	MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
1056 
1057 	MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
1058 	MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
1059 	MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
1060 	MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
1061 	MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
1062 	MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
1063 	MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
1064 	MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
1065 	MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
1066 	MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
1067 	MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
1068 	MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
1069 	MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
1070 	MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
1071 	MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
1072 	MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
1073 
1074 	buf[0] += a;
1075 	buf[1] += b;
1076 	buf[2] += c;
1077 	buf[3] += d;
1078 }
1079 
1080 # endif /* ASM_MD5 */
1081 
md5_hash(const char * message)1082 char *md5_hash(const char *message)
1083 {
1084 	MD5Context ctx;
1085 
1086 	MD5Init(&ctx);
1087 	MD5Update(&ctx, message, strlen(message));
1088 	return MD5Final(&ctx);
1089 }
1090 
1091 /* Convert string 'src' from the current locale to UTF-8 */
to_utf8(const gchar * src)1092 gchar *to_utf8(const gchar *src)
1093 {
1094 	gchar *retval;
1095 
1096 	if (!src)
1097 		return NULL;
1098 
1099 	retval = g_locale_to_utf8(src, -1, NULL, NULL, NULL);
1100 	if (!retval)
1101 		retval = g_convert_with_fallback(src, -1, "utf-8", "iso-8859-1",
1102 						"?", NULL, NULL, NULL);
1103 
1104 	return retval ? retval : g_strdup(src);
1105 }
1106 
1107 /* Ensure string at 'sp' is UTF-8. g_free() and replace by
1108  * UTF-8 version if not.
1109  */
ensure_utf8(gchar ** sp)1110 void ensure_utf8(gchar **sp)
1111 {
1112 	gchar *s = *sp;
1113 
1114 	if (!g_utf8_validate(s, -1, NULL))
1115 	{
1116 		*sp = to_utf8(s);
1117 		g_free(s);
1118 	}
1119 }
1120 
1121 /* Removes trailing / chars and converts a leading '~/' (if any) to
1122  * the user's home dir. g_free() the result.
1123  */
expand_path(const gchar * path)1124 gchar *expand_path(const gchar *path)
1125 {
1126 	guchar		*retval;
1127 	int		path_len;
1128 
1129 	g_return_val_if_fail(path != NULL, NULL);
1130 
1131 	path_len = strlen(path);
1132 	while (path_len > 1 && path[path_len - 1] == '/')
1133 		path_len--;
1134 
1135 	retval = g_strndup(path, path_len);
1136 
1137 	if (path[0] == '~' && (path[1] == '\0' || path[1] == '/'))
1138 	{
1139 		guchar *tmp = retval;
1140 
1141 		retval = g_strconcat(home_dir, retval + 1, NULL);
1142 		g_free(tmp);
1143 	}
1144 
1145 	return retval;
1146 }
1147 
1148 /* g_free() every element in the list, then free the list itself and
1149  * NULL the pointer to the list.
1150  */
destroy_glist(GList ** list)1151 void destroy_glist(GList **list)
1152 {
1153 	GList *l = *list;
1154 	g_list_foreach(l, (GFunc) g_free, NULL);
1155 	g_list_free(l);
1156 	*list = NULL;
1157 }
1158 
null_g_free(gpointer p)1159 void null_g_free(gpointer p)
1160 {
1161 	g_free(*(gpointer *)p);
1162 	*(gpointer *)p = NULL;
1163 }
1164 
1165 typedef struct _CollatePart CollatePart;
1166 
1167 struct _CollateKey {
1168 	CollatePart *parts;
1169 	gboolean caps;
1170 };
1171 
1172 struct _CollatePart {
1173 	guchar *text;	/* NULL => end of list */
1174 	long number;
1175 };
1176 
1177 /* Break 'name' (a UTF-8 string) down into a list of (text, number) pairs.
1178  * The text parts processed for collating. This allows any two names to be
1179  * quickly compared later for intelligent sorting (comparing names is
1180  * speed-critical).
1181  */
collate_key_new(const guchar * name)1182 CollateKey *collate_key_new(const guchar *name)
1183 {
1184 	const guchar *i;
1185 	guchar *to_free = NULL;
1186 	GArray *array;
1187 	CollatePart new;
1188 	CollateKey *retval;
1189 	char *tmp;
1190 
1191 	g_return_val_if_fail(name != NULL, NULL);
1192 
1193 	array = g_array_new(FALSE, FALSE, sizeof(CollatePart));
1194 
1195 	/* Ensure valid UTF-8 */
1196 	if (!g_utf8_validate(name, -1, NULL))
1197 	{
1198 		to_free = to_utf8(name);
1199 		name = to_free;
1200 	}
1201 
1202 	retval = g_new(CollateKey, 1);
1203 	retval->caps = g_unichar_isupper(g_utf8_get_char(name));
1204 
1205 	for (i = name; *i; i = g_utf8_next_char(i))
1206 	{
1207 		gunichar first_char;
1208 
1209 		/* We're in a (possibly blank) text section starting at 'name'.
1210 		 * Find the end of it (the next digit, or end of string).
1211 		 * Note: g_ascii_isdigit takes char, not unichar, while
1212 		 * g_unicode_isdigit returns true for non ASCII digits.
1213 		 */
1214 		first_char = g_utf8_get_char(i);
1215 		if (first_char >= '0' && first_char <= '9')
1216 		{
1217 			char *endp;
1218 
1219 			/* i -> first digit character */
1220 			tmp = g_utf8_strdown(name, i - name);
1221 			new.text = g_utf8_collate_key(tmp, -1);
1222 			g_free(tmp);
1223 			new.number = strtol(i, &endp, 10);
1224 
1225 			g_array_append_val(array, new);
1226 
1227 			g_return_val_if_fail(endp > (char *) i, NULL);
1228 
1229 			name = endp;
1230 			i = name - 1;
1231 		}
1232 	}
1233 
1234 	tmp = g_utf8_strdown(name, i - name);
1235 	new.text = g_utf8_collate_key(tmp, -1);
1236 	g_free(tmp);
1237 	new.number = -1;
1238 	g_array_append_val(array, new);
1239 
1240 	new.text = NULL;
1241 	g_array_append_val(array, new);
1242 
1243 	retval->parts = (CollatePart *) array->data;
1244 	g_array_free(array, FALSE);
1245 
1246 	if (to_free)
1247 		g_free(to_free);	/* Only taken for invalid UTF-8 */
1248 
1249 	return retval;
1250 }
1251 
collate_key_free(CollateKey * key)1252 void collate_key_free(CollateKey *key)
1253 {
1254 	CollatePart *part;
1255 
1256 	for (part = key->parts; part->text; part++)
1257 		g_free(part->text);
1258 	g_free(key->parts);
1259 	g_free(key);
1260 }
1261 
collate_key_cmp(const CollateKey * key1,const CollateKey * key2,gboolean caps_first)1262 int collate_key_cmp(const CollateKey *key1, const CollateKey *key2,
1263 		    gboolean caps_first)
1264 {
1265 	CollatePart *n1 = key1->parts;
1266 	CollatePart *n2 = key2->parts;
1267 	int r;
1268 
1269 	if (caps_first)
1270 	{
1271 		if (key1->caps && !key2->caps)
1272 			return -1;
1273 		else if (key2->caps && !key1->caps)
1274 			return 1;
1275 	}
1276 
1277 	while (1)
1278 	{
1279 		if (!n1->text)
1280 			return n2->text ? -1 : 0;
1281 		if (!n2->text)
1282 			return 1;
1283 		r = strcmp(n1->text, n2->text);
1284 		if (r)
1285 			return r;
1286 
1287 		if (n1->number < n2->number)
1288 			return -1;
1289 		if (n1->number > n2->number)
1290 			return 1;
1291 
1292 		n1++;
1293 		n2++;
1294 	}
1295 }
1296 
1297 /* Returns TRUE if the object exists, FALSE if it doesn't.
1298  * For symlinks, the file pointed to must exist.
1299  */
file_exists(const char * path)1300 gboolean file_exists(const char *path)
1301 {
1302 	struct stat info;
1303 
1304 	return !mc_stat(path, &info);
1305 }
1306 
1307 /* Escape path for future use in URI */
escape_uri_path(const char * path)1308 EscapedPath *escape_uri_path(const char *path)
1309 {
1310 	const char *safe = ":-_./"; /* Plus isalnum() */
1311 	const guchar *s;
1312 	gchar *ans;
1313 	GString *str;
1314 
1315 	str = g_string_sized_new(strlen(path));
1316 
1317 	for (s = path; *s; s++)
1318 	{
1319 		if (!g_ascii_isalnum(*s) && !strchr(safe, *s))
1320 			g_string_append_printf(str, "%%%02x", *s);
1321 		else
1322 			str = g_string_append_c(str, *s);
1323 	}
1324 
1325 	ans = str->str;
1326 	g_string_free(str, FALSE);
1327 
1328 	return (EscapedPath *) ans;
1329 }
1330 
encode_path_as_uri(const guchar * path)1331 EscapedPath *encode_path_as_uri(const guchar *path)
1332 {
1333 	gchar *tpath = (gchar *) escape_uri_path(path);
1334 	gchar *uri;
1335 
1336 	uri = g_strconcat("file://", our_host_name_for_dnd(), tpath, NULL);
1337 	g_free(tpath);
1338 
1339 	return (EscapedPath *) uri;
1340 }
1341 
unescape_uri(const EscapedPath * uri)1342 gchar *unescape_uri(const EscapedPath *uri)
1343 {
1344 	const char *uri_string = (char *) uri;
1345 	const gchar *s;
1346 	gchar *d;
1347 	gchar *tmp;
1348 
1349 	tmp = g_malloc(strlen(uri_string) + 1);
1350 	for (s = uri_string, d = tmp; *s; s++, d++)
1351 	{
1352 		/*printf("%s\n", s);*/
1353 		if (*s == '%' && g_ascii_isxdigit(s[1]) &&
1354 				 g_ascii_isxdigit(s[2]))
1355 		{
1356 			int c;
1357 			char buf[3];
1358 			buf[0] = s[1];
1359 			buf[1] = s[2];
1360 			buf[2] = 0;
1361 			c = (int) strtol(buf, NULL, 16);
1362 			*d = c;
1363 			s += 2;
1364 		}
1365 		else
1366 			*d = *s;
1367 	}
1368 	*d = '\0';
1369 
1370 	return tmp;
1371 }
1372 
1373 /* Used as the sort function for sorting GPtrArrays */
strcmp2(gconstpointer a,gconstpointer b)1374 gint strcmp2(gconstpointer a, gconstpointer b)
1375 {
1376 	const char *aa = *(char **) a;
1377 	const char *bb = *(char **) b;
1378 
1379 	return g_strcasecmp(aa, bb);
1380 }
1381 
1382 /* Returns an array listing all the names in the directory 'path'.
1383  * The array is sorted.
1384  * '.' and '..' are skipped.
1385  * On error, the error is reported with g_warning and NULL is returned.
1386  */
list_dir(const guchar * path)1387 GPtrArray *list_dir(const guchar *path)
1388 {
1389 	GDir *dir;
1390 	GError *error = NULL;
1391 	const char *leaf;
1392 	GPtrArray *names;
1393 
1394 	dir = g_dir_open(path, 0, &error);
1395 	if (error)
1396 	{
1397 		g_warning("Can't list directory:\n%s", error->message);
1398 		g_error_free(error);
1399 		return NULL;
1400 	}
1401 
1402 	names = g_ptr_array_new();
1403 
1404 	while ((leaf = g_dir_read_name(dir))) {
1405 		if (leaf[0] != '.')
1406 			g_ptr_array_add(names, g_strdup(leaf));
1407 	}
1408 
1409 	g_dir_close(dir);
1410 
1411 	g_ptr_array_sort(names, strcmp2);
1412 
1413 	return names;
1414 }
1415 
stat_with_timeout(const char * path,struct stat * info)1416 int stat_with_timeout(const char *path, struct stat *info)
1417 {
1418 	int status;
1419 	pid_t child;
1420 	gboolean retval;
1421 
1422 	child = fork();
1423 	if (child < 0)
1424 	{
1425 		g_warning("stat_with_timeout: fork(): %s", g_strerror(errno));
1426 		return -1;
1427 	}
1428 
1429 	if (child == 0)
1430 	{
1431 		/* Child */
1432 		alarm(3);
1433 		_exit(mc_stat(path, info) ? 1 : 0);
1434 	}
1435 
1436 	waitpid(child, &status, 0);
1437 
1438 	if (status == 0)
1439 		retval = mc_stat(path, info);
1440 	else
1441 		retval = -1;
1442 
1443 	return retval;
1444 }
1445 
1446 /* From glib. */
my_strchrnul(const gchar * str,gchar c)1447 static gchar *my_strchrnul(const gchar *str, gchar c)
1448 {
1449 	gchar *p = (gchar*) str;
1450 	while (*p && (*p != c))
1451 		++p;
1452 
1453 	return p;
1454 }
1455 
1456 /* Based on code from glib. */
available_in_path(const char * file)1457 gboolean available_in_path(const char *file)
1458 {
1459 	const gchar *path, *p;
1460 	gchar *name, *freeme;
1461 	size_t len;
1462 	size_t pathlen;
1463 	gboolean found = FALSE;
1464 
1465 	path = g_getenv("PATH");
1466 	if (path == NULL)
1467 		path = "/bin:/usr/bin:.";
1468 
1469 	len = strlen(file) + 1;
1470 	pathlen = strlen(path);
1471 	freeme = name = g_malloc(pathlen + len + 1);
1472 
1473 	/* Copy the file name at the top, including '\0'  */
1474 	memcpy(name + pathlen + 1, file, len);
1475 	name = name + pathlen;
1476 	/* And add the slash before the filename  */
1477 	*name = '/';
1478 
1479 	p = path;
1480 	do
1481 	{
1482 		char *startp;
1483 
1484 		path = p;
1485 		p = my_strchrnul(path, ':');
1486 
1487 		if (p == path)
1488 			/* Two adjacent colons, or a colon at the beginning or
1489 			 * the end of `PATH' means to search the current
1490 			 * directory.
1491 			 */
1492 			startp = name + 1;
1493 		else
1494 			startp = memcpy (name - (p - path), path, p - path);
1495 
1496 		/* Try to execute this name.  If it works, execv will not
1497 		 * return.
1498 		 */
1499 		if (access(startp, X_OK) == 0)
1500 			found = TRUE;
1501 	} while (!found && *p++ != '\0');
1502 
1503 	g_free(freeme);
1504 
1505 	return found;
1506 }
1507 
get_value_from_desktop_data(const char * data,size_t length,const char * section,const char * key,GError ** error)1508 static char *get_value_from_desktop_data(const char *data, size_t length,
1509 					 const char *section, const char *key,
1510 					 GError **error)
1511 {
1512 	int section_length, key_length;
1513 	const char *last_possible_key_start;
1514 	const char *last_possible_section_start;
1515 	const char *next;
1516 
1517 	section_length = strlen(section);
1518 	last_possible_section_start = data + length - section_length - 3;
1519 
1520 	for (next = data; next <= last_possible_section_start; next++)
1521 	{
1522 		if (next[0] != '[' || (next > data && next[-1] != '\n'))
1523 			continue;
1524 		next += 1;
1525 		if (next[section_length] != ']' ||
1526 		    next[section_length + 1] != '\n')
1527 			continue;
1528 		if (strncmp(next, section, section_length) != 0)
1529 			continue;
1530 		next += section_length + 1;	/* Newline */
1531 		goto find_key;
1532 	}
1533 	return NULL;
1534 
1535 find_key:
1536 	key_length = strlen(key);
1537 	last_possible_key_start = data + length - key_length - 3;
1538 	for (;
1539 	     next && next < last_possible_key_start;
1540 	     next = memchr(next + 1, '\n', last_possible_key_start - next))
1541 	{
1542 		const char *nl;
1543 
1544 		if (next[1] == '\n')
1545 			continue; /* Cope with blank lines */
1546 
1547 		next++;		/* Skip newline */
1548 
1549 		if (next[0] == '[')
1550 			break;	/* End of section */
1551 
1552 		if (strncmp(next, key, key_length) != 0)
1553 			continue;
1554 		next += key_length;
1555 		while (next[0] == ' ' || next[0] == '\t')
1556 			next++;
1557 
1558 		/* Note: if we had GLib 2.6, we could use
1559 		 * g_get_language_names() to get translations here.
1560 		 */
1561 		if (next[0] != '=')
1562 			continue;
1563 		next++;
1564 		while (next[0] == ' ' || next[0] == '\t')
1565 			next++;
1566 		nl = memchr(next, '\n', data + length - next);
1567 		if (!nl)
1568 			break;
1569 		return g_strndup(next, nl - next);
1570 	}
1571 
1572 	return NULL;
1573 }
1574 
1575 /* Load .desktop file 'path' and return the value of the named key.
1576  * Sets error if the desktop file cannot be parsed.
1577  * Returns NULL (but does not set error) if the key is not present.
1578  * Returned string must be g_free()d.
1579  */
get_value_from_desktop_file(const char * path,const char * section,const char * key,GError ** error)1580 char *get_value_from_desktop_file(const char *path,
1581 				  const char *section,
1582 				  const char *key,
1583 				  GError **error)
1584 {
1585 	char *value = NULL;
1586 	struct stat info;
1587 	int fd = -1;
1588 	void *start = NULL;
1589 
1590 	fd = open(path, O_RDONLY);
1591 	if (fd == -1 || fstat(fd, &info) != 0)
1592 	{
1593                 g_set_error(error,
1594                              G_FILE_ERROR,
1595                              g_file_error_from_errno(errno),
1596                              _("Failed to open and stat file '%s': %s"),
1597                              path,
1598                              g_strerror(errno));
1599 		goto err;
1600 	}
1601 	start = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd, 0);
1602 	if (start == MAP_FAILED)
1603 	{
1604                 g_set_error(error,
1605                              G_FILE_ERROR,
1606                              g_file_error_from_errno(errno),
1607                              _("Failed to mmap file '%s': %s"),
1608                              path,
1609                              g_strerror(errno));
1610 		goto err;
1611 	}
1612 
1613 	value = get_value_from_desktop_data((const char *) start,
1614 					    info.st_size,
1615 					    section, key,
1616 					    error);
1617 err:
1618 	if (fd != -1)
1619 		close(fd);
1620 
1621 	if (start != NULL && start != MAP_FAILED)
1622 		munmap(start, info.st_size);
1623 
1624 	return value;
1625 }
1626 
1627 /* Load .desktop file 'path' and set the value of the named keys in the
1628  * NULL terminated list.
1629  * Sets error if the desktop file cannot be parsed and returns false.
1630  * Sets NULL (but does not set error) if the key is not present.
1631  * String set must be g_free()d.
1632  */
get_values_from_desktop_file(const char * path,GError ** error,const char * section,const char * key,gchar ** value,...)1633 gboolean get_values_from_desktop_file(const char *path,
1634 				      GError **error,
1635 				      const char *section,
1636 				      const char *key,
1637 				      gchar **value, ...)
1638 {
1639 	struct stat info;
1640 	int fd = -1;
1641 	void *start = NULL;
1642 	va_list list;
1643 
1644 	fd = open(path, O_RDONLY);
1645 	if (fd == -1 || fstat(fd, &info) != 0)
1646 	{
1647                 g_set_error(error,
1648                              G_FILE_ERROR,
1649                              g_file_error_from_errno(errno),
1650                              _("Failed to open and stat file '%s': %s"),
1651                              path,
1652                              g_strerror(errno));
1653 		goto err;
1654 	}
1655 	start = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd, 0);
1656 	if (start == MAP_FAILED)
1657 	{
1658                 g_set_error(error,
1659                              G_FILE_ERROR,
1660                              g_file_error_from_errno(errno),
1661                              _("Failed to mmap file '%s': %s"),
1662                              path,
1663                              g_strerror(errno));
1664 		goto err;
1665 	}
1666 
1667 	va_start(list, value);
1668 	while(section && key && value) {
1669 		*value = get_value_from_desktop_data((const char *) start,
1670 						     info.st_size,
1671 						     section, key,
1672 						     error);
1673 		if(*error)
1674 			break;
1675 
1676 		section=va_arg(list, const char *);
1677 		key=va_arg(list, const char *);
1678 		value=va_arg(list, char **);
1679 	}
1680 
1681 err:
1682 	if (fd != -1)
1683 		close(fd);
1684 
1685 	if (start != NULL && start != MAP_FAILED)
1686 		munmap(start, info.st_size);
1687 
1688 	return (*error==NULL);
1689 }
1690 
1691 /**
1692  * Build a command to execute with a supplied path.  If the command pattern
1693  * given contains @c $1 it is substituted with the path, otherwise the path
1694  * is appended to the pattern after a space character
1695  * @param[in] cmd command pattern, with optional @c $1
1696  * @param[in] path path to substitute or append
1697  * @return generated command line, pass to g_free() when done.
1698  */
build_command_with_path(const char * cmd,const char * path)1699 gchar *build_command_with_path(const char *cmd, const char *path)
1700 {
1701 	const char *subs;
1702 	gchar *result;
1703 
1704 	for(subs=cmd; *subs; subs++)
1705 	{
1706 		if(subs[0]=='\\' && subs[1]=='$')
1707 		{
1708 			subs++;
1709 			continue;
1710 		}
1711 		if(strncmp(subs, "$1", 2)==0)
1712 			break;
1713 	}
1714 	if(*subs) {
1715 		int size=strlen(cmd)+strlen(path)+1;
1716 		result = g_new(char, size);
1717 		strncpy(result, cmd, subs-cmd);
1718 		result[subs-cmd] = 0;
1719 		strcat(result, path);
1720 		strcat(result, subs+2);
1721 	} else {
1722 		result = g_strconcat(cmd, " ", path, NULL);
1723 	}
1724 
1725 	return result;
1726 }
1727 
1728 /* Search for a application on $APPDIRPATH
1729  * (~/Apps:/usr/local/apps:/usr/apps) and return a copy of the path found.
1730  * Returns NULL if not found
1731  */
find_app(const char * appname)1732 gchar *find_app(const char *appname)
1733 {
1734   const gchar *path=g_getenv("APPDIRPATH");
1735   gchar **search;
1736   gchar *app=NULL;
1737   int i;
1738 
1739   if(path)
1740   {
1741 	  search=g_strsplit(path, ":", 0);
1742   }
1743   else
1744   {
1745 	  gchar *tmp;
1746 
1747 	  tmp=g_strdup_printf("%s/Apps:/usr/local/apps:/usr/apps",
1748 			       g_get_home_dir());
1749 	  search=g_strsplit(tmp, ":", 0);
1750 	  g_free(tmp);
1751   }
1752 
1753   for(i=0; search[i]; i++)
1754   {
1755 	  app=g_strconcat(search[i], "/", appname, NULL);
1756 	  if(access(app, X_OK)==0)
1757 		  goto out;
1758 	  g_free(app);
1759   }
1760   app=NULL;
1761 
1762  out:
1763   g_strfreev(search);
1764   return app;
1765 }
1766