]> git.sur5r.net Git - i3/i3/blob - src/util.c
explicitly set filenames to $(basename __FILE__)
[i3/i3] / src / util.c
1 #line 2 "util.c"
2 /*
3  * vim:ts=4:sw=4:expandtab
4  *
5  * i3 - an improved dynamic tiling window manager
6  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
7  *
8  * util.c: Utility functions, which can be useful everywhere within i3 (see
9  *         also libi3).
10  *
11  */
12 #include "all.h"
13
14 #include <sys/wait.h>
15 #include <stdarg.h>
16 #if defined(__OpenBSD__)
17 #include <sys/cdefs.h>
18 #endif
19 #include <fcntl.h>
20 #include <pwd.h>
21 #include <yajl/yajl_version.h>
22 #include <libgen.h>
23
24 #define SN_API_NOT_YET_FROZEN 1
25 #include <libsn/sn-launcher.h>
26
27 int min(int a, int b) {
28     return (a < b ? a : b);
29 }
30
31 int max(int a, int b) {
32     return (a > b ? a : b);
33 }
34
35 bool rect_contains(Rect rect, uint32_t x, uint32_t y) {
36     return (x >= rect.x &&
37             x <= (rect.x + rect.width) &&
38             y >= rect.y &&
39             y <= (rect.y + rect.height));
40 }
41
42 Rect rect_add(Rect a, Rect b) {
43     return (Rect){a.x + b.x,
44                   a.y + b.y,
45                   a.width + b.width,
46                   a.height + b.height};
47 }
48
49 /*
50  * Updates *destination with new_value and returns true if it was changed or false
51  * if it was the same
52  *
53  */
54 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
55     uint32_t old_value = *destination;
56
57     return ((*destination = new_value) != old_value);
58 }
59
60 /*
61  * exec()s an i3 utility, for example the config file migration script or
62  * i3-nagbar. This function first searches $PATH for the given utility named,
63  * then falls back to the dirname() of the i3 executable path and then falls
64  * back to the dirname() of the target of /proc/self/exe (on linux).
65  *
66  * This function should be called after fork()ing.
67  *
68  * The first argument of the given argv vector will be overwritten with the
69  * executable name, so pass NULL.
70  *
71  * If the utility cannot be found in any of these locations, it exits with
72  * return code 2.
73  *
74  */
75 void exec_i3_utility(char *name, char *argv[]) {
76     /* start the migration script, search PATH first */
77     char *migratepath = name;
78     argv[0] = migratepath;
79     execvp(migratepath, argv);
80
81     /* if the script is not in path, maybe the user installed to a strange
82      * location and runs the i3 binary with an absolute path. We use
83      * argv[0]’s dirname */
84     char *pathbuf = strdup(start_argv[0]);
85     char *dir = dirname(pathbuf);
86     sasprintf(&migratepath, "%s/%s", dir, name);
87     argv[0] = migratepath;
88     execvp(migratepath, argv);
89
90 #if defined(__linux__)
91     /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
92     char buffer[BUFSIZ];
93     if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
94         warn("could not read /proc/self/exe");
95         exit(1);
96     }
97     dir = dirname(buffer);
98     sasprintf(&migratepath, "%s/%s", dir, name);
99     argv[0] = migratepath;
100     execvp(migratepath, argv);
101 #endif
102
103     warn("Could not start %s", name);
104     exit(2);
105 }
106
107 /*
108  * Checks a generic cookie for errors and quits with the given message if there
109  * was an error.
110  *
111  */
112 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
113     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
114     if (error != NULL) {
115         fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
116         xcb_disconnect(conn);
117         exit(-1);
118     }
119 }
120
121 /*
122  * This function resolves ~ in pathnames.
123  * It may resolve wildcards in the first part of the path, but if no match
124  * or multiple matches are found, it just returns a copy of path as given.
125  *
126  */
127 char *resolve_tilde(const char *path) {
128         static glob_t globbuf;
129         char *head, *tail, *result;
130
131         tail = strchr(path, '/');
132         head = strndup(path, tail ? tail - path : strlen(path));
133
134         int res = glob(head, GLOB_TILDE, NULL, &globbuf);
135         free(head);
136         /* no match, or many wildcard matches are bad */
137         if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
138                 result = sstrdup(path);
139         else if (res != 0) {
140                 die("glob() failed");
141         } else {
142                 head = globbuf.gl_pathv[0];
143                 result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
144                 strncpy(result, head, strlen(head));
145                 if (tail)
146                     strncat(result, tail, strlen(tail));
147         }
148         globfree(&globbuf);
149
150         return result;
151 }
152
153 /*
154  * Checks if the given path exists by calling stat().
155  *
156  */
157 bool path_exists(const char *path) {
158         struct stat buf;
159         return (stat(path, &buf) == 0);
160 }
161
162 /*
163  * Goes through the list of arguments (for exec()) and checks if the given argument
164  * is present. If not, it copies the arguments (because we cannot realloc it) and
165  * appends the given argument.
166  *
167  */
168 static char **append_argument(char **original, char *argument) {
169     int num_args;
170     for (num_args = 0; original[num_args] != NULL; num_args++) {
171         DLOG("original argument: \"%s\"\n", original[num_args]);
172         /* If the argument is already present we return the original pointer */
173         if (strcmp(original[num_args], argument) == 0)
174             return original;
175     }
176     /* Copy the original array */
177     char **result = smalloc((num_args+2) * sizeof(char*));
178     memcpy(result, original, num_args * sizeof(char*));
179     result[num_args] = argument;
180     result[num_args+1] = NULL;
181
182     return result;
183 }
184
185 /*
186  * Returns the name of a temporary file with the specified prefix.
187  *
188  */
189 char *get_process_filename(const char *prefix) {
190     /* dir stores the directory path for this and all subsequent calls so that
191      * we only create a temporary directory once per i3 instance. */
192     static char *dir = NULL;
193     if (dir == NULL) {
194         /* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
195         if ((dir = getenv("XDG_RUNTIME_DIR"))) {
196             char *tmp;
197             sasprintf(&tmp, "%s/i3", dir);
198             dir = tmp;
199             if (!path_exists(dir)) {
200                 if (mkdir(dir, 0700) == -1) {
201                     perror("mkdir()");
202                     return NULL;
203                 }
204             }
205         } else {
206             /* If not, we create a (secure) temp directory using the template
207              * /tmp/i3-<user>.XXXXXX */
208             struct passwd *pw = getpwuid(getuid());
209             const char *username = pw ? pw->pw_name : "unknown";
210             sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
211             /* mkdtemp modifies dir */
212             if (mkdtemp(dir) == NULL) {
213                 perror("mkdtemp()");
214                 return NULL;
215             }
216         }
217     }
218     char *filename;
219     sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
220     return filename;
221 }
222
223 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
224 #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
225
226 char *store_restart_layout(void) {
227     setlocale(LC_NUMERIC, "C");
228 #if YAJL_MAJOR >= 2
229     yajl_gen gen = yajl_gen_alloc(NULL);
230 #else
231     yajl_gen gen = yajl_gen_alloc(NULL, NULL);
232 #endif
233
234     dump_node(gen, croot, true);
235
236     setlocale(LC_NUMERIC, "");
237
238     const unsigned char *payload;
239 #if YAJL_MAJOR >= 2
240     size_t length;
241 #else
242     unsigned int length;
243 #endif
244     y(get_buf, &payload, &length);
245
246     /* create a temporary file if one hasn't been specified, or just
247      * resolve the tildes in the specified path */
248     char *filename;
249     if (config.restart_state_path == NULL) {
250         filename = get_process_filename("restart-state");
251         if (!filename)
252             return NULL;
253     } else {
254         filename = resolve_tilde(config.restart_state_path);
255     }
256
257     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
258     if (fd == -1) {
259         perror("open()");
260         free(filename);
261         return NULL;
262     }
263
264     int written = 0;
265     while (written < length) {
266         int n = write(fd, payload + written, length - written);
267         /* TODO: correct error-handling */
268         if (n == -1) {
269             perror("write()");
270             free(filename);
271             close(fd);
272             return NULL;
273         }
274         if (n == 0) {
275             printf("write == 0?\n");
276             free(filename);
277             close(fd);
278             return NULL;
279         }
280         written += n;
281 #if YAJL_MAJOR >= 2
282         printf("written: %d of %zd\n", written, length);
283 #else
284         printf("written: %d of %d\n", written, length);
285 #endif
286     }
287     close(fd);
288
289     if (length > 0) {
290         printf("layout: %.*s\n", (int)length, payload);
291     }
292
293     y(free);
294
295     return filename;
296 }
297
298 /*
299  * Restart i3 in-place
300  * appends -a to argument list to disable autostart
301  *
302  */
303 void i3_restart(bool forget_layout) {
304     char *restart_filename = forget_layout ? NULL : store_restart_layout();
305
306     kill_configerror_nagbar(true);
307     kill_commanderror_nagbar(true);
308
309     restore_geometry();
310
311     ipc_shutdown();
312
313     LOG("restarting \"%s\"...\n", start_argv[0]);
314     /* make sure -a is in the argument list or append it */
315     start_argv = append_argument(start_argv, "-a");
316
317     /* replace -r <file> so that the layout is restored */
318     if (restart_filename != NULL) {
319         /* create the new argv */
320         int num_args;
321         for (num_args = 0; start_argv[num_args] != NULL; num_args++);
322         char **new_argv = scalloc((num_args + 3) * sizeof(char*));
323
324         /* copy the arguments, but skip the ones we'll replace */
325         int write_index = 0;
326         bool skip_next = false;
327         for (int i = 0; i < num_args; ++i) {
328             if (skip_next)
329                 skip_next = false;
330             else if (!strcmp(start_argv[i], "-r") ||
331                      !strcmp(start_argv[i], "--restart"))
332                 skip_next = true;
333             else
334                 new_argv[write_index++] = start_argv[i];
335         }
336
337         /* add the arguments we'll replace */
338         new_argv[write_index++] = "--restart";
339         new_argv[write_index] = restart_filename;
340
341         /* swap the argvs */
342         start_argv = new_argv;
343     }
344
345     execvp(start_argv[0], start_argv);
346     /* not reached */
347 }
348
349 #if defined(__OpenBSD__) || defined(__APPLE__)
350
351 /*
352  * Taken from FreeBSD
353  * Find the first occurrence of the byte string s in byte string l.
354  *
355  */
356 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
357     register char *cur, *last;
358     const char *cl = (const char *)l;
359     const char *cs = (const char *)s;
360
361     /* we need something to compare */
362     if (l_len == 0 || s_len == 0)
363         return NULL;
364
365     /* "s" must be smaller or equal to "l" */
366     if (l_len < s_len)
367         return NULL;
368
369     /* special case where s_len == 1 */
370     if (s_len == 1)
371         return memchr(l, (int)*cs, l_len);
372
373     /* the last position where its possible to find "s" in "l" */
374     last = (char *)cl + l_len - s_len;
375
376     for (cur = (char *)cl; cur <= last; cur++)
377         if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
378             return cur;
379
380     return NULL;
381 }
382
383 #endif