i3
src/util.c
Go to the documentation of this file.
00001 /*
00002  * vim:ts=4:sw=4:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  *
00006  * © 2009-2011 Michael Stapelberg and contributors
00007  *
00008  * See file LICENSE for license information.
00009  *
00010  * util.c: Utility functions, which can be useful everywhere.
00011  *
00012  */
00013 #include <sys/wait.h>
00014 #include <stdarg.h>
00015 #include <iconv.h>
00016 #if defined(__OpenBSD__)
00017 #include <sys/cdefs.h>
00018 #endif
00019 #include <fcntl.h>
00020 #include <pwd.h>
00021 #include <yajl/yajl_version.h>
00022 #include <libgen.h>
00023 
00024 #include "all.h"
00025 
00026 static iconv_t conversion_descriptor = 0;
00027 
00028 int min(int a, int b) {
00029     return (a < b ? a : b);
00030 }
00031 
00032 int max(int a, int b) {
00033     return (a > b ? a : b);
00034 }
00035 
00036 bool rect_contains(Rect rect, uint32_t x, uint32_t y) {
00037     return (x >= rect.x &&
00038             x <= (rect.x + rect.width) &&
00039             y >= rect.y &&
00040             y <= (rect.y + rect.height));
00041 }
00042 
00043 Rect rect_add(Rect a, Rect b) {
00044     return (Rect){a.x + b.x,
00045                   a.y + b.y,
00046                   a.width + b.width,
00047                   a.height + b.height};
00048 }
00049 
00050 /*
00051  * Updates *destination with new_value and returns true if it was changed or false
00052  * if it was the same
00053  *
00054  */
00055 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
00056     uint32_t old_value = *destination;
00057 
00058     return ((*destination = new_value) != old_value);
00059 }
00060 
00061 /*
00062  * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
00063  * the called functions returns NULL, meaning that there is no more memory available
00064  *
00065  */
00066 void *smalloc(size_t size) {
00067     void *result = malloc(size);
00068     exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
00069     return result;
00070 }
00071 
00072 void *scalloc(size_t size) {
00073     void *result = calloc(size, 1);
00074     exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
00075     return result;
00076 }
00077 
00078 void *srealloc(void *ptr, size_t size) {
00079     void *result = realloc(ptr, size);
00080     exit_if_null(result, "Error: out memory (realloc(%zd))\n", size);
00081     return result;
00082 }
00083 
00084 char *sstrdup(const char *str) {
00085     char *result = strdup(str);
00086     exit_if_null(result, "Error: out of memory (strdup())\n");
00087     return result;
00088 }
00089 
00090 /*
00091  * Starts the given application by passing it through a shell. We use double fork
00092  * to avoid zombie processes. As the started application’s parent exits (immediately),
00093  * the application is reparented to init (process-id 1), which correctly handles
00094  * childs, so we don’t have to do it :-).
00095  *
00096  * The shell is determined by looking for the SHELL environment variable. If it
00097  * does not exist, /bin/sh is used.
00098  *
00099  */
00100 void start_application(const char *command) {
00101     LOG("executing: %s\n", command);
00102     if (fork() == 0) {
00103         /* Child process */
00104         setsid();
00105         if (fork() == 0) {
00106             /* Stores the path of the shell */
00107             static const char *shell = NULL;
00108 
00109             if (shell == NULL)
00110                 if ((shell = getenv("SHELL")) == NULL)
00111                     shell = "/bin/sh";
00112 
00113             /* This is the child */
00114             execl(shell, shell, "-c", command, (void*)NULL);
00115             /* not reached */
00116         }
00117         exit(0);
00118     }
00119     wait(0);
00120 }
00121 
00122 /*
00123  * exec()s an i3 utility, for example the config file migration script or
00124  * i3-nagbar. This function first searches $PATH for the given utility named,
00125  * then falls back to the dirname() of the i3 executable path and then falls
00126  * back to the dirname() of the target of /proc/self/exe (on linux).
00127  *
00128  * This function should be called after fork()ing.
00129  *
00130  * The first argument of the given argv vector will be overwritten with the
00131  * executable name, so pass NULL.
00132  *
00133  * If the utility cannot be found in any of these locations, it exits with
00134  * return code 2.
00135  *
00136  */
00137 void exec_i3_utility(char *name, char *argv[]) {
00138     /* start the migration script, search PATH first */
00139     char *migratepath = name;
00140     argv[0] = migratepath;
00141     execvp(migratepath, argv);
00142 
00143     /* if the script is not in path, maybe the user installed to a strange
00144      * location and runs the i3 binary with an absolute path. We use
00145      * argv[0]’s dirname */
00146     char *pathbuf = strdup(start_argv[0]);
00147     char *dir = dirname(pathbuf);
00148     asprintf(&migratepath, "%s/%s", dir, name);
00149     argv[0] = migratepath;
00150     execvp(migratepath, argv);
00151 
00152 #if defined(__linux__)
00153     /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
00154     char buffer[BUFSIZ];
00155     if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
00156         warn("could not read /proc/self/exe");
00157         exit(1);
00158     }
00159     dir = dirname(buffer);
00160     asprintf(&migratepath, "%s/%s", dir, name);
00161     argv[0] = migratepath;
00162     execvp(migratepath, argv);
00163 #endif
00164 
00165     warn("Could not start %s", name);
00166     exit(2);
00167 }
00168 
00169 /*
00170  * Checks a generic cookie for errors and quits with the given message if there
00171  * was an error.
00172  *
00173  */
00174 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
00175     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
00176     if (error != NULL) {
00177         fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
00178         xcb_disconnect(conn);
00179         exit(-1);
00180     }
00181 }
00182 
00183 /*
00184  * Converts the given string to UCS-2 big endian for use with
00185  * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
00186  * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
00187  * returned. It has to be freed when done.
00188  *
00189  */
00190 char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
00191     size_t input_size = strlen(input) + 1;
00192     /* UCS-2 consumes exactly two bytes for each glyph */
00193     int buffer_size = input_size * 2;
00194 
00195     char *buffer = smalloc(buffer_size);
00196     size_t output_size = buffer_size;
00197     /* We need to use an additional pointer, because iconv() modifies it */
00198     char *output = buffer;
00199 
00200     /* We convert the input into UCS-2 big endian */
00201     if (conversion_descriptor == 0) {
00202         conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
00203         if (conversion_descriptor == 0) {
00204             fprintf(stderr, "error opening the conversion context\n");
00205             exit(1);
00206         }
00207     }
00208 
00209     /* Get the conversion descriptor back to original state */
00210     iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
00211 
00212     /* Convert our text */
00213     int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
00214     if (rc == (size_t)-1) {
00215         perror("Converting to UCS-2 failed");
00216         if (real_strlen != NULL)
00217             *real_strlen = 0;
00218         return NULL;
00219     }
00220 
00221     if (real_strlen != NULL)
00222         *real_strlen = ((buffer_size - output_size) / 2) - 1;
00223 
00224     return buffer;
00225 }
00226 
00227 /*
00228  * This function resolves ~ in pathnames.
00229  * It may resolve wildcards in the first part of the path, but if no match
00230  * or multiple matches are found, it just returns a copy of path as given.
00231  *
00232  */
00233 char *resolve_tilde(const char *path) {
00234         static glob_t globbuf;
00235         char *head, *tail, *result;
00236 
00237         tail = strchr(path, '/');
00238         head = strndup(path, tail ? tail - path : strlen(path));
00239 
00240         int res = glob(head, GLOB_TILDE, NULL, &globbuf);
00241         free(head);
00242         /* no match, or many wildcard matches are bad */
00243         if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
00244                 result = sstrdup(path);
00245         else if (res != 0) {
00246                 die("glob() failed");
00247         } else {
00248                 head = globbuf.gl_pathv[0];
00249                 result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
00250                 strncpy(result, head, strlen(head));
00251                 if (tail)
00252                     strncat(result, tail, strlen(tail));
00253         }
00254         globfree(&globbuf);
00255 
00256         return result;
00257 }
00258 
00259 /*
00260  * Checks if the given path exists by calling stat().
00261  *
00262  */
00263 bool path_exists(const char *path) {
00264         struct stat buf;
00265         return (stat(path, &buf) == 0);
00266 }
00267 
00268 /*
00269  * Goes through the list of arguments (for exec()) and checks if the given argument
00270  * is present. If not, it copies the arguments (because we cannot realloc it) and
00271  * appends the given argument.
00272  *
00273  */
00274 static char **append_argument(char **original, char *argument) {
00275     int num_args;
00276     for (num_args = 0; original[num_args] != NULL; num_args++) {
00277         DLOG("original argument: \"%s\"\n", original[num_args]);
00278         /* If the argument is already present we return the original pointer */
00279         if (strcmp(original[num_args], argument) == 0)
00280             return original;
00281     }
00282     /* Copy the original array */
00283     char **result = smalloc((num_args+2) * sizeof(char*));
00284     memcpy(result, original, num_args * sizeof(char*));
00285     result[num_args] = argument;
00286     result[num_args+1] = NULL;
00287 
00288     return result;
00289 }
00290 
00291 /*
00292  * Returns the name of a temporary file with the specified prefix.
00293  *
00294  */
00295 char *get_process_filename(const char *prefix) {
00296     char *dir = getenv("XDG_RUNTIME_DIR");
00297     if (dir == NULL) {
00298         struct passwd *pw = getpwuid(getuid());
00299         const char *username = pw ? pw->pw_name : "unknown";
00300         if (asprintf(&dir, "/tmp/i3-%s", username) == -1) {
00301             perror("asprintf()");
00302             return NULL;
00303         }
00304     } else {
00305         char *tmp;
00306         if (asprintf(&tmp, "%s/i3", dir) == -1) {
00307             perror("asprintf()");
00308             return NULL;
00309         }
00310         dir = tmp;
00311     }
00312     if (!path_exists(dir)) {
00313         if (mkdir(dir, 0700) == -1) {
00314             perror("mkdir()");
00315             return NULL;
00316         }
00317     }
00318     char *filename;
00319     if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) {
00320         perror("asprintf()");
00321         filename = NULL;
00322     }
00323 
00324     free(dir);
00325     return filename;
00326 }
00327 
00328 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
00329 #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
00330 
00331 char *store_restart_layout() {
00332     setlocale(LC_NUMERIC, "C");
00333 #if YAJL_MAJOR >= 2
00334     yajl_gen gen = yajl_gen_alloc(NULL);
00335 #else
00336     yajl_gen gen = yajl_gen_alloc(NULL, NULL);
00337 #endif
00338 
00339     dump_node(gen, croot, true);
00340 
00341     setlocale(LC_NUMERIC, "");
00342 
00343     const unsigned char *payload;
00344 #if YAJL_MAJOR >= 2
00345     size_t length;
00346 #else
00347     unsigned int length;
00348 #endif
00349     y(get_buf, &payload, &length);
00350 
00351     /* create a temporary file if one hasn't been specified, or just
00352      * resolve the tildes in the specified path */
00353     char *filename;
00354     if (config.restart_state_path == NULL) {
00355         filename = get_process_filename("restart-state");
00356         if (!filename)
00357             return NULL;
00358     } else {
00359         filename = resolve_tilde(config.restart_state_path);
00360     }
00361 
00362     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
00363     if (fd == -1) {
00364         perror("open()");
00365         free(filename);
00366         return NULL;
00367     }
00368 
00369     int written = 0;
00370     while (written < length) {
00371         int n = write(fd, payload + written, length - written);
00372         /* TODO: correct error-handling */
00373         if (n == -1) {
00374             perror("write()");
00375             free(filename);
00376             return NULL;
00377         }
00378         if (n == 0) {
00379             printf("write == 0?\n");
00380             free(filename);
00381             return NULL;
00382         }
00383         written += n;
00384 #if YAJL_MAJOR >= 2
00385         printf("written: %d of %zd\n", written, length);
00386 #else
00387         printf("written: %d of %d\n", written, length);
00388 #endif
00389     }
00390     close(fd);
00391 
00392     if (length > 0) {
00393         printf("layout: %.*s\n", (int)length, payload);
00394     }
00395 
00396     y(free);
00397 
00398     return filename;
00399 }
00400 
00401 /*
00402  * Restart i3 in-place
00403  * appends -a to argument list to disable autostart
00404  *
00405  */
00406 void i3_restart(bool forget_layout) {
00407     char *restart_filename = forget_layout ? NULL : store_restart_layout();
00408 
00409     kill_configerror_nagbar(true);
00410 
00411     restore_geometry();
00412 
00413     ipc_shutdown();
00414 
00415     LOG("restarting \"%s\"...\n", start_argv[0]);
00416     /* make sure -a is in the argument list or append it */
00417     start_argv = append_argument(start_argv, "-a");
00418 
00419     /* replace -r <file> so that the layout is restored */
00420     if (restart_filename != NULL) {
00421         /* create the new argv */
00422         int num_args;
00423         for (num_args = 0; start_argv[num_args] != NULL; num_args++);
00424         char **new_argv = scalloc((num_args + 3) * sizeof(char*));
00425 
00426         /* copy the arguments, but skip the ones we'll replace */
00427         int write_index = 0;
00428         bool skip_next = false;
00429         for (int i = 0; i < num_args; ++i) {
00430             if (skip_next)
00431                 skip_next = false;
00432             else if (!strcmp(start_argv[i], "-r") ||
00433                      !strcmp(start_argv[i], "--restart"))
00434                 skip_next = true;
00435             else
00436                 new_argv[write_index++] = start_argv[i];
00437         }
00438 
00439         /* add the arguments we'll replace */
00440         new_argv[write_index++] = "--restart";
00441         new_argv[write_index] = restart_filename;
00442 
00443         /* swap the argvs */
00444         start_argv = new_argv;
00445     }
00446 
00447     execvp(start_argv[0], start_argv);
00448     /* not reached */
00449 }
00450 
00451 #if defined(__OpenBSD__) || defined(__APPLE__)
00452 
00453 /*
00454  * Taken from FreeBSD
00455  * Find the first occurrence of the byte string s in byte string l.
00456  *
00457  */
00458 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
00459     register char *cur, *last;
00460     const char *cl = (const char *)l;
00461     const char *cs = (const char *)s;
00462 
00463     /* we need something to compare */
00464     if (l_len == 0 || s_len == 0)
00465         return NULL;
00466 
00467     /* "s" must be smaller or equal to "l" */
00468     if (l_len < s_len)
00469         return NULL;
00470 
00471     /* special case where s_len == 1 */
00472     if (s_len == 1)
00473         return memchr(l, (int)*cs, l_len);
00474 
00475     /* the last position where its possible to find "s" in "l" */
00476     last = (char *)cl + l_len - s_len;
00477 
00478     for (cur = (char *)cl; cur <= last; cur++)
00479         if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
00480             return cur;
00481 
00482     return NULL;
00483 }
00484 
00485 #endif
00486 
00487 #if defined(__APPLE__)
00488 
00489 /*
00490  * Taken from FreeBSD
00491  * Returns a pointer to a new string which is a duplicate of the
00492  * string, but only copies at most n characters.
00493  *
00494  */
00495 char *strndup(const char *str, size_t n) {
00496     size_t len;
00497     char *copy;
00498 
00499     for (len = 0; len < n && str[len]; len++)
00500         continue;
00501 
00502     if ((copy = malloc(len + 1)) == NULL)
00503         return (NULL);
00504     memcpy(copy, str, len);
00505     copy[len] = '\0';
00506     return (copy);
00507 }
00508 
00509 #endif