1 #define _POSIX_C_SOURCE 200809L
2 #include <getopt.h>
3 #include <pango/pangocairo.h>
4 #include <signal.h>
5 #include <stdbool.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 #include <sys/un.h>
13 #include <unistd.h>
14 #include <wlr/util/log.h>
15 #include "sway/commands.h"
16 #include "sway/config.h"
17 #include "sway/server.h"
18 #include "sway/swaynag.h"
19 #include "sway/desktop/transaction.h"
20 #include "sway/tree/root.h"
21 #include "sway/ipc-server.h"
22 #include "ipc-client.h"
23 #include "log.h"
24 #include "stringop.h"
25 #include "util.h"
26 
27 static bool terminate_request = false;
28 static int exit_value = 0;
29 struct sway_server server = {0};
30 struct sway_debug debug = {0};
31 
sway_terminate(int exit_code)32 void sway_terminate(int exit_code) {
33 	if (!server.wl_display) {
34 		// Running as IPC client
35 		exit(exit_code);
36 	} else {
37 		// Running as server
38 		terminate_request = true;
39 		exit_value = exit_code;
40 		ipc_event_shutdown("exit");
41 		wl_display_terminate(server.wl_display);
42 	}
43 }
44 
sig_handler(int signal)45 void sig_handler(int signal) {
46 	sway_terminate(EXIT_SUCCESS);
47 }
48 
detect_raspi(void)49 void detect_raspi(void) {
50 	bool raspi = false;
51 	FILE *f = fopen("/sys/firmware/devicetree/base/model", "r");
52 	if (!f) {
53 		return;
54 	}
55 	char *line = NULL;
56 	size_t line_size = 0;
57 	while (getline(&line, &line_size, f) != -1) {
58 		if (strstr(line, "Raspberry Pi")) {
59 			raspi = true;
60 			break;
61 		}
62 	}
63 	fclose(f);
64 	FILE *g = fopen("/proc/modules", "r");
65 	if (!g) {
66 		free(line);
67 		return;
68 	}
69 	bool vc4 = false;
70 	while (getline(&line, &line_size, g) != -1) {
71 		if (strstr(line, "vc4")) {
72 			vc4 = true;
73 			break;
74 		}
75 	}
76 	free(line);
77 	fclose(g);
78 	if (!vc4 && raspi) {
79 		fprintf(stderr, "\x1B[1;31mWarning: You have a "
80 				"Raspberry Pi, but the vc4 Module is "
81 				"not loaded! Set 'dtoverlay=vc4-kms-v3d'"
82 				"in /boot/config.txt and reboot.\x1B[0m\n");
83 	}
84 }
85 
detect_proprietary(int allow_unsupported_gpu)86 void detect_proprietary(int allow_unsupported_gpu) {
87 	FILE *f = fopen("/proc/modules", "r");
88 	if (!f) {
89 		return;
90 	}
91 	char *line = NULL;
92 	size_t line_size = 0;
93 	while (getline(&line, &line_size, f) != -1) {
94 		if (strncmp(line, "nvidia ", 7) == 0) {
95 			if (allow_unsupported_gpu) {
96 				sway_log(SWAY_ERROR,
97 						"!!! Proprietary Nvidia drivers are in use !!!");
98 			} else {
99 				sway_log(SWAY_ERROR,
100 					"Proprietary Nvidia drivers are NOT supported. "
101 					"Use Nouveau. To launch sway anyway, launch with "
102 					"--my-next-gpu-wont-be-nvidia and DO NOT report issues.");
103 				exit(EXIT_FAILURE);
104 			}
105 			break;
106 		}
107 		if (strstr(line, "fglrx")) {
108 			if (allow_unsupported_gpu) {
109 				sway_log(SWAY_ERROR,
110 						"!!! Proprietary AMD drivers are in use !!!");
111 			} else {
112 				sway_log(SWAY_ERROR, "Proprietary AMD drivers do NOT support "
113 					"Wayland. Use radeon. To try anyway, launch sway with "
114 					"--unsupported-gpu and DO NOT report issues.");
115 				exit(EXIT_FAILURE);
116 			}
117 			break;
118 		}
119 	}
120 	free(line);
121 	fclose(f);
122 }
123 
run_as_ipc_client(char * command,char * socket_path)124 void run_as_ipc_client(char *command, char *socket_path) {
125 	int socketfd = ipc_open_socket(socket_path);
126 	uint32_t len = strlen(command);
127 	char *resp = ipc_single_command(socketfd, IPC_COMMAND, command, &len);
128 	printf("%s\n", resp);
129 	free(resp);
130 	close(socketfd);
131 }
132 
log_env(void)133 static void log_env(void) {
134 	const char *log_vars[] = {
135 		"LD_LIBRARY_PATH",
136 		"LD_PRELOAD",
137 		"PATH",
138 		"SWAYSOCK",
139 	};
140 	for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) {
141 		sway_log(SWAY_INFO, "%s=%s", log_vars[i], getenv(log_vars[i]));
142 	}
143 }
144 
log_file(FILE * f)145 static void log_file(FILE *f) {
146 	char *line = NULL;
147 	size_t line_size = 0;
148 	ssize_t nread;
149 	while ((nread = getline(&line, &line_size, f)) != -1) {
150 		if (line[nread - 1] == '\n') {
151 			line[nread - 1] = '\0';
152 		}
153 		sway_log(SWAY_INFO, "%s", line);
154 	}
155 	free(line);
156 }
157 
log_distro(void)158 static void log_distro(void) {
159 	const char *paths[] = {
160 		"/etc/lsb-release",
161 		"/etc/os-release",
162 		"/etc/debian_version",
163 		"/etc/redhat-release",
164 		"/etc/gentoo-release",
165 	};
166 	for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) {
167 		FILE *f = fopen(paths[i], "r");
168 		if (f) {
169 			sway_log(SWAY_INFO, "Contents of %s:", paths[i]);
170 			log_file(f);
171 			fclose(f);
172 		}
173 	}
174 }
175 
log_kernel(void)176 static void log_kernel(void) {
177 	FILE *f = popen("uname -a", "r");
178 	if (!f) {
179 		sway_log(SWAY_INFO, "Unable to determine kernel version");
180 		return;
181 	}
182 	log_file(f);
183 	pclose(f);
184 }
185 
186 
drop_permissions(void)187 static bool drop_permissions(void) {
188 	if (getuid() != geteuid() || getgid() != getegid()) {
189 		// Set the gid and uid in the correct order.
190 		if (setgid(getgid()) != 0) {
191 			sway_log(SWAY_ERROR, "Unable to drop root group, refusing to start");
192 			return false;
193 		}
194 		if (setuid(getuid()) != 0) {
195 			sway_log(SWAY_ERROR, "Unable to drop root user, refusing to start");
196 			return false;
197 		}
198 	}
199 	if (setgid(0) != -1 || setuid(0) != -1) {
200 		sway_log(SWAY_ERROR, "Unable to drop root (we shouldn't be able to "
201 			"restore it after setuid), refusing to start");
202 		return false;
203 	}
204 	return true;
205 }
206 
enable_debug_flag(const char * flag)207 void enable_debug_flag(const char *flag) {
208 	if (strcmp(flag, "damage=highlight") == 0) {
209 		debug.damage = DAMAGE_HIGHLIGHT;
210 	} else if (strcmp(flag, "damage=rerender") == 0) {
211 		debug.damage = DAMAGE_RERENDER;
212 	} else if (strcmp(flag, "noatomic") == 0) {
213 		debug.noatomic = true;
214 	} else if (strcmp(flag, "txn-wait") == 0) {
215 		debug.txn_wait = true;
216 	} else if (strcmp(flag, "txn-timings") == 0) {
217 		debug.txn_timings = true;
218 	} else if (strncmp(flag, "txn-timeout=", 12) == 0) {
219 		server.txn_timeout_ms = atoi(&flag[12]);
220 	} else {
221 		sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag);
222 	}
223 }
224 
main(int argc,char ** argv)225 int main(int argc, char **argv) {
226 	static int verbose = 0, debug = 0, validate = 0, allow_unsupported_gpu = 0;
227 
228 	static struct option long_options[] = {
229 		{"help", no_argument, NULL, 'h'},
230 		{"config", required_argument, NULL, 'c'},
231 		{"validate", no_argument, NULL, 'C'},
232 		{"debug", no_argument, NULL, 'd'},
233 		{"version", no_argument, NULL, 'v'},
234 		{"verbose", no_argument, NULL, 'V'},
235 		{"get-socketpath", no_argument, NULL, 'p'},
236 		{"unsupported-gpu", no_argument, NULL, 'u'},
237 		{"my-next-gpu-wont-be-nvidia", no_argument, NULL, 'u'},
238 		{0, 0, 0, 0}
239 	};
240 
241 	char *config_path = NULL;
242 
243 	const char* usage =
244 		"Usage: sway [options] [command]\n"
245 		"\n"
246 		"  -h, --help             Show help message and quit.\n"
247 		"  -c, --config <config>  Specify a config file.\n"
248 		"  -C, --validate         Check the validity of the config file, then exit.\n"
249 		"  -d, --debug            Enables full logging, including debug information.\n"
250 		"  -v, --version          Show the version number and quit.\n"
251 		"  -V, --verbose          Enables more verbose logging.\n"
252 		"      --get-socketpath   Gets the IPC socket path and prints it, then exits.\n"
253 		"\n";
254 
255 	int c;
256 	while (1) {
257 		int option_index = 0;
258 		c = getopt_long(argc, argv, "hCdD:vVc:", long_options, &option_index);
259 		if (c == -1) {
260 			break;
261 		}
262 		switch (c) {
263 		case 'h': // help
264 			fprintf(stdout, "%s", usage);
265 			exit(EXIT_SUCCESS);
266 			break;
267 		case 'c': // config
268 			free(config_path);
269 			config_path = strdup(optarg);
270 			break;
271 		case 'C': // validate
272 			validate = 1;
273 			break;
274 		case 'd': // debug
275 			debug = 1;
276 			break;
277 		case 'D': // extended debug options
278 			enable_debug_flag(optarg);
279 			break;
280 		case 'u':
281 			allow_unsupported_gpu = 1;
282 			break;
283 		case 'v': // version
284 			fprintf(stdout, "sway version " SWAY_VERSION "\n");
285 			exit(EXIT_SUCCESS);
286 			break;
287 		case 'V': // verbose
288 			verbose = 1;
289 			break;
290 		case 'p': ; // --get-socketpath
291 			if (getenv("SWAYSOCK")) {
292 				fprintf(stdout, "%s\n", getenv("SWAYSOCK"));
293 				exit(EXIT_SUCCESS);
294 			} else {
295 				fprintf(stderr, "sway socket not detected.\n");
296 				exit(EXIT_FAILURE);
297 			}
298 			break;
299 		default:
300 			fprintf(stderr, "%s", usage);
301 			exit(EXIT_FAILURE);
302 		}
303 	}
304 
305 	// Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the
306 	// clear error message (when not running as an IPC client).
307 	if (!getenv("XDG_RUNTIME_DIR") && optind == argc) {
308 		fprintf(stderr,
309 				"XDG_RUNTIME_DIR is not set in the environment. Aborting.\n");
310 		exit(EXIT_FAILURE);
311 	}
312 
313 	// As the 'callback' function for wlr_log is equivalent to that for
314 	// sway, we do not need to override it.
315 	if (debug) {
316 		sway_log_init(SWAY_DEBUG, sway_terminate);
317 		wlr_log_init(WLR_DEBUG, NULL);
318 	} else if (verbose) {
319 		sway_log_init(SWAY_INFO, sway_terminate);
320 		wlr_log_init(WLR_INFO, NULL);
321 	} else {
322 		sway_log_init(SWAY_ERROR, sway_terminate);
323 		wlr_log_init(WLR_ERROR, NULL);
324 	}
325 
326 	sway_log(SWAY_INFO, "Sway version " SWAY_VERSION);
327 	log_kernel();
328 	log_distro();
329 	log_env();
330 	detect_proprietary(allow_unsupported_gpu);
331 	detect_raspi();
332 
333 	if (optind < argc) { // Behave as IPC client
334 		if (optind != 1) {
335 			sway_log(SWAY_ERROR,
336 					"Detected both options and positional arguments. If you "
337 					"are trying to use the IPC client, options are not "
338 					"supported. Otherwise, check the provided arguments for "
339 					"issues. See `man 1 sway` or `sway -h` for usage. If you "
340 					"are trying to generate a debug log, use "
341 					"`sway -d 2>sway.log`.");
342 			exit(EXIT_FAILURE);
343 		}
344 		if (!drop_permissions()) {
345 			exit(EXIT_FAILURE);
346 		}
347 		char *socket_path = getenv("SWAYSOCK");
348 		if (!socket_path) {
349 			sway_log(SWAY_ERROR, "Unable to retrieve socket path");
350 			exit(EXIT_FAILURE);
351 		}
352 		char *command = join_args(argv + optind, argc - optind);
353 		run_as_ipc_client(command, socket_path);
354 		free(command);
355 		return 0;
356 	}
357 
358 	if (!server_privileged_prepare(&server)) {
359 		return 1;
360 	}
361 
362 	if (!drop_permissions()) {
363 		server_fini(&server);
364 		exit(EXIT_FAILURE);
365 	}
366 
367 	// handle SIGTERM signals
368 	signal(SIGTERM, sig_handler);
369 
370 	// prevent ipc from crashing sway
371 	signal(SIGPIPE, SIG_IGN);
372 
373 	sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION);
374 
375 	root = root_create();
376 
377 	if (!server_init(&server)) {
378 		return 1;
379 	}
380 
381 	if (validate) {
382 		bool valid = load_main_config(config_path, false, true);
383 		free(config_path);
384 		return valid ? 0 : 1;
385 	}
386 
387 	ipc_init(&server);
388 
389 	setenv("WAYLAND_DISPLAY", server.socket, true);
390 	if (!load_main_config(config_path, false, false)) {
391 		sway_terminate(EXIT_FAILURE);
392 		goto shutdown;
393 	}
394 
395 	if (!server_start(&server)) {
396 		sway_terminate(EXIT_FAILURE);
397 		goto shutdown;
398 	}
399 
400 	config->active = true;
401 	load_swaybars();
402 	run_deferred_commands();
403 	run_deferred_bindings();
404 	transaction_commit_dirty();
405 
406 	if (config->swaynag_config_errors.client != NULL) {
407 		swaynag_show(&config->swaynag_config_errors);
408 	}
409 
410 	server_run(&server);
411 
412 shutdown:
413 	sway_log(SWAY_INFO, "Shutting down sway");
414 
415 	server_fini(&server);
416 	root_destroy(root);
417 	root = NULL;
418 
419 	free(config_path);
420 	free_config(config);
421 
422 	pango_cairo_font_map_set_default(NULL);
423 
424 	return exit_value;
425 }
426