r1101: Big changes to internationalisation for pinboards and panels to fix problems
[rox-filer.git] / ROX-Filer / src / support.c
blob864b70a19072c04476005c12c23cba48be092472
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 <parser.h>
41 #include "global.h"
43 #include "choices.h"
44 #include "main.h"
45 #include "options.h"
46 #include "support.h"
47 #include "my_vfs.h"
48 #include "fscache.h"
50 #ifndef GTK2
51 # include "gconvert.h"
52 #endif
54 static GHashTable *uid_hash = NULL; /* UID -> User name */
55 static GHashTable *gid_hash = NULL; /* GID -> Group name */
57 /* Static prototypes */
58 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
59 static void MD5Transform(guint32 buf[4], guint32 const in[16]);
60 #endif
62 /* Static prototypes */
63 static XMLwrapper *xml_load(char *pathname, gpointer data);
64 static void xml_ref(XMLwrapper *dir, gpointer data);
65 static void xml_unref(XMLwrapper *dir, gpointer data);
66 static int xml_getref(XMLwrapper *dir, gpointer data);
68 /****************************************************************
69 * EXTERNAL INTERFACE *
70 ****************************************************************/
72 XMLwrapper *xml_cache_load(gchar *pathname)
74 static GFSCache *xml_cache = NULL;
76 if (!xml_cache)
77 xml_cache = g_fscache_new((GFSLoadFunc) xml_load,
78 (GFSRefFunc) xml_ref,
79 (GFSRefFunc) xml_unref,
80 (GFSGetRefFunc) xml_getref,
81 NULL, NULL);
82 return g_fscache_lookup(xml_cache, pathname);
85 void xml_cache_unref(XMLwrapper *wrapper)
87 xml_unref(wrapper, NULL);
90 /* Return the (first) child of this node with the given name.
91 * NULL if not found.
93 xmlNode *get_subnode(xmlNode *node, const char *namespaceURI, const char *name)
95 for (node = node->xmlChildrenNode; node; node = node->next)
97 if (node->type != XML_ELEMENT_NODE)
98 continue;
100 if (strcmp(node->name, name))
101 continue;
103 if (node->ns == NULL || namespaceURI == NULL)
105 if (node->ns == NULL && namespaceURI == NULL)
106 return node;
107 continue;
110 if (strcmp(node->ns->href, namespaceURI) == 0)
111 return node;
114 return NULL;
117 /* Save doc as XML as filename, 0 on success or -1 on failure */
118 int save_xml_file(xmlDocPtr doc, gchar *filename)
120 #if LIBXML_VERSION > 20400
121 if (xmlSaveFormatFileEnc(filename, doc, NULL, 1) < 0)
122 return 1;
123 #else
124 FILE *out;
126 out = fopen(filename, "w");
127 if (!out)
128 return 1;
130 xmlDocDump(out, doc); /* Some versions return void */
132 if (fclose(out))
133 return 1;
134 #endif
136 return 0;
139 /* Create a new SOAP message and return the document and the (empty)
140 * body node.
142 xmlDocPtr soap_new(xmlNodePtr *ret_body)
144 xmlDocPtr doc;
145 xmlNodePtr root;
146 xmlNs *env_ns;
148 doc = xmlNewDoc("1.0");
149 root = xmlNewDocNode(doc, NULL, "Envelope", NULL);
150 xmlDocSetRootElement(doc, root);
152 env_ns = xmlNewNs(root, SOAP_ENV_NS, "env");
153 xmlSetNs(root, env_ns);
155 *ret_body = xmlNewTextChild(root, env_ns, "Body", NULL);
156 xmlNewNs(*ret_body, ROX_NS, "rox");
158 return doc;
161 /* Like g_strdup, but does realpath() too (if possible) */
162 char *pathdup(char *path)
164 char real[MAXPATHLEN];
166 g_return_val_if_fail(path != NULL, NULL);
168 if (realpath(path, real))
169 return g_strdup(real);
171 return g_strdup(path);
174 /* Join the path to the leaf (adding a / between them) and
175 * return a pointer to a buffer with the result. Buffer is valid until
176 * the next call to make_path.
178 GString *make_path(char *dir, char *leaf)
180 static GString *buffer = NULL;
182 if (!buffer)
183 buffer = g_string_new(NULL);
185 g_return_val_if_fail(dir != NULL, buffer);
186 g_return_val_if_fail(leaf != NULL, buffer);
188 g_string_sprintf(buffer, "%s%s%s",
189 dir,
190 dir[0] == '/' && dir[1] == '\0' ? "" : "/",
191 leaf);
193 return buffer;
196 /* Return our complete host name for DND */
197 char *our_host_name_for_dnd(void)
199 if (option_get_int("dnd_no_hostnames"))
200 return "";
201 return our_host_name();
204 /* Return our complete host name, unconditionally */
205 char *our_host_name(void)
207 static char *name = NULL;
209 if (!name)
211 char buffer[4096];
213 if (gethostname(buffer, 4096) == 0)
215 /* gethostname doesn't always return the full name... */
216 struct hostent *ent;
218 buffer[4095] = '\0';
219 ent = gethostbyname(buffer);
220 name = g_strdup(ent ? ent->h_name : buffer);
222 else
224 g_warning("gethostname() failed - using localhost\n");
225 name = g_strdup("localhost");
229 return name;
232 /* Create a child process. cd to dir first (if dir is non-NULL).
233 * If from_stderr is set, create a pipe for stderr and return the readable
234 * side here.
235 * Returns the PID of the child, or 0 on failure (from_stderr is still valid).
237 pid_t spawn_full(char **argv, char *dir, int *from_stderr)
239 int child;
240 int fd[2];
242 if (from_stderr)
244 if (pipe(fd) == 0)
245 *from_stderr = fd[0];
246 else
248 *from_stderr = -1;
249 from_stderr = NULL;
253 child = fork();
255 if (child == -1)
256 return 0; /* Failure */
257 else if (child == 0)
259 /* We are the child process */
260 if (from_stderr)
262 close(fd[0]);
263 if (fd[1] != STDERR_FILENO)
265 dup2(fd[1], STDERR_FILENO);
266 close(fd[1]);
267 close_on_exec(STDERR_FILENO, FALSE);
271 if (dir)
272 if (chdir(dir))
273 fprintf(stderr, "chdir() failed: %s\n",
274 g_strerror(errno));
275 execvp(argv[0], argv);
276 fprintf(stderr, "execvp(%s, ...) failed: %s\n",
277 argv[0],
278 g_strerror(errno));
279 _exit(0);
282 if (from_stderr)
283 close(fd[1]);
285 /* We are the parent */
286 return child;
289 void debug_free_string(void *data)
291 g_print("Freeing string '%s'\n", (char *) data);
292 g_free(data);
295 char *user_name(uid_t uid)
297 char *retval;
299 if (!uid_hash)
300 uid_hash = g_hash_table_new(NULL, NULL);
302 retval = g_hash_table_lookup(uid_hash, GINT_TO_POINTER(uid));
304 if (!retval)
306 struct passwd *passwd;
308 passwd = getpwuid(uid);
309 retval = passwd ? g_strdup(passwd->pw_name)
310 : g_strdup_printf("[%d]", (int) uid);
311 g_hash_table_insert(uid_hash, GINT_TO_POINTER(uid), retval);
314 return retval;
317 char *group_name(gid_t gid)
319 char *retval;
321 if (!gid_hash)
322 gid_hash = g_hash_table_new(NULL, NULL);
324 retval = g_hash_table_lookup(gid_hash, GINT_TO_POINTER(gid));
326 if (!retval)
328 struct group *group;
330 group = getgrgid(gid);
331 retval = group ? g_strdup(group->gr_name)
332 : g_strdup_printf("[%d]", (int) gid);
333 g_hash_table_insert(gid_hash, GINT_TO_POINTER(gid), retval);
336 return retval;
339 /* Return a string in the form '23Mb' in a static buffer valid until
340 * the next call.
342 char *format_size(off_t size)
344 static char *buffer = NULL;
345 char *units;
347 if (size >= PRETTY_SIZE_LIMIT)
349 size += 1023;
350 size >>= 10;
351 if (size >= PRETTY_SIZE_LIMIT)
353 size += 1023;
354 size >>= 10;
355 if (size >= PRETTY_SIZE_LIMIT)
357 size += 1023;
358 size >>= 10;
359 units = "Gb";
361 else
362 units = "Mb";
364 else
365 units = "K";
367 else if (size == 1)
368 units = _("byte");
369 else
370 units = _("bytes");
372 if (buffer)
373 g_free(buffer);
374 buffer = g_strdup_printf("%" SIZE_FMT " %s", size, units);
376 return buffer;
379 /* Return a string in the form '23Mb' in a static buffer valid until
380 * the next call. Aligned to the right (5 chars).
382 char *format_size_aligned(off_t size)
384 static char *buffer = NULL;
385 char units;
387 if (size >= PRETTY_SIZE_LIMIT)
389 size += 1023;
390 size >>= 10;
391 if (size >= PRETTY_SIZE_LIMIT)
393 size += 1023;
394 size >>= 10;
395 if (size >= PRETTY_SIZE_LIMIT)
397 size += 1023;
398 size >>= 10;
399 units = 'G';
401 else
402 units = 'M';
404 else
405 units = 'K';
407 else
408 units = ' ';
410 if (buffer)
411 g_free(buffer);
413 buffer = g_strdup_printf("%4" SIZE_FMT "%c", size, units);
415 return buffer;
419 * Similar to format_size(), but this one uses a double argument since
420 * unsigned long isn't wide enough on all platforms and we must be able to
421 * sum sizes above 4 GB.
423 gchar *format_double_size(double size)
425 static gchar *buf = NULL;
426 char *units;
428 if (size >= PRETTY_SIZE_LIMIT)
430 size += 1023;
431 size /= 1024;
432 if (size >= PRETTY_SIZE_LIMIT)
434 size += 1023;
435 size /= 1024;
436 if (size >= PRETTY_SIZE_LIMIT)
438 size += 1023;
439 size /= 1024;
440 units = "Gb";
442 else
443 units = "Mb";
445 else
446 units = "K";
449 else if (size != 1)
450 units = _("bytes");
451 else
452 units = _("byte");
454 if (buf)
455 g_free(buf);
456 buf = g_strdup_printf("%.0f %s", size, units);
458 return buf;
461 /* Ensure that str ends with a newline (or is empty) */
462 static void newline(GString *str)
464 if (str->len && str->str[str->len - 1] != '\n')
465 g_string_append_c(str, '\n');
468 /* Fork and exec argv. Wait and return the child's exit status.
469 * -1 if spawn fails.
470 * Returns the error string from the command if any, or NULL on success.
471 * If the process returns a non-zero exit status without producing a message,
472 * a suitable message is created.
473 * g_free() the result.
475 char *fork_exec_wait(char **argv)
477 pid_t child;
478 int status = -1;
479 GString *errors;
480 char buffer[257];
481 int from_stderr;
483 errors = g_string_new(NULL);
485 child = spawn_full(argv, NULL, &from_stderr);
487 if (!child)
489 newline(errors);
490 g_string_append(errors, "fork: ");
491 g_string_append(errors, g_strerror(errno));
492 goto out;
495 while (from_stderr != -1)
497 int got;
499 got = read(from_stderr, buffer, sizeof(buffer) - 1);
500 if (got < 0)
502 newline(errors);
503 g_string_append(errors, "read: ");
504 g_string_append(errors, g_strerror(errno));
506 if (got <= 0)
507 break;
508 buffer[got] = '\0';
509 g_string_append(errors, buffer);
512 while (1)
514 if (waitpid(child, &status, 0) == -1)
516 if (errno != EINTR)
518 newline(errors);
519 g_string_append(errors, "waitpid: ");
520 g_string_append(errors, g_strerror(errno));
521 break;
524 else
526 if (!WIFEXITED(status))
528 newline(errors);
529 g_string_append(errors, "(crashed?)");
531 else if (WEXITSTATUS(status))
533 newline(errors);
534 if (!errors->len)
535 g_string_append(errors, "ERROR");
537 break;
541 out:
542 if (from_stderr != -1)
543 close(from_stderr);
545 if (errors->len && errors->str[errors->len - 1] == '\n')
546 g_string_truncate(errors, errors->len - 1);
548 if (errors->len)
550 char *retval = errors->str;
551 g_string_free(errors, FALSE);
552 return retval;
555 return NULL;
558 /* If a file has this UID and GID, which permissions apply to us?
559 * 0 = User, 1 = Group, 2 = World
561 gint applicable(uid_t uid, gid_t gid)
563 int i;
565 if (uid == euid)
566 return 0;
568 if (gid == egid)
569 return 1;
571 for (i = 0; i < ngroups; i++)
573 if (supplemental_groups[i] == gid)
574 return 1;
577 return 2;
580 /* Converts a file's mode to a string. Result is a pointer
581 * to a static buffer, valid until the next call.
583 char *pretty_permissions(mode_t m)
585 static char buffer[] = "rwx,rwx,rwx/UGT";
587 buffer[0] = m & S_IRUSR ? 'r' : '-';
588 buffer[1] = m & S_IWUSR ? 'w' : '-';
589 buffer[2] = m & S_IXUSR ? 'x' : '-';
591 buffer[4] = m & S_IRGRP ? 'r' : '-';
592 buffer[5] = m & S_IWGRP ? 'w' : '-';
593 buffer[6] = m & S_IXGRP ? 'x' : '-';
595 buffer[8] = m & S_IROTH ? 'r' : '-';
596 buffer[9] = m & S_IWOTH ? 'w' : '-';
597 buffer[10] = m & S_IXOTH ? 'x' : '-';
599 buffer[12] = m & S_ISUID ? 'U' : '-';
600 buffer[13] = m & S_ISGID ? 'G' : '-';
601 #ifdef S_ISVTX
602 buffer[14] = m & S_ISVTX ? 'T' : '-';
603 buffer[15] = 0;
604 #else
605 buffer[14] = 0;
606 #endif
608 return buffer;
611 /* Gets the canonical name for address and compares to our_host_name() */
612 static gboolean is_local_address(char *address)
614 struct hostent *ent;
616 ent = gethostbyname(address);
618 return strcmp(our_host_name(), ent ? ent->h_name : address) == 0;
621 /* Convert a URI to a local pathname (or NULL if it isn't local).
622 * The returned pointer points inside the input string.
623 * Possible formats:
624 * /path
625 * ///path
626 * //host/path
627 * file://host/path
629 char *get_local_path(char *uri)
631 if (*uri == '/')
633 char *path, *uri_host;
635 if (uri[1] != '/')
636 return uri; /* Just a local path - no host part */
638 path = strchr(uri + 2, '/');
639 if (!path)
640 return NULL; /* //something */
642 if (path - uri == 2)
643 return path; /* ///path */
645 uri_host = g_strndup(uri + 2, path - uri - 2);
646 if (is_local_address(uri_host))
648 g_free(uri_host);
649 return path; /* //myhost/path */
651 g_free(uri_host);
653 return NULL; /* From a different host */
655 else
657 if (strncasecmp(uri, "file:", 5))
658 return NULL; /* Don't know this format */
660 uri += 5;
662 if (*uri == '/')
663 return get_local_path(uri);
665 return NULL;
669 /* Set the close-on-exec flag for this FD.
670 * TRUE means that an exec()'d process will not get the FD.
672 void close_on_exec(int fd, gboolean close)
674 if (fcntl(fd, F_SETFD, close))
675 g_warning("fcntl() failed: %s\n", g_strerror(errno));
678 void set_blocking(int fd, gboolean blocking)
680 if (fcntl(fd, F_SETFL, blocking ? 0 : O_NONBLOCK))
681 g_warning("fcntl() failed: %s\n", g_strerror(errno));
684 /* Format this time nicely. The result is a pointer to a static buffer,
685 * valid until the next call.
687 char *pretty_time(time_t *time)
689 static char time_buf[32];
691 if (strftime(time_buf, sizeof(time_buf),
692 TIME_FORMAT, localtime(time)) == 0)
693 time_buf[0]= 0;
695 return time_buf;
698 #ifndef O_NOFOLLOW
699 # define O_NOFOLLOW 0x0
700 #endif
702 #ifdef HAVE_LIBVFS
703 /* Copy data from 'read_fd' FD to 'write_fd' FD until EOF or error.
704 * Returns 0 on success, -1 on error (and sets errno).
706 static int copy_fd(int read_fd, int write_fd)
708 char buffer[4096];
709 int got;
711 while ((got = mc_read(read_fd, buffer, sizeof(buffer))) > 0)
713 int sent = 0;
715 while (sent < got)
717 int c;
719 c = mc_write(write_fd, buffer + sent, got - sent);
720 if (c < 0)
721 return -1;
722 sent += c;
726 return got;
728 #endif
730 /* 'from' and 'to' are complete pathnames of files (not dirs or symlinks).
731 * This spawns 'cp' to do the copy if lstat() succeeds, otherwise we
732 * do the copy manually using vfs.
734 * Returns an error string, or NULL on success. g_free() the result.
736 guchar *copy_file(guchar *from, guchar *to)
738 char *argv[] = {"cp", "-pRf", NULL, NULL, NULL};
740 #ifdef HAVE_LIBVFS
741 struct stat info;
743 if (lstat(from, &info))
745 int read_fd = -1;
746 int write_fd = -1;
747 int error = 0;
749 if (mc_lstat(from, &info))
750 return g_strdup(g_strerror(errno));
752 /* Regular lstat() can't find it, but mc_lstat() can,
753 * so try reading it with VFS.
756 if (!S_ISREG(info.st_mode))
757 goto err;
759 read_fd = mc_open(from, O_RDONLY);
760 if (read_fd == -1)
761 goto err;
762 write_fd = mc_open(to,
763 O_NOFOLLOW | O_WRONLY | O_CREAT | O_TRUNC,
764 info.st_mode & 0777);
765 if (write_fd == -1)
766 goto err;
768 if (copy_fd(read_fd, write_fd))
769 goto err;
771 /* (yes, the single | is right) */
772 if (mc_close(read_fd) | mc_close(write_fd))
773 return g_strdup(g_strerror(errno));
774 return NULL;
775 err:
776 error = errno;
777 if (read_fd != -1)
778 mc_close(read_fd);
779 if (write_fd != -1)
780 mc_close(write_fd);
781 return g_strdup(error ? g_strerror(error) : _("Copy error"));
783 #endif
785 argv[2] = from;
786 argv[3] = to;
788 return fork_exec_wait(argv);
791 /* 'word' has all special characters escaped so that it may be inserted
792 * into a shell command.
793 * Eg: 'My Dir?' becomes 'My\ Dir\?'. g_free() the result.
795 guchar *shell_escape(guchar *word)
797 GString *tmp;
798 guchar *retval;
800 tmp = g_string_new(NULL);
802 while (*word)
804 if (strchr(" ?*['\"$~\\|();!`&", *word))
805 g_string_append_c(tmp, '\\');
806 g_string_append_c(tmp, *word);
807 word++;
810 retval = tmp->str;
811 g_string_free(tmp, FALSE);
812 return retval;
815 /* TRUE iff `sub' is (or would be) an object inside the directory `parent',
816 * (or the two are the same item/directory).
817 * FALSE if parent doesn't exist.
819 gboolean is_sub_dir(char *sub, char *parent)
821 struct stat parent_info;
823 /* These were pathdup()s, but that follows symlinks!
824 * I guess they were here for a reason - something is probably
825 * broken now...
827 sub = g_strdup(sub);
829 if (mc_stat(parent, &parent_info))
830 return FALSE; /* Parent doesn't exist */
832 while (1)
834 char *slash;
835 struct stat info;
837 if (mc_stat(sub, &info) == 0)
839 if (info.st_dev == parent_info.st_dev &&
840 info.st_ino == parent_info.st_ino)
842 g_free(sub);
843 return TRUE;
847 slash = strrchr(sub, '/');
848 if (!slash)
849 break;
850 if (slash == sub)
852 if (sub[1])
853 sub[1] = '\0';
854 else
855 break;
857 else
858 *slash = '\0';
861 g_free(sub);
863 return FALSE;
866 /* True if the string 'list' contains 'item'.
867 * Eg ("close", "close, help") -> TRUE
869 gboolean in_list(guchar *item, guchar *list)
871 int len;
873 len = strlen(item);
875 while (*list)
877 if (strncmp(item, list, len) == 0 && !isalpha(list[len]))
878 return TRUE;
879 list = strchr(list, ',');
880 if (!list)
881 return FALSE;
882 while (isspace(*++list))
886 return FALSE;
889 /* Split a path into its components. Eg:
891 * /bob/fred -> ["bob", "fred"]
892 * ///a//b// -> ["a", "b"]
893 * / -> []
895 * The array and the strings in it must be freed after use.
897 GPtrArray *split_path(guchar *path)
899 GPtrArray *array;
900 guchar *slash;
902 g_return_val_if_fail(path != NULL, NULL);
904 array = g_ptr_array_new();
906 while (1)
908 while (path[0] == '/')
909 path++;
910 if (path[0] == '\0')
911 break;
913 slash = strchr(path, '/');
914 if (slash)
916 g_ptr_array_add(array, g_strndup(path, slash - path));
917 path = slash + 1;
918 continue;
920 g_ptr_array_add(array, g_strdup(path));
921 break;
924 return array;
927 /* Return the shortest path from 'from' to 'to'.
928 * Eg: get_relative_path("/a/b/c", "a/d/e") -> "../d/e"
930 guchar *get_relative_path(guchar *from, guchar *to)
932 GString *path;
933 guchar *retval;
934 GPtrArray *src, *dst;
935 int i, j;
937 src = split_path(from);
938 dst = split_path(to);
940 /* The last component of src doesn't matter... */
941 if (src->len)
943 g_free(src->pdata[src->len - 1]);
944 g_ptr_array_remove_index(src, src->len - 1);
947 /* Strip off common path elements... */
948 i = 0;
949 while (i < src->len && i < dst->len)
951 guchar *a = (guchar *) src->pdata[i];
952 guchar *b = (guchar *) dst->pdata[i];
954 if (strcmp(a, b) != 0)
955 break;
956 i++;
959 /* Go up one dir for each element remaining in src */
960 path = g_string_new(NULL);
961 for (j = i; j < src->len; j++)
962 g_string_append(path, "../");
964 /* Go down one dir for each element remaining in dst */
965 for (j = i; j < dst->len; j++)
967 g_string_append(path, (guchar *) dst->pdata[j]);
968 g_string_append_c(path, '/');
971 if (path->str[path->len - 1] == '/')
972 g_string_truncate(path, path->len - 1);
973 if (path->len == 0)
974 g_string_assign(path, ".");
976 /* Free the arrays */
977 for (i = 0; i < src->len; i++)
978 g_free(src->pdata[i]);
979 g_ptr_array_free(src, TRUE);
980 for (i = 0; i < dst->len; i++)
981 g_free(dst->pdata[i]);
982 g_ptr_array_free(dst, TRUE);
984 retval = path->str;
985 g_string_free(path, FALSE);
987 return retval;
991 * Interperet text as a boolean value. Return defvalue if we don't
992 * recognise it
994 int text_to_boolean(const char *text, int defvalue)
996 if(g_strcasecmp(text, "true")==0)
997 return TRUE;
998 else if(g_strcasecmp(text, "false")==0)
999 return FALSE;
1000 else if(g_strcasecmp(text, "yes")==0)
1001 return TRUE;
1002 else if(g_strcasecmp(text, "no")==0)
1003 return FALSE;
1004 else if(isdigit(text[0]))
1005 return !!atoi(text);
1007 return defvalue;
1010 void set_to_null(gpointer *data)
1012 *data = NULL;
1015 /* Return the pathname that this symlink points to.
1016 * NULL on error (not a symlink, path too long) and errno set.
1017 * g_free() the result.
1019 char *readlink_dup(char *source)
1021 char path[MAXPATHLEN + 1];
1022 int got;
1024 got = readlink(source, path, MAXPATHLEN);
1025 if (got < 0 || got > MAXPATHLEN)
1026 return NULL;
1028 return g_strndup(path, got);
1031 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
1033 * This code implements the MD5 message-digest algorithm.
1034 * The algorithm is due to Ron Rivest. The original code was
1035 * written by Colin Plumb in 1993, and put in the public domain.
1037 * Modified to use glib datatypes. Put under GPL to simplify
1038 * licensing for ROX-Filer. Taken from Debian's dpkg package.
1041 #define md5byte unsigned char
1043 typedef struct _MD5Context MD5Context;
1045 struct _MD5Context {
1046 guint32 buf[4];
1047 guint32 bytes[2];
1048 guint32 in[16];
1051 #if G_BYTE_ORDER == G_BIG_ENDIAN
1052 void byteSwap(guint32 *buf, unsigned words)
1054 md5byte *p = (md5byte *)buf;
1056 do {
1057 *buf++ = (guint32)((unsigned)p[3] << 8 | p[2]) << 16 |
1058 ((unsigned)p[1] << 8 | p[0]);
1059 p += 4;
1060 } while (--words);
1062 #else
1063 #define byteSwap(buf,words)
1064 #endif
1067 * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
1068 * initialization constants.
1070 static void MD5Init(MD5Context *ctx)
1072 ctx->buf[0] = 0x67452301;
1073 ctx->buf[1] = 0xefcdab89;
1074 ctx->buf[2] = 0x98badcfe;
1075 ctx->buf[3] = 0x10325476;
1077 ctx->bytes[0] = 0;
1078 ctx->bytes[1] = 0;
1082 * Update context to reflect the concatenation of another buffer full
1083 * of bytes.
1085 static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len)
1087 guint32 t;
1089 /* Update byte count */
1091 t = ctx->bytes[0];
1092 if ((ctx->bytes[0] = t + len) < t)
1093 ctx->bytes[1]++; /* Carry from low to high */
1095 t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
1096 if (t > len) {
1097 memcpy((md5byte *)ctx->in + 64 - t, buf, len);
1098 return;
1100 /* First chunk is an odd size */
1101 memcpy((md5byte *)ctx->in + 64 - t, buf, t);
1102 byteSwap(ctx->in, 16);
1103 MD5Transform(ctx->buf, ctx->in);
1104 buf += t;
1105 len -= t;
1107 /* Process data in 64-byte chunks */
1108 while (len >= 64) {
1109 memcpy(ctx->in, buf, 64);
1110 byteSwap(ctx->in, 16);
1111 MD5Transform(ctx->buf, ctx->in);
1112 buf += 64;
1113 len -= 64;
1116 /* Handle any remaining bytes of data. */
1117 memcpy(ctx->in, buf, len);
1121 * Final wrapup - pad to 64-byte boundary with the bit pattern
1122 * 1 0* (64-bit count of bits processed, MSB-first)
1123 * Returns the newly allocated string of the hash.
1125 static char *MD5Final(MD5Context *ctx)
1127 char *retval;
1128 int i;
1129 int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
1130 md5byte *p = (md5byte *)ctx->in + count;
1131 guint8 *bytes;
1133 /* Set the first char of padding to 0x80. There is always room. */
1134 *p++ = 0x80;
1136 /* Bytes of padding needed to make 56 bytes (-8..55) */
1137 count = 56 - 1 - count;
1139 if (count < 0) { /* Padding forces an extra block */
1140 memset(p, 0, count + 8);
1141 byteSwap(ctx->in, 16);
1142 MD5Transform(ctx->buf, ctx->in);
1143 p = (md5byte *)ctx->in;
1144 count = 56;
1146 memset(p, 0, count);
1147 byteSwap(ctx->in, 14);
1149 /* Append length in bits and transform */
1150 ctx->in[14] = ctx->bytes[0] << 3;
1151 ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
1152 MD5Transform(ctx->buf, ctx->in);
1154 byteSwap(ctx->buf, 4);
1156 retval = g_malloc(33);
1157 bytes = (guint8 *) ctx->buf;
1158 for (i = 0; i < 16; i++)
1159 sprintf(retval + (i * 2), "%02x", bytes[i]);
1160 retval[32] = '\0';
1162 return retval;
1165 # ifndef ASM_MD5
1167 /* The four core functions - F1 is optimized somewhat */
1169 /* #define F1(x, y, z) (x & y | ~x & z) */
1170 #define F1(x, y, z) (z ^ (x & (y ^ z)))
1171 #define F2(x, y, z) F1(z, x, y)
1172 #define F3(x, y, z) (x ^ y ^ z)
1173 #define F4(x, y, z) (y ^ (x | ~z))
1175 /* This is the central step in the MD5 algorithm. */
1176 #define MD5STEP(f,w,x,y,z,in,s) \
1177 (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
1180 * The core of the MD5 algorithm, this alters an existing MD5 hash to
1181 * reflect the addition of 16 longwords of new data. MD5Update blocks
1182 * the data and converts bytes into longwords for this routine.
1184 static void MD5Transform(guint32 buf[4], guint32 const in[16])
1186 register guint32 a, b, c, d;
1188 a = buf[0];
1189 b = buf[1];
1190 c = buf[2];
1191 d = buf[3];
1193 MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
1194 MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
1195 MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
1196 MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
1197 MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
1198 MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
1199 MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
1200 MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
1201 MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
1202 MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
1203 MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
1204 MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
1205 MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
1206 MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
1207 MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
1208 MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
1210 MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
1211 MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
1212 MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
1213 MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
1214 MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
1215 MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
1216 MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
1217 MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
1218 MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
1219 MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
1220 MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
1221 MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
1222 MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
1223 MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
1224 MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
1225 MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
1227 MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
1228 MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
1229 MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
1230 MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
1231 MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
1232 MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
1233 MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
1234 MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
1235 MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
1236 MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
1237 MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
1238 MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
1239 MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
1240 MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
1241 MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
1242 MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
1244 MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
1245 MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
1246 MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
1247 MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
1248 MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
1249 MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
1250 MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
1251 MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
1252 MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
1253 MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
1254 MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
1255 MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
1256 MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
1257 MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
1258 MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
1259 MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
1261 buf[0] += a;
1262 buf[1] += b;
1263 buf[2] += c;
1264 buf[3] += d;
1267 # endif /* ASM_MD5 */
1269 char *md5_hash(char *message)
1271 MD5Context ctx;
1273 MD5Init(&ctx);
1274 MD5Update(&ctx, message, strlen(message));
1275 return MD5Final(&ctx);
1277 #endif /* GTK2 or THUMBS_USE_LIBPNG */
1279 /* Removes trailing / chars and converts a leading '~/' (if any) to
1280 * the user's home dir. g_free() the result.
1282 gchar *icon_convert_path(gchar *path)
1284 guchar *retval;
1285 int path_len;
1287 g_return_val_if_fail(path != NULL, NULL);
1289 path_len = strlen(path);
1290 while (path_len > 1 && path[path_len - 1] == '/')
1291 path_len--;
1293 retval = g_strndup(path, path_len);
1295 if (path[0] == '~' && (path[1] == '\0' || path[1] == '/'))
1297 guchar *tmp = retval;
1299 retval = g_strconcat(home_dir, retval + 1, NULL);
1300 g_free(tmp);
1303 return retval;
1306 /* Convert string 'src' from the current locale to UTF-8.
1307 * If conversion fails, try again using iso-8859-1 -> UTF-8.
1309 gchar *to_utf8(gchar *src)
1311 gchar *retval;
1313 if (!src)
1314 return NULL;
1316 retval = g_locale_to_utf8(src, -1, NULL, NULL, NULL);
1317 if (retval)
1318 return retval;
1320 return g_convert(src, -1, "UTF-8", "iso-8859-1", NULL, NULL, NULL);
1323 /* Convert string 'src' to the current locale from UTF-8.
1324 * If conversion fails, try again using iso-8859-1 -> UTF-8.
1326 gchar *from_utf8(gchar *src)
1328 return src ? g_locale_from_utf8(src, -1, NULL, NULL, NULL) : NULL;
1331 /****************************************************************
1332 * INTERNAL FUNCTIONS *
1333 ****************************************************************/
1335 static XMLwrapper *xml_load(char *pathname, gpointer data)
1337 xmlDocPtr doc;
1338 XMLwrapper *xml_data = NULL;
1340 doc = xmlParseFile(pathname);
1341 if (!doc)
1342 return NULL; /* Bad XML */
1344 xml_data = g_new(XMLwrapper, 1);
1345 xml_data->ref = 1;
1346 xml_data->doc = doc;
1348 return xml_data;
1351 static void xml_ref(XMLwrapper *doc, gpointer data)
1353 if (doc)
1354 doc->ref++;
1357 static void xml_unref(XMLwrapper *doc, gpointer data)
1359 if (doc && --doc->ref == 0)
1361 xmlFreeDoc(doc->doc);
1362 g_free(doc);
1366 static int xml_getref(XMLwrapper *doc, gpointer data)
1368 return doc ? doc->ref : 0;