r4275: If we can't find libc.so.6 for xattr support, try libc.so in case we only...
[rox-filer.git] / ROX-Filer / src / support.c
blobd10b0bceb1fd7c32b68aba6035f71bafee34fe60
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* support.c - (non-GUI) useful routines */
24 #include "config.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <netdb.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <sys/param.h>
32 #include <pwd.h>
33 #include <grp.h>
34 #include <fcntl.h>
35 #include <sys/wait.h>
36 #include <string.h>
37 #include <time.h>
38 #include <unistd.h>
39 #include <libxml/parser.h>
40 #include <math.h>
41 #include <sys/mman.h>
43 #include "global.h"
45 #include "choices.h"
46 #include "main.h"
47 #include "options.h"
48 #include "support.h"
49 #include "fscache.h"
50 #include "main.h"
51 #include "xml.h"
53 static GHashTable *uid_hash = NULL; /* UID -> User name */
54 static GHashTable *gid_hash = NULL; /* GID -> Group name */
56 /* Static prototypes */
57 static void MD5Transform(guint32 buf[4], guint32 const in[16]);
59 /****************************************************************
60 * EXTERNAL INTERFACE *
61 ****************************************************************/
63 /* g_object_unref() the result! */
64 XMLwrapper *xml_cache_load(const gchar *pathname)
66 static GFSCache *xml_cache = NULL;
68 if (!xml_cache)
69 xml_cache = g_fscache_new((GFSLoadFunc) xml_new, NULL, NULL);
70 return g_fscache_lookup(xml_cache, pathname);
73 /* Save doc as XML as filename, 0 on success or 1 on failure */
74 int save_xml_file(xmlDocPtr doc, const gchar *filename)
76 #if LIBXML_VERSION > 20400
77 if (xmlSaveFormatFileEnc(filename, doc, NULL, 1) < 0)
78 return 1;
79 #else
80 FILE *out;
82 out = fopen(filename, "w");
83 if (!out)
84 return 1;
86 xmlDocDump(out, doc); /* Some versions return void */
88 if (fclose(out))
89 return 1;
90 #endif
92 return 0;
95 /* Create a new SOAP message and return the document and the (empty)
96 * body node.
98 xmlDocPtr soap_new(xmlNodePtr *ret_body)
100 xmlDocPtr doc;
101 xmlNodePtr root;
102 xmlNs *env_ns;
104 doc = xmlNewDoc("1.0");
105 root = xmlNewDocNode(doc, NULL, "Envelope", NULL);
106 xmlDocSetRootElement(doc, root);
108 env_ns = xmlNewNs(root, SOAP_ENV_NS, "env");
109 xmlSetNs(root, env_ns);
111 *ret_body = xmlNewTextChild(root, env_ns, "Body", NULL);
112 xmlNewNs(*ret_body, ROX_NS, "rox");
114 return doc;
117 /* Like g_strdup, but does realpath() too (if possible) */
118 char *pathdup(const char *path)
120 char real[MAXPATHLEN];
122 g_return_val_if_fail(path != NULL, NULL);
124 if (realpath(path, real))
125 return g_strdup(real);
127 return g_strdup(path);
130 /* Join the path to the leaf (adding a / between them) and
131 * return a pointer to a static buffer with the result. Buffer is valid
132 * until the next call to make_path.
133 * The return value may be used as 'dir' for the next call.
135 const guchar *make_path(const char *dir, const char *leaf)
137 static GString *buffer = NULL;
139 if (!buffer)
140 buffer = g_string_new(NULL);
142 g_return_val_if_fail(dir != NULL, buffer->str);
143 g_return_val_if_fail(leaf != NULL, buffer->str);
145 if (buffer->str != dir)
146 g_string_assign(buffer, dir);
148 if (dir[0] != '/' || dir[1] != '\0')
149 g_string_append_c(buffer, '/'); /* For anything except "/" */
151 g_string_append(buffer, leaf);
153 return buffer->str;
156 /* Return our complete host name for DND */
157 const char *our_host_name_for_dnd(void)
159 if (o_dnd_no_hostnames.int_value)
160 return "";
161 return our_host_name();
164 /* Return our complete host name, unconditionally */
165 const char *our_host_name(void)
167 static char *name = NULL;
169 if (!name)
171 char buffer[4096];
173 if (gethostname(buffer, 4096) == 0)
175 /* gethostname doesn't always return the full name... */
176 struct hostent *ent;
178 buffer[4095] = '\0';
179 ent = gethostbyname(buffer);
180 name = g_strdup(ent ? ent->h_name : buffer);
182 else
184 g_warning("gethostname() failed - using localhost\n");
185 name = g_strdup("localhost");
189 return name;
192 void debug_free_string(void *data)
194 g_print("Freeing string '%s'\n", (char *) data);
195 g_free(data);
198 const char *user_name(uid_t uid)
200 const char *retval;
202 if (!uid_hash)
203 uid_hash = g_hash_table_new(NULL, NULL);
205 retval = g_hash_table_lookup(uid_hash, GINT_TO_POINTER(uid));
207 if (!retval)
209 struct passwd *passwd;
211 passwd = getpwuid(uid);
212 retval = passwd ? g_strdup(passwd->pw_name)
213 : g_strdup_printf("[%d]", (int) uid);
214 g_hash_table_insert(uid_hash, GINT_TO_POINTER(uid),
215 (gchar *) retval);
218 return retval;
221 const char *group_name(gid_t gid)
223 const char *retval;
225 if (!gid_hash)
226 gid_hash = g_hash_table_new(NULL, NULL);
228 retval = g_hash_table_lookup(gid_hash, GINT_TO_POINTER(gid));
230 if (!retval)
232 struct group *group;
234 group = getgrgid(gid);
235 retval = group ? g_strdup(group->gr_name)
236 : g_strdup_printf("[%d]", (int) gid);
237 g_hash_table_insert(gid_hash, GINT_TO_POINTER(gid),
238 (gchar *) retval);
241 return retval;
244 /* Return a string in the form '23 M' in a static buffer valid until
245 * the next call.
247 const char *format_size(off_t size)
249 static char *buffer = NULL;
250 const char *units;
252 if (size >= PRETTY_SIZE_LIMIT)
254 size += 1023;
255 size >>= 10;
256 if (size >= PRETTY_SIZE_LIMIT)
258 size += 1023;
259 size >>= 10;
260 if (size >= PRETTY_SIZE_LIMIT)
262 size += 1023;
263 size >>= 10;
264 units = "G";
266 else
267 units = "M";
269 else
270 units = "K";
272 else
273 units = _("B");
275 g_free(buffer);
276 buffer = g_strdup_printf("%" SIZE_FMT " %s", size, units);
278 return buffer;
281 /* Return a string in the form '23M' in a static buffer valid until
282 * the next call. Aligned to the right (5 chars).
284 const char *format_size_aligned(off_t size)
286 static char *buffer = NULL;
287 char units;
289 if (size >= PRETTY_SIZE_LIMIT)
291 size += 1023;
292 size >>= 10;
293 if (size >= PRETTY_SIZE_LIMIT)
295 size += 1023;
296 size >>= 10;
297 if (size >= PRETTY_SIZE_LIMIT)
299 size += 1023;
300 size >>= 10;
301 units = 'G';
303 else
304 units = 'M';
306 else
307 units = 'K';
309 else
310 units = ' ';
312 g_free(buffer);
313 buffer = g_strdup_printf("%4" SIZE_FMT "%c", size, units);
315 return buffer;
319 * Similar to format_size(), but this one uses a double argument since
320 * unsigned long isn't wide enough on all platforms and we must be able to
321 * sum sizes above 4 GB.
323 const gchar *format_double_size(double size)
325 static gchar *buf = NULL;
326 const char *units;
328 if (size >= PRETTY_SIZE_LIMIT)
330 size += 1023;
331 size /= 1024;
332 if (size >= PRETTY_SIZE_LIMIT)
334 size += 1023;
335 size /= 1024;
336 if (size >= PRETTY_SIZE_LIMIT)
338 size += 1023;
339 size /= 1024;
340 units = "G";
342 else
343 units = "M";
345 else
346 units = "K";
349 else if (size != 1)
350 units = _("bytes");
351 else
352 units = _("byte");
354 g_free(buf);
355 buf = g_strdup_printf("%.0f %s", floor(size), units);
357 return buf;
360 /* Fork and exec argv. Wait and return the child's exit status.
361 * -1 if spawn fails.
362 * Returns the error string from the command if any, or NULL on success.
363 * If the process returns a non-zero exit status without producing a message,
364 * a suitable message is created.
365 * g_free() the result.
367 char *fork_exec_wait(const char **argv)
369 int status;
370 gchar *errors = NULL;
371 GError *error = NULL;
373 if (!g_spawn_sync(NULL, (char **) argv, NULL,
374 G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
375 NULL, NULL,
376 NULL, &errors, &status, &error))
378 char *msg;
380 msg = g_strdup(error->message);
381 g_error_free(error);
382 return msg;
385 if (errors && !*errors)
386 null_g_free(&errors);
388 if (!WIFEXITED(status))
390 if (!errors)
391 errors = g_strdup("(Subprocess crashed?)");
393 else if (WEXITSTATUS(status))
395 if (!errors)
396 errors = g_strdup(_("ERROR"));
399 if (errors)
400 g_strstrip(errors);
402 return errors;
405 /* If a file has this UID and GID, which permissions apply to us?
406 * 0 = User, 1 = Group, 2 = World
408 gint applicable(uid_t uid, gid_t gid)
410 int i;
412 if (uid == euid)
413 return 0;
415 if (gid == egid)
416 return 1;
418 for (i = 0; i < ngroups; i++)
420 if (supplemental_groups[i] == gid)
421 return 1;
424 return 2;
427 /* Converts a file's mode to a string. Result is a pointer
428 * to a static buffer, valid until the next call.
430 const char *pretty_permissions(mode_t m)
432 static char buffer[] = "rwx,rwx,rwx/UG"
433 #ifdef S_ISVTX
435 #endif
438 buffer[0] = m & S_IRUSR ? 'r' : '-';
439 buffer[1] = m & S_IWUSR ? 'w' : '-';
440 buffer[2] = m & S_IXUSR ? 'x' : '-';
442 buffer[4] = m & S_IRGRP ? 'r' : '-';
443 buffer[5] = m & S_IWGRP ? 'w' : '-';
444 buffer[6] = m & S_IXGRP ? 'x' : '-';
446 buffer[8] = m & S_IROTH ? 'r' : '-';
447 buffer[9] = m & S_IWOTH ? 'w' : '-';
448 buffer[10] = m & S_IXOTH ? 'x' : '-';
450 buffer[12] = m & S_ISUID ? 'U' : '-';
451 buffer[13] = m & S_ISGID ? 'G' : '-';
452 #ifdef S_ISVTX
453 buffer[14] = m & S_ISVTX ? 'T' : '-';
454 #endif
456 return buffer;
459 /* Gets the canonical name for address and compares to our_host_name() */
460 static gboolean is_local_address(char *address)
462 struct hostent *ent;
464 ent = gethostbyname(address);
466 return strcmp(our_host_name(), ent ? ent->h_name : address) == 0;
469 /* Convert a URI to a local pathname (or NULL if it isn't local).
470 * The returned pointer needs to be passed to g_free() when done (if not NULL).
471 * THIS IS A CHANGE. The return path has been processed by unescape_uri().
472 * Possible formats:
473 * /path
474 * ///path
475 * //host/path
476 * file://host/path
478 char *get_local_path(const EscapedPath *escaped_uri)
480 const char *uri = (char *) escaped_uri;
482 if (*uri == '/')
484 char *path, *uri_host;
486 /* Just a local path - no host part */
487 if (uri[1] != '/')
488 return unescape_uri((EscapedPath *) uri);
490 path = strchr(uri + 2, '/');
491 if (!path)
492 return NULL; /* //something */
494 if (path - uri == 2)
496 /* ///path */
497 return unescape_uri((EscapedPath *) path);
500 uri_host = g_strndup(uri + 2, path - uri - 2);
501 if (is_local_address(uri_host))
503 g_free(uri_host);
504 /* //myhost/path */
505 return unescape_uri((EscapedPath *) path);
507 g_free(uri_host);
509 return NULL; /* From a different host */
511 else
513 if (strncasecmp(uri, "file:", 5))
514 return NULL; /* Don't know this format */
516 uri += 5;
518 if (*uri == '/')
519 return get_local_path((EscapedPath *) uri);
521 return NULL;
525 /* Set the close-on-exec flag for this FD.
526 * TRUE means that an exec()'d process will not get the FD.
528 void close_on_exec(int fd, gboolean close)
530 if (fcntl(fd, F_SETFD, close))
531 g_warning("fcntl() failed: %s\n", g_strerror(errno));
534 void set_blocking(int fd, gboolean blocking)
536 if (fcntl(fd, F_SETFL, blocking ? 0 : O_NONBLOCK))
537 g_warning("fcntl() failed: %s\n", g_strerror(errno));
540 /* Format this time nicely.
541 * g_free() the result.
543 char *pretty_time(const time_t *time)
545 char time_buf[32];
547 if (strftime(time_buf, sizeof(time_buf),
548 TIME_FORMAT, localtime(time)) == 0)
549 time_buf[0]= 0;
551 return to_utf8(time_buf);
554 #ifndef O_NOFOLLOW
555 # define O_NOFOLLOW 0x0
556 #endif
558 /* 'from' and 'to' are complete pathnames of files (not dirs or symlinks).
559 * This spawns 'cp' to do the copy if lstat() succeeds, otherwise we
560 * do the copy manually using vfs.
562 * Returns an error string, or NULL on success. g_free() the result.
564 * XXX: This was only used for libvfs...
566 guchar *copy_file(const guchar *from, const guchar *to)
568 const char *argv[] = {"cp", "-pRf", NULL, NULL, NULL};
570 argv[2] = from;
571 argv[3] = to;
573 return fork_exec_wait(argv);
576 /* 'word' has all special characters escaped so that it may be inserted
577 * into a shell command.
578 * Eg: 'My Dir?' becomes 'My\ Dir\?'. g_free() the result.
580 guchar *shell_escape(const guchar *word)
582 GString *tmp;
583 guchar *retval;
585 tmp = g_string_new(NULL);
587 while (*word)
589 if (strchr(" ?*['\"$~\\|();!`&", *word))
590 g_string_append_c(tmp, '\\');
591 g_string_append_c(tmp, *word);
592 word++;
595 retval = tmp->str;
596 g_string_free(tmp, FALSE);
597 return retval;
600 /* TRUE iff `sub' is (or would be) an object inside the directory `parent',
601 * (or the two are the same item/directory).
602 * FALSE if parent doesn't exist.
604 gboolean is_sub_dir(const char *sub_obj, const char *parent)
606 struct stat parent_info;
607 char *sub;
609 if (mc_lstat(parent, &parent_info))
610 return FALSE; /* Parent doesn't exist */
612 /* For checking Copy/Move operations do a realpath first on sub
613 * (the destination), since copying into a symlink is the same as
614 * copying into the thing it points to. Don't realpath 'parent' though;
615 * copying a symlink just makes a new symlink.
617 * When checking if an icon depends on a file (parent), use realpath on
618 * sub (the icon) too.
620 sub = pathdup(sub_obj);
622 while (1)
624 char *slash;
625 struct stat info;
627 if (mc_lstat(sub, &info) == 0)
629 if (info.st_dev == parent_info.st_dev &&
630 info.st_ino == parent_info.st_ino)
632 g_free(sub);
633 return TRUE;
637 slash = strrchr(sub, '/');
638 if (!slash)
639 break;
640 if (slash == sub)
642 if (sub[1])
643 sub[1] = '\0';
644 else
645 break;
647 else
648 *slash = '\0';
651 g_free(sub);
653 return FALSE;
656 /* True if the string 'list' contains 'item'.
657 * Eg ("close", "close, help") -> TRUE
659 gboolean in_list(const guchar *item, const guchar *list)
661 int len;
663 len = strlen(item);
665 while (*list)
667 if (strncmp(item, list, len) == 0 &&
668 !g_ascii_isalpha(list[len]))
669 return TRUE;
670 list = strchr(list, ',');
671 if (!list)
672 return FALSE;
673 while (g_ascii_isspace(*++list))
677 return FALSE;
680 /* Split a path into its components. Eg:
682 * /bob/fred -> ["bob", "fred"]
683 * ///a//b// -> ["a", "b"]
684 * / -> []
686 * The array and the strings in it must be freed after use.
688 GPtrArray *split_path(const guchar *path)
690 GPtrArray *array;
691 guchar *slash;
693 g_return_val_if_fail(path != NULL, NULL);
695 array = g_ptr_array_new();
697 while (1)
699 while (path[0] == '/')
700 path++;
701 if (path[0] == '\0')
702 break;
704 slash = strchr(path, '/');
705 if (slash)
707 g_ptr_array_add(array, g_strndup(path, slash - path));
708 path = slash + 1;
709 continue;
711 g_ptr_array_add(array, g_strdup(path));
712 break;
715 return array;
718 /* Return the shortest path from 'from' to 'to'.
719 * Eg: get_relative_path("/a/b/c", "a/d/e") -> "../d/e"
721 guchar *get_relative_path(const guchar *from, const guchar *to)
723 GString *path;
724 guchar *retval;
725 GPtrArray *src, *dst;
726 int i, j;
728 src = split_path(from);
729 dst = split_path(to);
731 /* The last component of src doesn't matter... */
732 if (src->len)
734 g_free(src->pdata[src->len - 1]);
735 g_ptr_array_remove_index(src, src->len - 1);
738 /* Strip off common path elements... */
739 i = 0;
740 while (i < src->len && i < dst->len)
742 guchar *a = (guchar *) src->pdata[i];
743 guchar *b = (guchar *) dst->pdata[i];
745 if (strcmp(a, b) != 0)
746 break;
747 i++;
750 /* Go up one dir for each element remaining in src */
751 path = g_string_new(NULL);
752 for (j = i; j < src->len; j++)
753 g_string_append(path, "../");
755 /* Go down one dir for each element remaining in dst */
756 for (j = i; j < dst->len; j++)
758 g_string_append(path, (guchar *) dst->pdata[j]);
759 g_string_append_c(path, '/');
762 if (path->str[path->len - 1] == '/')
763 g_string_truncate(path, path->len - 1);
764 if (path->len == 0)
765 g_string_assign(path, ".");
767 /* Free the arrays */
768 for (i = 0; i < src->len; i++)
769 g_free(src->pdata[i]);
770 g_ptr_array_free(src, TRUE);
771 for (i = 0; i < dst->len; i++)
772 g_free(dst->pdata[i]);
773 g_ptr_array_free(dst, TRUE);
775 retval = path->str;
776 g_string_free(path, FALSE);
778 return retval;
782 * Interperet text as a boolean value. Return defvalue if we don't
783 * recognise it
785 int text_to_boolean(const char *text, int defvalue)
787 if (g_strcasecmp(text, "true")==0)
788 return TRUE;
789 else if (g_strcasecmp(text, "false")==0)
790 return FALSE;
791 else if (g_strcasecmp(text, "yes")==0)
792 return TRUE;
793 else if (g_strcasecmp(text, "no")==0)
794 return FALSE;
795 else if (g_ascii_isdigit(text[0]))
796 return !!atoi(text);
798 return defvalue;
801 /* Return the pathname that this symlink points to.
802 * NULL on error (not a symlink, path too long) and errno set.
803 * g_free() the result.
805 char *readlink_dup(const char *source)
807 char path[MAXPATHLEN + 1];
808 int got;
810 got = readlink(source, path, MAXPATHLEN);
811 if (got < 0 || got > MAXPATHLEN)
812 return NULL;
814 return g_strndup(path, got);
818 * This code implements the MD5 message-digest algorithm.
819 * The algorithm is due to Ron Rivest. The original code was
820 * written by Colin Plumb in 1993, and put in the public domain.
822 * Modified to use glib datatypes. Put under GPL to simplify
823 * licensing for ROX-Filer. Taken from Debian's dpkg package.
826 #define md5byte unsigned char
828 typedef struct _MD5Context MD5Context;
830 struct _MD5Context {
831 guint32 buf[4];
832 guint32 bytes[2];
833 guint32 in[16];
836 #if G_BYTE_ORDER == G_BIG_ENDIAN
837 static void byteSwap(guint32 *buf, unsigned words)
839 md5byte *p = (md5byte *)buf;
841 do {
842 *buf++ = (guint32)((unsigned)p[3] << 8 | p[2]) << 16 |
843 ((unsigned)p[1] << 8 | p[0]);
844 p += 4;
845 } while (--words);
847 #else
848 #define byteSwap(buf,words)
849 #endif
852 * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
853 * initialization constants.
855 static void MD5Init(MD5Context *ctx)
857 ctx->buf[0] = 0x67452301;
858 ctx->buf[1] = 0xefcdab89;
859 ctx->buf[2] = 0x98badcfe;
860 ctx->buf[3] = 0x10325476;
862 ctx->bytes[0] = 0;
863 ctx->bytes[1] = 0;
867 * Update context to reflect the concatenation of another buffer full
868 * of bytes.
870 static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len)
872 guint32 t;
874 /* Update byte count */
876 t = ctx->bytes[0];
877 if ((ctx->bytes[0] = t + len) < t)
878 ctx->bytes[1]++; /* Carry from low to high */
880 t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
881 if (t > len) {
882 memcpy((md5byte *)ctx->in + 64 - t, buf, len);
883 return;
885 /* First chunk is an odd size */
886 memcpy((md5byte *)ctx->in + 64 - t, buf, t);
887 byteSwap(ctx->in, 16);
888 MD5Transform(ctx->buf, ctx->in);
889 buf += t;
890 len -= t;
892 /* Process data in 64-byte chunks */
893 while (len >= 64) {
894 memcpy(ctx->in, buf, 64);
895 byteSwap(ctx->in, 16);
896 MD5Transform(ctx->buf, ctx->in);
897 buf += 64;
898 len -= 64;
901 /* Handle any remaining bytes of data. */
902 memcpy(ctx->in, buf, len);
906 * Final wrapup - pad to 64-byte boundary with the bit pattern
907 * 1 0* (64-bit count of bits processed, MSB-first)
908 * Returns the newly allocated string of the hash.
910 static char *MD5Final(MD5Context *ctx)
912 char *retval;
913 int i;
914 int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
915 md5byte *p = (md5byte *)ctx->in + count;
916 guint8 *bytes;
918 /* Set the first char of padding to 0x80. There is always room. */
919 *p++ = 0x80;
921 /* Bytes of padding needed to make 56 bytes (-8..55) */
922 count = 56 - 1 - count;
924 if (count < 0) { /* Padding forces an extra block */
925 memset(p, 0, count + 8);
926 byteSwap(ctx->in, 16);
927 MD5Transform(ctx->buf, ctx->in);
928 p = (md5byte *)ctx->in;
929 count = 56;
931 memset(p, 0, count);
932 byteSwap(ctx->in, 14);
934 /* Append length in bits and transform */
935 ctx->in[14] = ctx->bytes[0] << 3;
936 ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
937 MD5Transform(ctx->buf, ctx->in);
939 byteSwap(ctx->buf, 4);
941 retval = g_malloc(33);
942 bytes = (guint8 *) ctx->buf;
943 for (i = 0; i < 16; i++)
944 sprintf(retval + (i * 2), "%02x", bytes[i]);
945 retval[32] = '\0';
947 return retval;
950 # ifndef ASM_MD5
952 /* The four core functions - F1 is optimized somewhat */
954 /* #define F1(x, y, z) (x & y | ~x & z) */
955 #define F1(x, y, z) (z ^ (x & (y ^ z)))
956 #define F2(x, y, z) F1(z, x, y)
957 #define F3(x, y, z) (x ^ y ^ z)
958 #define F4(x, y, z) (y ^ (x | ~z))
960 /* This is the central step in the MD5 algorithm. */
961 #define MD5STEP(f,w,x,y,z,in,s) \
962 (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
965 * The core of the MD5 algorithm, this alters an existing MD5 hash to
966 * reflect the addition of 16 longwords of new data. MD5Update blocks
967 * the data and converts bytes into longwords for this routine.
969 static void MD5Transform(guint32 buf[4], guint32 const in[16])
971 register guint32 a, b, c, d;
973 a = buf[0];
974 b = buf[1];
975 c = buf[2];
976 d = buf[3];
978 MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
979 MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
980 MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
981 MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
982 MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
983 MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
984 MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
985 MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
986 MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
987 MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
988 MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
989 MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
990 MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
991 MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
992 MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
993 MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
995 MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
996 MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
997 MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
998 MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
999 MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
1000 MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
1001 MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
1002 MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
1003 MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
1004 MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
1005 MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
1006 MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
1007 MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
1008 MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
1009 MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
1010 MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
1012 MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
1013 MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
1014 MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
1015 MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
1016 MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
1017 MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
1018 MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
1019 MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
1020 MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
1021 MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
1022 MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
1023 MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
1024 MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
1025 MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
1026 MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
1027 MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
1029 MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
1030 MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
1031 MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
1032 MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
1033 MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
1034 MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
1035 MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
1036 MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
1037 MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
1038 MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
1039 MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
1040 MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
1041 MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
1042 MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
1043 MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
1044 MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
1046 buf[0] += a;
1047 buf[1] += b;
1048 buf[2] += c;
1049 buf[3] += d;
1052 # endif /* ASM_MD5 */
1054 char *md5_hash(const char *message)
1056 MD5Context ctx;
1058 MD5Init(&ctx);
1059 MD5Update(&ctx, message, strlen(message));
1060 return MD5Final(&ctx);
1063 /* Convert string 'src' from the current locale to UTF-8 */
1064 gchar *to_utf8(const gchar *src)
1066 gchar *retval;
1068 if (!src)
1069 return NULL;
1071 retval = g_locale_to_utf8(src, -1, NULL, NULL, NULL);
1072 if (!retval)
1073 retval = g_convert_with_fallback(src, -1, "utf-8", "iso-8859-1",
1074 "?", NULL, NULL, NULL);
1076 return retval ? retval : g_strdup(src);
1079 /* Ensure string at 'sp' is UTF-8. g_free() and replace by
1080 * UTF-8 version if not.
1082 void ensure_utf8(gchar **sp)
1084 gchar *s = *sp;
1086 if (!g_utf8_validate(s, -1, NULL))
1088 *sp = to_utf8(s);
1089 g_free(s);
1093 /* Removes trailing / chars and converts a leading '~/' (if any) to
1094 * the user's home dir. g_free() the result.
1096 gchar *expand_path(const gchar *path)
1098 guchar *retval;
1099 int path_len;
1101 g_return_val_if_fail(path != NULL, NULL);
1103 path_len = strlen(path);
1104 while (path_len > 1 && path[path_len - 1] == '/')
1105 path_len--;
1107 retval = g_strndup(path, path_len);
1109 if (path[0] == '~' && (path[1] == '\0' || path[1] == '/'))
1111 guchar *tmp = retval;
1113 retval = g_strconcat(home_dir, retval + 1, NULL);
1114 g_free(tmp);
1117 return retval;
1120 /* g_free() every element in the list, then free the list itself and
1121 * NULL the pointer to the list.
1123 void destroy_glist(GList **list)
1125 GList *l = *list;
1126 g_list_foreach(l, (GFunc) g_free, NULL);
1127 g_list_free(l);
1128 *list = NULL;
1131 void null_g_free(gpointer p)
1133 g_free(*(gpointer *)p);
1134 *(gpointer *)p = NULL;
1137 typedef struct _CollatePart CollatePart;
1139 struct _CollateKey {
1140 CollatePart *parts;
1141 gboolean caps;
1144 struct _CollatePart {
1145 guchar *text; /* NULL => end of list */
1146 long number;
1149 /* Break 'name' (a UTF-8 string) down into a list of (text, number) pairs.
1150 * The text parts processed for collating. This allows any two names to be
1151 * quickly compared later for intelligent sorting (comparing names is
1152 * speed-critical).
1154 CollateKey *collate_key_new(const guchar *name)
1156 const guchar *i;
1157 guchar *to_free = NULL;
1158 GArray *array;
1159 CollatePart new;
1160 CollateKey *retval;
1161 char *tmp;
1163 g_return_val_if_fail(name != NULL, NULL);
1165 array = g_array_new(FALSE, FALSE, sizeof(CollatePart));
1167 /* Ensure valid UTF-8 */
1168 if (!g_utf8_validate(name, -1, NULL))
1170 to_free = to_utf8(name);
1171 name = to_free;
1174 retval = g_new(CollateKey, 1);
1175 retval->caps = g_unichar_isupper(g_utf8_get_char(name));
1177 for (i = name; *i; i = g_utf8_next_char(i))
1179 gunichar first_char;
1181 /* We're in a (possibly blank) text section starting at 'name'.
1182 * Find the end of it (the next digit, or end of string).
1183 * Note: g_ascii_isdigit takes char, not unichar, while
1184 * g_unicode_isdigit returns true for non ASCII digits.
1186 first_char = g_utf8_get_char(i);
1187 if (first_char >= '0' && first_char <= '9')
1189 char *endp;
1191 /* i -> first digit character */
1192 tmp = g_utf8_strdown(name, i - name);
1193 new.text = g_utf8_collate_key(tmp, -1);
1194 g_free(tmp);
1195 new.number = strtol(i, &endp, 10);
1197 g_array_append_val(array, new);
1199 g_return_val_if_fail(endp > (char *) i, NULL);
1201 name = endp;
1202 i = name - 1;
1206 tmp = g_utf8_strdown(name, i - name);
1207 new.text = g_utf8_collate_key(tmp, -1);
1208 g_free(tmp);
1209 new.number = -1;
1210 g_array_append_val(array, new);
1212 new.text = NULL;
1213 g_array_append_val(array, new);
1215 retval->parts = (CollatePart *) array->data;
1216 g_array_free(array, FALSE);
1218 if (to_free)
1219 g_free(to_free); /* Only taken for invalid UTF-8 */
1221 return retval;
1224 void collate_key_free(CollateKey *key)
1226 CollatePart *part;
1228 for (part = key->parts; part->text; part++)
1229 g_free(part->text);
1230 g_free(key->parts);
1231 g_free(key);
1234 int collate_key_cmp(const CollateKey *key1, const CollateKey *key2,
1235 gboolean caps_first)
1237 CollatePart *n1 = key1->parts;
1238 CollatePart *n2 = key2->parts;
1239 int r;
1241 if (caps_first)
1243 if (key1->caps && !key2->caps)
1244 return -1;
1245 else if (key2->caps && !key1->caps)
1246 return 1;
1249 while (1)
1251 if (!n1->text)
1252 return n2->text ? -1 : 0;
1253 if (!n2->text)
1254 return 1;
1255 r = strcmp(n1->text, n2->text);
1256 if (r)
1257 return r;
1259 if (n1->number < n2->number)
1260 return -1;
1261 if (n1->number > n2->number)
1262 return 1;
1264 n1++;
1265 n2++;
1269 /* Returns TRUE if the object exists, FALSE if it doesn't.
1270 * For symlinks, the file pointed to must exist.
1272 gboolean file_exists(const char *path)
1274 struct stat info;
1276 return !mc_stat(path, &info);
1279 /* Escape path for future use in URI */
1280 EscapedPath *escape_uri_path(const char *path)
1282 const char *safe = ":-_./"; /* Plus isalnum() */
1283 const guchar *s;
1284 gchar *ans;
1285 GString *str;
1287 str = g_string_sized_new(strlen(path));
1289 for (s = path; *s; s++)
1291 if (!g_ascii_isalnum(*s) && !strchr(safe, *s))
1292 g_string_append_printf(str, "%%%02x", *s);
1293 else
1294 str = g_string_append_c(str, *s);
1297 ans = str->str;
1298 g_string_free(str, FALSE);
1300 return (EscapedPath *) ans;
1303 EscapedPath *encode_path_as_uri(const guchar *path)
1305 gchar *tpath = (gchar *) escape_uri_path(path);
1306 gchar *uri;
1308 uri = g_strconcat("file://", our_host_name_for_dnd(), tpath, NULL);
1309 g_free(tpath);
1311 return (EscapedPath *) uri;
1314 gchar *unescape_uri(const EscapedPath *uri)
1316 const char *uri_string = (char *) uri;
1317 const gchar *s;
1318 gchar *d;
1319 gchar *tmp;
1321 tmp = g_malloc(strlen(uri_string) + 1);
1322 for (s = uri_string, d = tmp; *s; s++, d++)
1324 /*printf("%s\n", s);*/
1325 if (*s == '%' && g_ascii_isxdigit(s[1]) &&
1326 g_ascii_isxdigit(s[2]))
1328 int c;
1329 char buf[3];
1330 buf[0] = s[1];
1331 buf[1] = s[2];
1332 buf[2] = 0;
1333 c = (int) strtol(buf, NULL, 16);
1334 *d = c;
1335 s += 2;
1337 else
1338 *d = *s;
1340 *d = '\0';
1342 return tmp;
1345 /* Used as the sort function for sorting GPtrArrays */
1346 gint strcmp2(gconstpointer a, gconstpointer b)
1348 const char *aa = *(char **) a;
1349 const char *bb = *(char **) b;
1351 return g_strcasecmp(aa, bb);
1354 /* Returns an array listing all the names in the directory 'path'.
1355 * The array is sorted.
1356 * '.' and '..' are skipped.
1357 * On error, the error is reported with g_warning and NULL is returned.
1359 GPtrArray *list_dir(const guchar *path)
1361 GDir *dir;
1362 GError *error = NULL;
1363 const char *leaf;
1364 GPtrArray *names;
1366 dir = g_dir_open(path, 0, &error);
1367 if (error)
1369 g_warning("Can't list directory:\n%s", error->message);
1370 g_error_free(error);
1371 return NULL;
1374 names = g_ptr_array_new();
1376 while ((leaf = g_dir_read_name(dir))) {
1377 if (leaf[0] != '.')
1378 g_ptr_array_add(names, g_strdup(leaf));
1381 g_dir_close(dir);
1383 g_ptr_array_sort(names, strcmp2);
1385 return names;
1388 int stat_with_timeout(const char *path, struct stat *info)
1390 int status;
1391 pid_t child;
1392 gboolean retval;
1394 child = fork();
1395 if (child < 0)
1397 g_warning("stat_with_timeout: fork(): %s", g_strerror(errno));
1398 return -1;
1401 if (child == 0)
1403 /* Child */
1404 alarm(3);
1405 _exit(mc_stat(path, info) ? 1 : 0);
1408 waitpid(child, &status, 0);
1410 if (status == 0)
1411 retval = mc_stat(path, info);
1412 else
1413 retval = -1;
1415 return retval;
1418 /* From glib. */
1419 static gchar *my_strchrnul(const gchar *str, gchar c)
1421 gchar *p = (gchar*) str;
1422 while (*p && (*p != c))
1423 ++p;
1425 return p;
1428 /* Based on code from glib. */
1429 gboolean available_in_path(const char *file)
1431 const gchar *path, *p;
1432 gchar *name, *freeme;
1433 size_t len;
1434 size_t pathlen;
1435 gboolean found = FALSE;
1437 path = g_getenv("PATH");
1438 if (path == NULL)
1439 path = "/bin:/usr/bin:.";
1441 len = strlen(file) + 1;
1442 pathlen = strlen(path);
1443 freeme = name = g_malloc(pathlen + len + 1);
1445 /* Copy the file name at the top, including '\0' */
1446 memcpy(name + pathlen + 1, file, len);
1447 name = name + pathlen;
1448 /* And add the slash before the filename */
1449 *name = '/';
1451 p = path;
1454 char *startp;
1456 path = p;
1457 p = my_strchrnul(path, ':');
1459 if (p == path)
1460 /* Two adjacent colons, or a colon at the beginning or
1461 * the end of `PATH' means to search the current
1462 * directory.
1464 startp = name + 1;
1465 else
1466 startp = memcpy (name - (p - path), path, p - path);
1468 /* Try to execute this name. If it works, execv will not
1469 * return.
1471 if (access(startp, X_OK) == 0)
1472 found = TRUE;
1473 } while (!found && *p++ != '\0');
1475 g_free(freeme);
1477 return found;
1480 static char *get_value_from_desktop_data(const char *data, size_t length,
1481 const char *section, const char *key,
1482 GError **error)
1484 int section_length, key_length;
1485 const char *last_possible_key_start;
1486 const char *last_possible_section_start;
1487 const char *next;
1489 section_length = strlen(section);
1490 last_possible_section_start = data + length - section_length - 3;
1492 for (next = data; next <= last_possible_section_start; next++)
1494 if (next[0] != '[' || (next > data && next[-1] != '\n'))
1495 continue;
1496 next += 1;
1497 if (next[section_length] != ']' ||
1498 next[section_length + 1] != '\n')
1499 continue;
1500 if (strncmp(next, section, section_length) != 0)
1501 continue;
1502 next += section_length + 1; /* Newline */
1503 goto find_key;
1505 return NULL;
1507 find_key:
1508 key_length = strlen(key);
1509 last_possible_key_start = data + length - key_length - 3;
1510 for (;
1511 next && next < last_possible_key_start;
1512 next = memchr(next + 1, '\n', last_possible_key_start - next))
1514 const char *nl;
1516 next++; /* Skip newline */
1518 if (next[0] == '[')
1519 break; /* End of section */
1521 if (strncmp(next, key, key_length) != 0)
1522 continue;
1523 next += key_length;
1524 while (next[0] == ' ' || next[0] == '\t')
1525 next++;
1527 /* Note: if we had GLib 2.6, we could use
1528 * g_get_language_names() to get translations here.
1530 if (next[0] != '=')
1531 continue;
1532 next++;
1533 while (next[0] == ' ' || next[0] == '\t')
1534 next++;
1535 nl = memchr(next, '\n', data + length - next);
1536 if (!nl)
1537 break;
1538 return g_strndup(next, nl - next);
1541 return NULL;
1544 /* Load .desktop file 'path' and return the value of the named key.
1545 * Sets error if the desktop file cannot be parsed.
1546 * Returns NULL (but does not set error) if the key is not present.
1547 * Returned string must be g_free()d.
1549 char *get_value_from_desktop_file(const char *path,
1550 const char *section,
1551 const char *key,
1552 GError **error)
1554 char *value = NULL;
1555 struct stat info;
1556 int fd = -1;
1557 void *start = NULL;
1559 fd = open(path, O_RDONLY);
1560 if (fd == -1 || fstat(fd, &info) != 0)
1562 g_set_error(error,
1563 G_FILE_ERROR,
1564 g_file_error_from_errno(errno),
1565 _("Failed to open and stat file '%s': %s"),
1566 path,
1567 g_strerror(errno));
1568 goto err;
1570 start = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd, 0);
1571 if (start == MAP_FAILED)
1573 g_set_error(error,
1574 G_FILE_ERROR,
1575 g_file_error_from_errno(errno),
1576 _("Failed to mmap file '%s': %s"),
1577 path,
1578 g_strerror(errno));
1579 goto err;
1582 value = get_value_from_desktop_data((const char *) start,
1583 info.st_size,
1584 section, key,
1585 error);
1586 err:
1587 if (fd != -1)
1588 close(fd);
1590 if (start != NULL && start != MAP_FAILED)
1591 munmap(start, info.st_size);
1593 return value;