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