/*
* Copyright © 2016 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*
* Authors:
* Ted Gould
*/
#define _POSIX_C_SOURCE 200212L
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ENVVAR_SIZE 4096
#define SOCKETNAME_SIZE 256
#define ENVNAME_SIZE 64
void
sigchild_handler (int signal)
{
fprintf(stderr, "XMir has closed unexpectedly\n");
exit(EXIT_FAILURE);
}
struct sigaction sigchild_action = {
.sa_handler = sigchild_handler,
.sa_flags = SA_NOCLDWAIT
};
int
main (int argc, char * argv[])
{
if (argc < 3) {
fprintf(stderr, "%s: Usage: [appid] [command to execute...]\n", argv[0]);
return EXIT_FAILURE;
}
char * appid = argv[1];
/* Build Socket Name */
srand(time(NULL));
char socketname[SOCKETNAME_SIZE] = {0};
snprintf(socketname, sizeof(socketname), "/ual-socket-%08X-%s", rand(), appid);
/* Setup abstract socket */
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd <= 0) {
fprintf(stderr, "%s: Unable to create socket\n", argv[0]);
return EXIT_FAILURE;
}
struct sockaddr_un socketaddr = {0};
socketaddr.sun_family = AF_UNIX;
strncpy(socketaddr.sun_path, socketname, sizeof(socketaddr.sun_path) - 1);
socketaddr.sun_path[0] = 0;
if (bind(socketfd, (const struct sockaddr *)&socketaddr, sizeof(struct sockaddr_un)) < 0) {
fprintf(stderr, "%s: Unable to bind socket '%s'\n", argv[0], socketname);
return EXIT_FAILURE;
}
/* Fork and exec the x11 setup under it's confiment */
if (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) {
fprintf(stderr, "Unable to setup child signal handler\n");
return EXIT_FAILURE;
}
if (fork() == 0) {
/* XMir start here */
/* GOAL: /snap/bin/unity8-session.xmir-helper $appid libertine-launch /snap/unity8-session/current/usr/bin/snappy-xmir-envvars socketname */
char * snappyhelper = getenv("UBUNTU_APP_LAUNCH_SNAPPY_XMIR_HELPER");
if (snappyhelper == NULL) {
snappyhelper = "xmir-helper";
}
char * libertinelaunch = getenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH");
if (libertinelaunch == NULL) {
libertinelaunch = "libertine-launch";
}
/* envvar is like us, but a little more */
char envvars[256] = {0};
snprintf(envvars, sizeof(envvars), "%s-envvars", argv[0]);
char * xmirexec[6] = {
snappyhelper,
appid,
libertinelaunch,
envvars,
socketname,
NULL
};
printf("Executing xmir-helper on PID: %d\n", getpid());
fflush(stdout);
return execv(xmirexec[0], xmirexec);
}
listen(socketfd, 1); /* 1 is the number of people who can connect */
int readsocket = accept(socketfd, NULL, NULL);
if (getenv("G_MESSAGES_DEBUG") != NULL) {
printf("Got a socket connection on: %s\n", socketname);
}
/* Read our socket until we get all of the environment */
char readbuf[ENVVAR_SIZE] = {0};
int amountread = 0;
int thisread = 0;
while ((thisread = read(readsocket, readbuf + amountread, ENVVAR_SIZE - amountread)) > 0) {
amountread += thisread;
if (amountread == ENVVAR_SIZE) {
fprintf(stderr, "Environment is too large, abort!\n");
exit(EXIT_FAILURE);
}
}
if (thisread < 0) {
fprintf(stderr, "Error reading environment variables from Xmir utilities: %s", strerror(errno));
exit(EXIT_FAILURE);
}
close(readsocket);
close(socketfd);
/* Parse the environment into variables we can insert */
if (amountread > 0) {
char * startvar = readbuf;
int debug = (getenv("G_MESSAGES_DEBUG") != NULL);
do {
char * startval = startvar + strlen(startvar) + 1;
setenv(startvar, startval, 1);
if (debug) {
printf("Got env: %s=%s\n", startvar, startval);
}
startvar = startval + strlen(startval) + 1;
}
while (startvar < readbuf + amountread);
}
/* Clear MIR_* variables from the environment */
/* Unfortunately calling unsetenv resets the environ array so
it can become invalid. So we need to start over, though this
is fast */
int unset = 1;
while (unset) {
unset = 0;
unsigned int i;
for (i = 0; __environ[i] != NULL; i++) {
const char * env = __environ[i];
/* Not checking the length becasue imagining that block
size will always be larger than 4 bytes on 32-bit systems.
Compiler should fold this into one comparison. */
if (env[0] == 'M' && env[1] == 'I' && env[2] == 'R' && env[3] == '_') {
char envname[ENVNAME_SIZE] = {0};
unset = 1;
strncpy(envname, env, ENVNAME_SIZE - 1);
unsigned int j;
for (j = 0; j < ENVNAME_SIZE && envname[j] != '\0'; j++) {
if (envname[j] == '=') {
envname[j] = '\0';
if (unsetenv(envname) != 0) {
/* Shouldn't happen unless we're out of memory,
might as well bail now if that's the case. */
fprintf(stderr, "Unable to unset '%s' environment variable\n", envname);
exit(EXIT_FAILURE);
}
break;
}
}
break;
}
}
}
fflush(stdout);
/* Exec the application with the new environment under its confinement */
return execv(argv[2], &(argv[2]));
}