Automatically call the migration script when the config does not look like v4

This commit is contained in:
Michael Stapelberg 2011-07-06 20:40:46 +02:00
parent 3e24b7170f
commit ac335fcffa
2 changed files with 221 additions and 0 deletions

View File

@ -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.

View 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 executables
* 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 scripts 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 scripts stdout) */
close(readpipe[1]);
/* read the scripts 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 its 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));