]> git.sur5r.net Git - i3/i3/commitdiff
Automatically call the migration script when the config does not look like v4
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 6 Jul 2011 18:40:46 +0000 (20:40 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 6 Jul 2011 18:43:00 +0000 (20:43 +0200)
i3.config
src/cfgparse.y

index 232dce41b140489a10314ccd907970e4ba54dafb..4c49e4bc1284d81f68effdb185907d916c8d90ac 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -1,3 +1,5 @@
+# i3 config file (v4)
+#
 # This configuration file was written for the NEO layout. If you are using a
 # different layout, you should change it.
 
index 2258268f82adf2f80e9a7ef3224255c89cd98db4..424388936807a7d9512ac8ead4f39b57693c4c82 100644 (file)
@@ -5,9 +5,11 @@
  */
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <libgen.h>
 
 #include "all.h"
 
@@ -46,6 +48,194 @@ int yywrap() {
     return 1;
 }
 
+/*
+ * Goes through each line of buf (separated by \n) and checks for statements /
+ * commands which only occur in i3 v4 configuration files. If it finds any, it
+ * returns version 4, otherwise it returns version 3.
+ *
+ */
+static int detect_version(char *buf) {
+    char *walk = buf;
+    char *line = buf;
+    while (*walk != '\0') {
+        if (*walk != '\n') {
+            walk++;
+            continue;
+        }
+
+        /* check for some v4-only statements */
+        if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
+            strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
+            strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
+            printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+            return 4;
+        }
+
+        /* if this is a bind statement, we can check the command */
+        if (strncasecmp(line, "bind", strlen("bind")) == 0) {
+            char *bind = strchr(line, ' ');
+            if (bind == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if ((bind = strchr(bind, ' ')) == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
+                strncasecmp(bind, "floating", strlen("floating")) == 0 ||
+                strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
+                strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
+                strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
+                strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
+                strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
+                strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
+                strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
+                strncasecmp(bind, "border borderless", strlen("border borderless")) == 0) {
+                printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+                return 4;
+            }
+        }
+
+next:
+        /* advance to the next line */
+        walk++;
+        line = walk;
+    }
+
+    return 3;
+}
+
+/*
+ * Calls i3-migrate-config-to-v4.pl to migrate a configuration file (input
+ * buffer).
+ *
+ * Returns the converted config file or NULL if there was an error (for
+ * example the script could not be found in $PATH or the i3 executable’s
+ * directory).
+ *
+ */
+static char *migrate_config(char *input, off_t size) {
+    int writepipe[2];
+    int readpipe[2];
+
+    if (pipe(writepipe) != 0 ||
+        pipe(readpipe) != 0) {
+        warn("migrate_config: Could not create pipes");
+        return NULL;
+    }
+
+    pid_t pid = fork();
+    if (pid == -1) {
+        warn("Could not fork()");
+        return NULL;
+    }
+
+    /* child */
+    if (pid == 0) {
+        /* close writing end of writepipe, connect reading side to stdin */
+        close(writepipe[1]);
+        dup2(writepipe[0], 0);
+
+        /* close reading end of readpipe, connect writing side to stdout */
+        close(readpipe[0]);
+        dup2(readpipe[1], 1);
+
+        /* start the migration script, search PATH first */
+        char *migratepath = "i3-migrate-config-to-v4.pl";
+        execlp(migratepath, migratepath, NULL);
+
+        /* if the script is not in path, maybe the user installed to a strange
+         * location and runs the i3 binary with an absolute path. We use
+         * argv[0]’s dirname */
+        char *pathbuf = strdup(start_argv[0]);
+        char *dir = dirname(pathbuf);
+        asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
+        execlp(migratepath, migratepath, NULL);
+
+#if defined(__linux__)
+        /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
+        char buffer[BUFSIZ];
+        if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
+            warn("could not read /proc/self/exe");
+            exit(1);
+        }
+        dir = dirname(buffer);
+        asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
+        execlp(migratepath, migratepath, NULL);
+#endif
+
+        warn("Could not start i3-migrate-config-to-v4.pl");
+        exit(2);
+    }
+
+    /* parent */
+
+    /* close reading end of the writepipe (connected to the script’s stdin) */
+    close(writepipe[0]);
+
+    /* write the whole config file to the pipe, the script will read everything
+     * immediately */
+    int written = 0;
+    int ret;
+    while (written < size) {
+        if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
+            warn("Could not write to pipe");
+            return NULL;
+        }
+        written += ret;
+    }
+    close(writepipe[1]);
+
+    /* close writing end of the readpipe (connected to the script’s stdout) */
+    close(readpipe[1]);
+
+    /* read the script’s output */
+    int conv_size = 65535;
+    char *converted = malloc(conv_size);
+    int read_bytes = 0;
+    do {
+        if (read_bytes == conv_size) {
+            conv_size += 65535;
+            converted = realloc(converted, conv_size);
+        }
+        ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
+        if (ret == -1) {
+            warn("Cannot read from pipe");
+            return NULL;
+        }
+        read_bytes += ret;
+    } while (ret > 0);
+
+    /* get the returncode */
+    int status;
+    wait(&status);
+    if (!WIFEXITED(status)) {
+        fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+        return NULL;
+    }
+
+    int returncode = WEXITSTATUS(status);
+    if (returncode != 0) {
+        fprintf(stderr, "Migration process exit code was != 0\n");
+        if (returncode == 2) {
+            fprintf(stderr, "could not start the migration script\n");
+            /* TODO: script was not found. tell the user to fix his system or create a v4 config */
+        } else if (returncode == 1) {
+            fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
+            fprintf(stderr, "# i3 config file (v4)\n");
+            /* TODO: nag the user with a message to include a hint for i3 in his config file */
+        }
+        return NULL;
+    }
+
+    return converted;
+}
+
 void parse_file(const char *f) {
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
     int fd, ret, read_bytes = 0;
@@ -160,6 +350,35 @@ void parse_file(const char *f) {
         }
     }
 
+    /* analyze the string to find out whether this is an old config file (3.x)
+     * or a new config file (4.x). If it’s old, we run the converter script. */
+    int version = detect_version(buf);
+    if (version == 3) {
+        /* We need to convert this v3 configuration */
+        char *converted = migrate_config(new, stbuf.st_size);
+        if (converted != NULL) {
+            printf("\n");
+            printf("****************************************************************\n");
+            printf("NOTE: Automatically converted configuration file from v3 to v4.\n");
+            printf("\n");
+            printf("Please convert your config file to v4. You can use this command:\n");
+            printf("    mv %s %s.O\n", f, f);
+            printf("    i3-migrate-config-to-v4.pl %s.O > %s\n", f, f);
+            printf("****************************************************************\n");
+            printf("\n");
+            free(new);
+            new = converted;
+        } else {
+            printf("\n");
+            printf("**********************************************************************\n");
+            printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4.pl\n");
+            printf("was not correctly installed on your system?\n");
+            printf("**********************************************************************\n");
+            printf("\n");
+        }
+    }
+
+    /* now lex/parse it */
     yy_scan_string(new);
 
     context = scalloc(sizeof(struct context));