]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
288a74cc | 2 | |
07630cea | 3 | #include <fcntl.h> |
23b27b39 LP |
4 | #include <linux/kd.h> |
5 | #include <linux/tiocl.h> | |
6 | #include <linux/vt.h> | |
7 | #include <poll.h> | |
8 | #include <signal.h> | |
11c3a366 | 9 | #include <stdlib.h> |
11c3a366 | 10 | #include <sys/inotify.h> |
23b27b39 | 11 | #include <sys/ioctl.h> |
11c3a366 | 12 | #include <sys/sysmacros.h> |
288a74cc | 13 | #include <termios.h> |
0c15577a | 14 | #include <time.h> |
07630cea | 15 | #include <unistd.h> |
288a74cc | 16 | |
b5efdb8a | 17 | #include "alloc-util.h" |
b7120388 | 18 | #include "ansi-color.h" |
dffbe1d1 | 19 | #include "chase.h" |
7176f06c | 20 | #include "devnum-util.h" |
0c15577a | 21 | #include "errno-util.h" |
6553db60 | 22 | #include "extract-word.h" |
3ffd4af2 | 23 | #include "fd-util.h" |
288a74cc | 24 | #include "fileio.h" |
f4f15635 | 25 | #include "fs-util.h" |
63e9c383 | 26 | #include "hexdecoct.h" |
9e5fd717 | 27 | #include "inotify-util.h" |
c004493c | 28 | #include "io-util.h" |
93cc7779 | 29 | #include "log.h" |
ce3a1593 | 30 | #include "missing_magic.h" |
0cb8e3d1 | 31 | #include "namespace-util.h" |
6bedfcbb | 32 | #include "parse-util.h" |
c2b32159 LP |
33 | #include "path-util.h" |
34 | #include "proc-cmdline.h" | |
07630cea | 35 | #include "process-util.h" |
0bd72176 | 36 | #include "signal-util.h" |
2583fbea | 37 | #include "socket-util.h" |
8fcde012 | 38 | #include "stat-util.h" |
11f3c130 | 39 | #include "stdio-util.h" |
07630cea | 40 | #include "string-util.h" |
6af62124 | 41 | #include "strv.h" |
3ffd4af2 | 42 | #include "terminal-util.h" |
07630cea | 43 | #include "time-util.h" |
e4a08721 | 44 | #include "utf8.h" |
288a74cc | 45 | |
b177095b LP |
46 | #define ANSI_RESET_CURSOR \ |
47 | "\033?25h" /* turn on cursor */ \ | |
48 | "\033?12l" /* reset cursor blinking */ \ | |
49 | "\033 1q" /* reset cursor style */ | |
50 | ||
5321b957 ZJS |
51 | /* How much to wait for a reply to a terminal sequence */ |
52 | #define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC) | |
53 | ||
288a74cc RC |
54 | static volatile unsigned cached_columns = 0; |
55 | static volatile unsigned cached_lines = 0; | |
56 | ||
c6063244 | 57 | static volatile int cached_on_tty = -1; |
197dd3a9 | 58 | static volatile int cached_on_dev_null = -1; |
c6063244 | 59 | |
76270f5c MY |
60 | bool isatty_safe(int fd) { |
61 | assert(fd >= 0); | |
62 | ||
63 | if (isatty(fd)) | |
64 | return true; | |
65 | ||
1b24357c LP |
66 | /* Linux/glibc returns EIO for hung up TTY on isatty(). Which is wrong, the thing doesn't stop being |
67 | * a TTY after all, just because it is temporarily hung up. Let's work around this here, until this | |
68 | * is fixed in glibc. See: https://sourceware.org/bugzilla/show_bug.cgi?id=32103 */ | |
69 | if (errno == EIO) | |
70 | return true; | |
71 | ||
76270f5c MY |
72 | /* Be resilient if we're working on stdio, since they're set up by parent process. */ |
73 | assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)); | |
74 | ||
75 | return false; | |
76 | } | |
77 | ||
288a74cc | 78 | int chvt(int vt) { |
254d1313 | 79 | _cleanup_close_ int fd = -EBADF; |
288a74cc | 80 | |
0295642d LP |
81 | /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go, |
82 | * if that's configured. */ | |
83 | ||
0a8b555c | 84 | fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); |
288a74cc | 85 | if (fd < 0) |
4c8c499e | 86 | return fd; |
288a74cc | 87 | |
b9e74c39 | 88 | if (vt <= 0) { |
288a74cc RC |
89 | int tiocl[2] = { |
90 | TIOCL_GETKMSGREDIRECT, | |
91 | 0 | |
92 | }; | |
93 | ||
94 | if (ioctl(fd, TIOCLINUX, tiocl) < 0) | |
95 | return -errno; | |
96 | ||
97 | vt = tiocl[0] <= 0 ? 1 : tiocl[0]; | |
98 | } | |
99 | ||
7c248223 | 100 | return RET_NERRNO(ioctl(fd, VT_ACTIVATE, vt)); |
288a74cc RC |
101 | } |
102 | ||
8fcd8576 | 103 | int read_one_char(FILE *f, char *ret, usec_t t, bool echo, bool *need_nl) { |
715bcf36 LP |
104 | _cleanup_free_ char *line = NULL; |
105 | struct termios old_termios; | |
14f594b9 | 106 | int r, fd; |
288a74cc | 107 | |
288a74cc RC |
108 | assert(ret); |
109 | ||
8fcd8576 LP |
110 | if (!f) |
111 | f = stdin; | |
112 | ||
14f594b9 LP |
113 | /* If this is a terminal, then switch canonical mode off, so that we can read a single |
114 | * character. (Note that fmemopen() streams do not have an fd associated with them, let's handle that | |
8fcd8576 LP |
115 | * nicely.) If 'echo' is false we'll also disable ECHO mode so that the pressed key is not made |
116 | * visible to the user. */ | |
14f594b9 LP |
117 | fd = fileno(f); |
118 | if (fd >= 0 && tcgetattr(fd, &old_termios) >= 0) { | |
715bcf36 | 119 | struct termios new_termios = old_termios; |
288a74cc | 120 | |
8fcd8576 | 121 | new_termios.c_lflag &= ~(ICANON|(echo ? 0 : ECHO)); |
288a74cc RC |
122 | new_termios.c_cc[VMIN] = 1; |
123 | new_termios.c_cc[VTIME] = 0; | |
124 | ||
f789b17e | 125 | if (tcsetattr(fd, TCSANOW, &new_termios) >= 0) { |
03a7dbea | 126 | char c; |
288a74cc RC |
127 | |
128 | if (t != USEC_INFINITY) { | |
14f594b9 | 129 | if (fd_wait_for_event(fd, POLLIN, t) <= 0) { |
f789b17e | 130 | (void) tcsetattr(fd, TCSANOW, &old_termios); |
288a74cc RC |
131 | return -ETIMEDOUT; |
132 | } | |
133 | } | |
134 | ||
03a7dbea | 135 | r = safe_fgetc(f, &c); |
f789b17e | 136 | (void) tcsetattr(fd, TCSANOW, &old_termios); |
d3f9790c LP |
137 | if (r < 0) |
138 | return r; | |
03a7dbea LP |
139 | if (r == 0) |
140 | return -EIO; | |
288a74cc RC |
141 | |
142 | if (need_nl) | |
143 | *need_nl = c != '\n'; | |
144 | ||
145 | *ret = c; | |
146 | return 0; | |
147 | } | |
148 | } | |
149 | ||
978e7d16 | 150 | if (t != USEC_INFINITY && fd >= 0) { |
14f594b9 LP |
151 | /* Let's wait the specified amount of time for input. When we have no fd we skip this, under |
152 | * the assumption that this is an fmemopen() stream or so where waiting doesn't make sense | |
153 | * anyway, as the data is either already in the stream or cannot possible be placed there | |
154 | * while we access the stream */ | |
155 | ||
156 | if (fd_wait_for_event(fd, POLLIN, t) <= 0) | |
288a74cc RC |
157 | return -ETIMEDOUT; |
158 | } | |
159 | ||
715bcf36 | 160 | /* If this is not a terminal, then read a full line instead */ |
288a74cc | 161 | |
715bcf36 LP |
162 | r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */ |
163 | if (r < 0) | |
164 | return r; | |
165 | if (r == 0) | |
166 | return -EIO; | |
288a74cc RC |
167 | |
168 | if (strlen(line) != 1) | |
169 | return -EBADMSG; | |
170 | ||
171 | if (need_nl) | |
172 | *need_nl = false; | |
173 | ||
174 | *ret = line[0]; | |
175 | return 0; | |
176 | } | |
177 | ||
3c670f89 FB |
178 | #define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC) |
179 | ||
180 | int ask_char(char *ret, const char *replies, const char *fmt, ...) { | |
288a74cc RC |
181 | int r; |
182 | ||
183 | assert(ret); | |
184 | assert(replies); | |
3c670f89 | 185 | assert(fmt); |
288a74cc RC |
186 | |
187 | for (;;) { | |
188 | va_list ap; | |
189 | char c; | |
190 | bool need_nl = true; | |
191 | ||
25e4608b | 192 | fputs(ansi_highlight(), stdout); |
288a74cc | 193 | |
3c670f89 FB |
194 | putchar('\r'); |
195 | ||
196 | va_start(ap, fmt); | |
197 | vprintf(fmt, ap); | |
288a74cc RC |
198 | va_end(ap); |
199 | ||
25e4608b | 200 | fputs(ansi_normal(), stdout); |
288a74cc RC |
201 | |
202 | fflush(stdout); | |
203 | ||
8fcd8576 | 204 | r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, /* echo= */ true, &need_nl); |
288a74cc RC |
205 | if (r < 0) { |
206 | ||
3c670f89 FB |
207 | if (r == -ETIMEDOUT) |
208 | continue; | |
209 | ||
288a74cc RC |
210 | if (r == -EBADMSG) { |
211 | puts("Bad input, please try again."); | |
212 | continue; | |
213 | } | |
214 | ||
215 | putchar('\n'); | |
216 | return r; | |
217 | } | |
218 | ||
219 | if (need_nl) | |
220 | putchar('\n'); | |
221 | ||
222 | if (strchr(replies, c)) { | |
223 | *ret = c; | |
224 | return 0; | |
225 | } | |
226 | ||
227 | puts("Read unexpected character, please try again."); | |
228 | } | |
229 | } | |
230 | ||
94a2b1cd LP |
231 | typedef enum CompletionResult{ |
232 | COMPLETION_ALREADY, /* the input string is already complete */ | |
233 | COMPLETION_FULL, /* completed the input string to be complete now */ | |
234 | COMPLETION_PARTIAL, /* completed the input string so that is still incomplete */ | |
235 | COMPLETION_NONE, /* found no matching completion */ | |
236 | _COMPLETION_RESULT_MAX, | |
237 | _COMPLETION_RESULT_INVALID = -EINVAL, | |
238 | _COMPLETION_RESULT_ERRNO_MAX = -ERRNO_MAX, | |
239 | } CompletionResult; | |
240 | ||
241 | static CompletionResult pick_completion(const char *string, char *const*completions, char **ret) { | |
242 | _cleanup_free_ char *found = NULL; | |
243 | bool partial = false; | |
244 | ||
245 | string = strempty(string); | |
246 | ||
247 | STRV_FOREACH(c, completions) { | |
248 | ||
249 | /* Ignore entries that are not actually completions */ | |
250 | if (!startswith(*c, string)) | |
251 | continue; | |
252 | ||
253 | /* Store first completion that matches */ | |
254 | if (!found) { | |
255 | found = strdup(*c); | |
256 | if (!found) | |
257 | return -ENOMEM; | |
258 | ||
259 | continue; | |
260 | } | |
261 | ||
262 | /* If there's another completion that works truncate the one we already found by common | |
263 | * prefix */ | |
264 | size_t n = str_common_prefix(found, *c); | |
265 | if (n == SIZE_MAX) | |
266 | continue; | |
267 | ||
268 | found[n] = 0; | |
269 | partial = true; | |
270 | } | |
271 | ||
272 | *ret = TAKE_PTR(found); | |
273 | ||
274 | if (!*ret) | |
275 | return COMPLETION_NONE; | |
276 | if (partial) | |
277 | return COMPLETION_PARTIAL; | |
278 | ||
279 | return streq(string, *ret) ? COMPLETION_ALREADY : COMPLETION_FULL; | |
280 | } | |
281 | ||
282 | static void clear_by_backspace(size_t n) { | |
283 | /* Erase the specified number of character cells backwards on the terminal */ | |
284 | for (size_t i = 0; i < n; i++) | |
285 | fputs("\b \b", stdout); | |
286 | } | |
287 | ||
288 | int ask_string_full( | |
289 | char **ret, | |
290 | GetCompletionsCallback get_completions, | |
291 | void *userdata, | |
292 | const char *text, ...) { | |
293 | ||
03d94294 | 294 | va_list ap; |
715bcf36 LP |
295 | int r; |
296 | ||
288a74cc RC |
297 | assert(ret); |
298 | assert(text); | |
299 | ||
94a2b1cd | 300 | /* Output the prompt */ |
25e4608b | 301 | fputs(ansi_highlight(), stdout); |
03d94294 ZJS |
302 | va_start(ap, text); |
303 | vprintf(text, ap); | |
304 | va_end(ap); | |
25e4608b | 305 | fputs(ansi_normal(), stdout); |
03d94294 | 306 | fflush(stdout); |
288a74cc | 307 | |
94a2b1cd LP |
308 | _cleanup_free_ char *string = NULL; |
309 | size_t n = 0; | |
310 | ||
311 | /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use | |
312 | * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows | |
313 | * swapping out stdin/stdout. */ | |
314 | int fd_input = fileno(stdin); | |
315 | int fd_output = fileno(stdout); | |
316 | if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0) | |
317 | goto fallback; | |
318 | ||
319 | /* Try to disable echo, which also tells us if this even is a terminal */ | |
320 | struct termios old_termios; | |
321 | if (tcgetattr(fd_input, &old_termios) < 0) | |
322 | goto fallback; | |
323 | ||
324 | struct termios new_termios = old_termios; | |
325 | termios_disable_echo(&new_termios); | |
f789b17e | 326 | if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0) |
94a2b1cd LP |
327 | return -errno; |
328 | ||
329 | for (;;) { | |
330 | int c = fgetc(stdin); | |
331 | ||
332 | /* On EOF or NUL, end the request, don't output anything anymore */ | |
333 | if (IN_SET(c, EOF, 0)) | |
334 | break; | |
335 | ||
336 | /* On Return also end the request, but make this visible */ | |
337 | if (IN_SET(c, '\n', '\r')) { | |
338 | fputc('\n', stdout); | |
339 | break; | |
340 | } | |
341 | ||
342 | if (c == '\t') { | |
343 | /* Tab */ | |
344 | ||
345 | _cleanup_strv_free_ char **completions = NULL; | |
346 | if (get_completions) { | |
347 | r = get_completions(string, &completions, userdata); | |
348 | if (r < 0) | |
349 | goto fail; | |
350 | } | |
351 | ||
352 | _cleanup_free_ char *new_string = NULL; | |
353 | CompletionResult cr = pick_completion(string, completions, &new_string); | |
354 | if (cr < 0) { | |
355 | r = cr; | |
356 | goto fail; | |
357 | } | |
358 | if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) { | |
359 | /* Output the new suffix we learned */ | |
360 | fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout); | |
361 | ||
362 | /* And update the whole string */ | |
363 | free_and_replace(string, new_string); | |
364 | n = strlen(string); | |
365 | } | |
366 | if (cr == COMPLETION_NONE) | |
367 | fputc('\a', stdout); /* BEL */ | |
368 | ||
369 | if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_ALREADY)) { | |
370 | /* If this worked only partially, or if the user hit TAB even though we were | |
371 | * complete already, then show the remaining options (in the latter case just | |
372 | * the one). */ | |
373 | fputc('\n', stdout); | |
374 | ||
375 | _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string); | |
376 | if (!filtered) { | |
377 | r = -ENOMEM; | |
378 | goto fail; | |
379 | } | |
380 | ||
381 | r = show_menu(filtered, | |
382 | /* n_columns= */ SIZE_MAX, | |
383 | /* column_width= */ SIZE_MAX, | |
384 | /* ellipsize_percentage= */ 0, | |
385 | /* grey_prefix=*/ string, | |
386 | /* with_numbers= */ false); | |
387 | if (r < 0) | |
388 | goto fail; | |
389 | ||
390 | /* Show the prompt again */ | |
391 | fputs(ansi_highlight(), stdout); | |
392 | va_start(ap, text); | |
393 | vprintf(text, ap); | |
394 | va_end(ap); | |
395 | fputs(ansi_normal(), stdout); | |
396 | fputs(string, stdout); | |
397 | } | |
398 | ||
399 | } else if (IN_SET(c, '\b', 127)) { | |
400 | /* Backspace */ | |
401 | ||
402 | if (n == 0) | |
403 | fputc('\a', stdout); /* BEL */ | |
404 | else { | |
405 | size_t m = utf8_last_length(string, n); | |
406 | ||
407 | char *e = string + n - m; | |
408 | clear_by_backspace(utf8_console_width(e)); | |
409 | ||
410 | *e = 0; | |
411 | n -= m; | |
412 | } | |
413 | ||
414 | } else if (c == 21) { | |
415 | /* Ctrl-u → erase all input */ | |
416 | ||
417 | clear_by_backspace(utf8_console_width(string)); | |
a6eb2296 YW |
418 | if (string) |
419 | string[n = 0] = 0; | |
420 | else | |
421 | assert(n == 0); | |
94a2b1cd LP |
422 | |
423 | } else if (c == 4) { | |
424 | /* Ctrl-d → cancel this field input */ | |
425 | ||
426 | r = -ECANCELED; | |
427 | goto fail; | |
428 | ||
429 | } else if (char_is_cc(c) || n >= LINE_MAX) | |
430 | /* refuse control characters and too long strings */ | |
431 | fputc('\a', stdout); /* BEL */ | |
432 | else { | |
433 | /* Regular char */ | |
434 | ||
435 | if (!GREEDY_REALLOC(string, n+2)) { | |
436 | r = -ENOMEM; | |
437 | goto fail; | |
438 | } | |
439 | ||
440 | string[n++] = (char) c; | |
441 | string[n] = 0; | |
442 | ||
443 | fputc(c, stdout); | |
444 | } | |
445 | ||
446 | fflush(stdout); | |
447 | } | |
448 | ||
f789b17e | 449 | if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0) |
94a2b1cd LP |
450 | return -errno; |
451 | ||
452 | if (!string) { | |
453 | string = strdup(""); | |
454 | if (!string) | |
455 | return -ENOMEM; | |
456 | } | |
457 | ||
458 | *ret = TAKE_PTR(string); | |
459 | return 0; | |
460 | ||
461 | fail: | |
f789b17e | 462 | (void) tcsetattr(fd_input, TCSANOW, &old_termios); |
94a2b1cd LP |
463 | return r; |
464 | ||
465 | fallback: | |
466 | /* A simple fallback without TTY magic */ | |
467 | r = read_line(stdin, LONG_LINE_MAX, &string); | |
03d94294 ZJS |
468 | if (r < 0) |
469 | return r; | |
470 | if (r == 0) | |
471 | return -EIO; | |
288a74cc | 472 | |
94a2b1cd | 473 | *ret = TAKE_PTR(string); |
03d94294 | 474 | return 0; |
288a74cc RC |
475 | } |
476 | ||
91ea3dcf | 477 | bool any_key_to_proceed(void) { |
91ea3dcf | 478 | |
8fcd8576 LP |
479 | /* Insert a new line here as well as to when the user inputs, as this is also used during the boot up |
480 | * sequence when status messages may be interleaved with the current program output. This ensures | |
481 | * that the status messages aren't appended on the same line as this message. */ | |
91ea3dcf | 482 | |
8fcd8576 LP |
483 | fputc('\n', stdout); |
484 | fputs(ansi_highlight_magenta(), stdout); | |
485 | fputs("-- Press any key to proceed --", stdout); | |
486 | fputs(ansi_normal(), stdout); | |
787904d0 | 487 | fputc('\n', stdout); |
8fcd8576 | 488 | fflush(stdout); |
91ea3dcf | 489 | |
8fcd8576 LP |
490 | char key = 0; |
491 | (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL); | |
492 | ||
8fcd8576 LP |
493 | fputc('\n', stdout); |
494 | fflush(stdout); | |
91ea3dcf MF |
495 | |
496 | return key != 'q'; | |
497 | } | |
498 | ||
b6478aa1 LP |
499 | static size_t widest_list_element(char *const*l) { |
500 | size_t w = 0; | |
501 | ||
502 | /* Returns the largest console width of all elements in 'l' */ | |
503 | ||
504 | STRV_FOREACH(i, l) | |
505 | w = MAX(w, utf8_console_width(*i)); | |
506 | ||
507 | return w; | |
508 | } | |
509 | ||
510 | int show_menu(char **x, | |
511 | size_t n_columns, | |
512 | size_t column_width, | |
513 | unsigned ellipsize_percentage, | |
514 | const char *grey_prefix, | |
515 | bool with_numbers) { | |
ec75a254 DDM |
516 | |
517 | assert(n_columns > 0); | |
518 | ||
b6478aa1 LP |
519 | if (n_columns == SIZE_MAX) |
520 | n_columns = 3; | |
521 | ||
522 | if (column_width == SIZE_MAX) { | |
523 | size_t widest = widest_list_element(x); | |
524 | ||
525 | /* If not specified, derive column width from screen width */ | |
526 | size_t column_max = (columns()-1) / n_columns; | |
527 | ||
528 | /* Subtract room for numbers */ | |
529 | if (with_numbers && column_max > 6) | |
530 | column_max -= 6; | |
ec75a254 | 531 | |
b6478aa1 LP |
532 | /* If columns would get too tight let's make this a linear list instead. */ |
533 | if (column_max < 10 && widest > 10) { | |
534 | n_columns = 1; | |
535 | column_max = columns()-1; | |
536 | ||
537 | if (with_numbers && column_max > 6) | |
538 | column_max -= 6; | |
539 | } | |
540 | ||
541 | column_width = CLAMP(widest+1, 10U, column_max); | |
542 | } | |
543 | ||
544 | size_t n = strv_length(x); | |
545 | size_t per_column = DIV_ROUND_UP(n, n_columns); | |
546 | ||
547 | size_t break_lines = lines(); | |
ec75a254 DDM |
548 | if (break_lines > 2) |
549 | break_lines--; | |
550 | ||
551 | /* The first page gets two extra lines, since we want to show | |
552 | * a title */ | |
b6478aa1 | 553 | size_t break_modulo = break_lines; |
ec75a254 DDM |
554 | if (break_modulo > 3) |
555 | break_modulo -= 3; | |
556 | ||
b6478aa1 | 557 | for (size_t i = 0; i < per_column; i++) { |
ec75a254 | 558 | |
b6478aa1 | 559 | for (size_t j = 0; j < n_columns; j++) { |
ec75a254 DDM |
560 | _cleanup_free_ char *e = NULL; |
561 | ||
562 | if (j * per_column + i >= n) | |
563 | break; | |
564 | ||
b6478aa1 | 565 | e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage); |
ec75a254 | 566 | if (!e) |
b6478aa1 LP |
567 | return -ENOMEM; |
568 | ||
569 | if (with_numbers) | |
570 | printf("%s%4zu)%s ", | |
571 | ansi_grey(), | |
572 | j * per_column + i + 1, | |
573 | ansi_normal()); | |
574 | ||
575 | if (grey_prefix && startswith(e, grey_prefix)) { | |
576 | size_t k = MIN(strlen(grey_prefix), column_width); | |
577 | printf("%s%.*s%s", | |
578 | ansi_grey(), | |
579 | (int) k, e, | |
580 | ansi_normal()); | |
581 | printf("%-*s", | |
582 | (int) (column_width - k), e+k); | |
583 | } else | |
584 | printf("%-*s", (int) column_width, e); | |
ec75a254 DDM |
585 | } |
586 | ||
587 | putchar('\n'); | |
588 | ||
589 | /* on the first screen we reserve 2 extra lines for the title */ | |
8fcd8576 | 590 | if (i % break_lines == break_modulo) |
ec75a254 DDM |
591 | if (!any_key_to_proceed()) |
592 | return 0; | |
ec75a254 DDM |
593 | } |
594 | ||
595 | return 0; | |
596 | } | |
597 | ||
288a74cc | 598 | int open_terminal(const char *name, int mode) { |
254d1313 | 599 | _cleanup_close_ int fd = -EBADF; |
288a74cc RC |
600 | |
601 | /* | |
4768529f LP |
602 | * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but |
603 | * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of | |
604 | * times. | |
288a74cc RC |
605 | * |
606 | * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 | |
607 | */ | |
608 | ||
fbd2679f | 609 | assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0); |
288a74cc | 610 | |
fbd2679f | 611 | for (unsigned c = 0;; c++) { |
288a74cc RC |
612 | fd = open(name, mode, 0); |
613 | if (fd >= 0) | |
614 | break; | |
615 | ||
616 | if (errno != EIO) | |
617 | return -errno; | |
618 | ||
619 | /* Max 1s in total */ | |
620 | if (c >= 20) | |
fbd2679f | 621 | return -EIO; |
288a74cc | 622 | |
4251512e | 623 | (void) usleep_safe(50 * USEC_PER_MSEC); |
288a74cc RC |
624 | } |
625 | ||
dd9c8da8 | 626 | if (!isatty_safe(fd)) |
aae47bf7 | 627 | return -ENOTTY; |
288a74cc | 628 | |
4768529f | 629 | return TAKE_FD(fd); |
288a74cc RC |
630 | } |
631 | ||
632 | int acquire_terminal( | |
633 | const char *name, | |
8854d795 | 634 | AcquireTerminalFlags flags, |
288a74cc RC |
635 | usec_t timeout) { |
636 | ||
254d1313 | 637 | _cleanup_close_ int notify = -EBADF, fd = -EBADF; |
8854d795 LP |
638 | usec_t ts = USEC_INFINITY; |
639 | int r, wd = -1; | |
288a74cc RC |
640 | |
641 | assert(name); | |
789f4f7e LP |
642 | |
643 | AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK; | |
644 | assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT)); | |
645 | assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM)); | |
288a74cc | 646 | |
8854d795 LP |
647 | /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually |
648 | * acquire it, so that we don't lose any event. | |
288a74cc | 649 | * |
8854d795 LP |
650 | * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch |
651 | * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty | |
f95dbcc2 ZJS |
652 | * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to |
653 | * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they | |
8854d795 LP |
654 | * probably should not do anyway.) */ |
655 | ||
789f4f7e LP |
656 | if (mode == ACQUIRE_TERMINAL_WAIT) { |
657 | notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); | |
8854d795 LP |
658 | if (notify < 0) |
659 | return -errno; | |
288a74cc RC |
660 | |
661 | wd = inotify_add_watch(notify, name, IN_CLOSE); | |
8854d795 LP |
662 | if (wd < 0) |
663 | return -errno; | |
664 | ||
665 | if (timeout != USEC_INFINITY) | |
666 | ts = now(CLOCK_MONOTONIC); | |
288a74cc RC |
667 | } |
668 | ||
789f4f7e LP |
669 | /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */ |
670 | sigset_t poll_ss; | |
671 | assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0); | |
672 | if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) { | |
673 | assert_se(sigismember(&poll_ss, SIGTERM) > 0); | |
674 | assert_se(sigdelset(&poll_ss, SIGTERM) >= 0); | |
675 | } | |
676 | ||
288a74cc | 677 | for (;;) { |
288a74cc RC |
678 | if (notify >= 0) { |
679 | r = flush_fd(notify); | |
680 | if (r < 0) | |
8854d795 | 681 | return r; |
288a74cc RC |
682 | } |
683 | ||
8854d795 LP |
684 | /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way |
685 | * to figure out if we successfully became the controlling process of the tty */ | |
288a74cc RC |
686 | fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); |
687 | if (fd < 0) | |
688 | return fd; | |
689 | ||
8854d795 | 690 | /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */ |
0bd72176 LP |
691 | struct sigaction sa_old; |
692 | assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0); | |
288a74cc RC |
693 | |
694 | /* First, try to get the tty */ | |
789f4f7e | 695 | r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE)); |
288a74cc | 696 | |
8854d795 | 697 | /* Reset signal handler to old value */ |
0bd72176 | 698 | assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0); |
288a74cc | 699 | |
8854d795 LP |
700 | /* Success? Exit the loop now! */ |
701 | if (r >= 0) | |
702 | break; | |
288a74cc | 703 | |
8854d795 LP |
704 | /* Any failure besides -EPERM? Fail, regardless of the mode. */ |
705 | if (r != -EPERM) | |
706 | return r; | |
288a74cc | 707 | |
8854d795 LP |
708 | if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this |
709 | * into a success. Note that EPERM is also returned if we | |
710 | * already are the owner of the TTY. */ | |
288a74cc RC |
711 | break; |
712 | ||
789f4f7e | 713 | if (mode != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */ |
8854d795 LP |
714 | return r; |
715 | ||
288a74cc | 716 | assert(notify >= 0); |
8854d795 | 717 | assert(wd >= 0); |
288a74cc RC |
718 | |
719 | for (;;) { | |
789f4f7e LP |
720 | usec_t left; |
721 | if (timeout == USEC_INFINITY) | |
722 | left = USEC_INFINITY; | |
723 | else { | |
8854d795 LP |
724 | assert(ts != USEC_INFINITY); |
725 | ||
789f4f7e | 726 | usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts); |
496db330 | 727 | if (n >= timeout) |
8854d795 | 728 | return -ETIMEDOUT; |
288a74cc | 729 | |
789f4f7e | 730 | left = timeout - n; |
288a74cc RC |
731 | } |
732 | ||
789f4f7e LP |
733 | r = ppoll_usec_full( |
734 | &(struct pollfd) { | |
735 | .fd = notify, | |
736 | .events = POLLIN, | |
737 | }, | |
fa6f4484 | 738 | /* n_fds = */ 1, |
789f4f7e LP |
739 | left, |
740 | &poll_ss); | |
741 | if (r < 0) | |
742 | return r; | |
743 | if (r == 0) | |
744 | return -ETIMEDOUT; | |
745 | ||
746 | union inotify_event_buffer buffer; | |
747 | ssize_t l; | |
288a74cc RC |
748 | l = read(notify, &buffer, sizeof(buffer)); |
749 | if (l < 0) { | |
8add30a0 | 750 | if (ERRNO_IS_TRANSIENT(errno)) |
288a74cc RC |
751 | continue; |
752 | ||
8854d795 | 753 | return -errno; |
288a74cc RC |
754 | } |
755 | ||
756 | FOREACH_INOTIFY_EVENT(e, buffer, l) { | |
8854d795 LP |
757 | if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */ |
758 | break; | |
759 | ||
760 | if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */ | |
761 | return -EIO; | |
288a74cc RC |
762 | } |
763 | ||
764 | break; | |
765 | } | |
766 | ||
8854d795 LP |
767 | /* We close the tty fd here since if the old session ended our handle will be dead. It's important that |
768 | * we do this after sleeping, so that we don't enter an endless loop. */ | |
288a74cc RC |
769 | fd = safe_close(fd); |
770 | } | |
771 | ||
c10d6bdb | 772 | return TAKE_FD(fd); |
288a74cc RC |
773 | } |
774 | ||
775 | int release_terminal(void) { | |
254d1313 | 776 | _cleanup_close_ int fd = -EBADF; |
87964ec7 | 777 | int r; |
288a74cc | 778 | |
0a8b555c | 779 | fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); |
288a74cc RC |
780 | if (fd < 0) |
781 | return -errno; | |
782 | ||
783 | /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed | |
784 | * by our own TIOCNOTTY */ | |
0bd72176 LP |
785 | struct sigaction sa_old; |
786 | assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0); | |
288a74cc | 787 | |
7c248223 | 788 | r = RET_NERRNO(ioctl(fd, TIOCNOTTY)); |
288a74cc | 789 | |
0bd72176 | 790 | assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0); |
288a74cc RC |
791 | |
792 | return r; | |
793 | } | |
794 | ||
1de12823 MY |
795 | int terminal_new_session(void) { |
796 | ||
797 | /* Make us the new session leader, and set stdin tty to be our controlling terminal. | |
798 | * | |
799 | * Why stdin? Well, the ctty logic is relevant for signal delivery mostly, i.e. if people hit C-c | |
800 | * or the line is hung up. Such events are basically just a form of input, via a side channel | |
801 | * (that side channel being signal delivery, i.e. SIGINT, SIGHUP et al). Hence we focus on input, | |
802 | * not output here. */ | |
803 | ||
804 | if (!isatty_safe(STDIN_FILENO)) | |
805 | return -ENXIO; | |
806 | ||
807 | (void) setsid(); | |
808 | return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0)); | |
809 | } | |
810 | ||
0c15577a DDM |
811 | void terminal_detach_session(void) { |
812 | (void) setsid(); | |
813 | (void) release_terminal(); | |
814 | } | |
815 | ||
288a74cc RC |
816 | int terminal_vhangup_fd(int fd) { |
817 | assert(fd >= 0); | |
7c248223 | 818 | return RET_NERRNO(ioctl(fd, TIOCVHANGUP)); |
288a74cc RC |
819 | } |
820 | ||
bc3477fd DDM |
821 | int terminal_vhangup(const char *tty) { |
822 | _cleanup_close_ int fd = -EBADF; | |
823 | ||
824 | assert(tty); | |
825 | ||
826 | fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC); | |
827 | if (fd < 0) | |
828 | return fd; | |
829 | ||
830 | return terminal_vhangup_fd(fd); | |
831 | } | |
832 | ||
45d785df LP |
833 | int vt_disallocate(const char *tty_path) { |
834 | assert(tty_path); | |
288a74cc | 835 | |
45d785df LP |
836 | /* Deallocate the VT if possible. If not possible (i.e. because it is the active one), at least clear |
837 | * it entirely (including the scrollback buffer). */ | |
288a74cc | 838 | |
45d785df LP |
839 | int ttynr = vtnr_from_tty(tty_path); |
840 | if (ttynr > 0) { | |
841 | _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); | |
ba5d26cc ZJS |
842 | if (fd < 0) |
843 | return fd; | |
288a74cc | 844 | |
45d785df LP |
845 | /* Try to deallocate */ |
846 | if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0) | |
ba5d26cc ZJS |
847 | return 0; |
848 | if (errno != EBUSY) | |
849 | return -errno; | |
850 | } | |
288a74cc | 851 | |
45d785df LP |
852 | /* So this is not a VT (in which case we cannot deallocate it), or we failed to deallocate. Let's at |
853 | * least clear the screen. */ | |
288a74cc | 854 | |
45d785df | 855 | _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); |
ba5d26cc ZJS |
856 | if (fd2 < 0) |
857 | return fd2; | |
288a74cc | 858 | |
45d785df | 859 | return loop_write_full(fd2, |
b177095b | 860 | ANSI_RESET_CURSOR |
45d785df LP |
861 | "\033[r" /* clear scrolling region */ |
862 | "\033[H" /* move home */ | |
863 | "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ | |
864 | "\033c", /* reset to initial state */ | |
865 | SIZE_MAX, | |
866 | 100 * USEC_PER_MSEC); | |
288a74cc RC |
867 | } |
868 | ||
b61c015a LP |
869 | static int vt_default_utf8(void) { |
870 | _cleanup_free_ char *b = NULL; | |
871 | int r; | |
872 | ||
873 | /* Read the default VT UTF8 setting from the kernel */ | |
874 | ||
875 | r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); | |
876 | if (r < 0) | |
877 | return r; | |
878 | ||
879 | return parse_boolean(b); | |
880 | } | |
881 | ||
882 | static int vt_reset_keyboard(int fd) { | |
af1d3a6d | 883 | int r, kb; |
b61c015a | 884 | |
af1d3a6d | 885 | assert(fd >= 0); |
b61c015a | 886 | |
af1d3a6d LP |
887 | /* If we can't read the default, then default to Unicode. It's 2024 after all. */ |
888 | r = vt_default_utf8(); | |
889 | if (r < 0) | |
890 | log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m"); | |
891 | ||
892 | kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE; | |
b61c015a LP |
893 | return RET_NERRNO(ioctl(fd, KDSKBMODE, kb)); |
894 | } | |
895 | ||
e2216800 LP |
896 | static int terminal_reset_ioctl(int fd, bool switch_to_text) { |
897 | struct termios termios; | |
898 | int r; | |
899 | ||
900 | /* Set terminal to some sane defaults */ | |
901 | ||
902 | assert(fd >= 0); | |
903 | ||
904 | /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set, | |
905 | * and we don't interfere with that. */ | |
906 | ||
907 | /* Disable exclusive mode, just in case */ | |
908 | if (ioctl(fd, TIOCNXCL) < 0) | |
909 | log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m"); | |
910 | ||
911 | /* Switch to text mode */ | |
912 | if (switch_to_text) | |
913 | if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) | |
914 | log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m"); | |
915 | ||
916 | /* Set default keyboard mode */ | |
917 | r = vt_reset_keyboard(fd); | |
918 | if (r < 0) | |
919 | log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m"); | |
920 | ||
921 | if (tcgetattr(fd, &termios) < 0) { | |
922 | r = log_debug_errno(errno, "Failed to get terminal parameters: %m"); | |
923 | goto finish; | |
924 | } | |
925 | ||
926 | /* We only reset the stuff that matters to the software. How | |
927 | * hardware is set up we don't touch assuming that somebody | |
928 | * else will do that for us */ | |
929 | ||
930 | termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); | |
931 | termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; | |
932 | termios.c_oflag |= ONLCR | OPOST; | |
933 | termios.c_cflag |= CREAD; | |
934 | termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE; | |
935 | ||
936 | termios.c_cc[VINTR] = 03; /* ^C */ | |
937 | termios.c_cc[VQUIT] = 034; /* ^\ */ | |
938 | termios.c_cc[VERASE] = 0177; | |
939 | termios.c_cc[VKILL] = 025; /* ^X */ | |
940 | termios.c_cc[VEOF] = 04; /* ^D */ | |
941 | termios.c_cc[VSTART] = 021; /* ^Q */ | |
942 | termios.c_cc[VSTOP] = 023; /* ^S */ | |
943 | termios.c_cc[VSUSP] = 032; /* ^Z */ | |
944 | termios.c_cc[VLNEXT] = 026; /* ^V */ | |
945 | termios.c_cc[VWERASE] = 027; /* ^W */ | |
946 | termios.c_cc[VREPRINT] = 022; /* ^R */ | |
947 | termios.c_cc[VEOL] = 0; | |
948 | termios.c_cc[VEOL2] = 0; | |
949 | ||
950 | termios.c_cc[VTIME] = 0; | |
951 | termios.c_cc[VMIN] = 1; | |
952 | ||
953 | r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios)); | |
954 | if (r < 0) | |
955 | log_debug_errno(r, "Failed to set terminal parameters: %m"); | |
956 | ||
957 | finish: | |
958 | /* Just in case, flush all crap out */ | |
959 | (void) tcflush(fd, TCIOFLUSH); | |
960 | ||
961 | return r; | |
962 | } | |
963 | ||
964 | static int terminal_reset_ansi_seq(int fd) { | |
965 | int r, k; | |
966 | ||
967 | assert(fd >= 0); | |
968 | ||
969 | if (getenv_terminal_is_dumb()) | |
970 | return 0; | |
971 | ||
972 | r = fd_nonblock(fd, true); | |
973 | if (r < 0) | |
974 | return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); | |
975 | ||
976 | k = loop_write_full(fd, | |
b177095b | 977 | ANSI_RESET_CURSOR |
e2216800 LP |
978 | "\033[!p" /* soft terminal reset */ |
979 | "\033]104\007" /* reset colors */ | |
4efd46c4 LP |
980 | "\033[?7h" /* enable line-wrapping */ |
981 | "\033[1G" /* place cursor at beginning of current line */ | |
982 | "\033[0J", /* erase till end of screen */ | |
e2216800 | 983 | SIZE_MAX, |
524e1240 | 984 | 100 * USEC_PER_MSEC); |
e2216800 | 985 | if (k < 0) |
524e1240 | 986 | log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m"); |
e2216800 LP |
987 | |
988 | if (r > 0) { | |
989 | r = fd_nonblock(fd, false); | |
990 | if (r < 0) | |
991 | log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); | |
992 | } | |
993 | ||
994 | return k < 0 ? k : r; | |
995 | } | |
996 | ||
2736295d LP |
997 | void reset_dev_console_fd(int fd, bool switch_to_text) { |
998 | int r; | |
999 | ||
1000 | assert(fd >= 0); | |
1001 | ||
2cd19499 LP |
1002 | _cleanup_close_ int lock_fd = lock_dev_console(); |
1003 | if (lock_fd < 0) | |
1004 | log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m"); | |
1005 | ||
5a4e5417 | 1006 | r = terminal_reset_ioctl(fd, switch_to_text); |
2736295d LP |
1007 | if (r < 0) |
1008 | log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m"); | |
1009 | ||
1010 | unsigned rows, cols; | |
1011 | r = proc_cmdline_tty_size("/dev/console", &rows, &cols); | |
1012 | if (r < 0) | |
1013 | log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m"); | |
1014 | else if (r > 0) { | |
1015 | r = terminal_set_size_fd(fd, NULL, rows, cols); | |
1016 | if (r < 0) | |
1017 | log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m"); | |
1018 | } else | |
1019 | (void) terminal_fix_size(fd, fd); | |
1020 | ||
1021 | r = terminal_reset_ansi_seq(fd); | |
1022 | if (r < 0) | |
1023 | log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m"); | |
1024 | } | |
1025 | ||
4a24cc85 LP |
1026 | int lock_dev_console(void) { |
1027 | _cleanup_close_ int fd = -EBADF; | |
1028 | int r; | |
1029 | ||
1030 | /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some | |
1031 | * pty in /dev/console, in which case it should be fine to lock the target TTY. */ | |
1032 | fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY); | |
1033 | if (fd < 0) | |
1034 | return fd; | |
1035 | ||
1036 | r = lock_generic(fd, LOCK_BSD, LOCK_EX); | |
1037 | if (r < 0) | |
1038 | return r; | |
1039 | ||
1040 | return TAKE_FD(fd); | |
1041 | } | |
1042 | ||
288a74cc RC |
1043 | int make_console_stdio(void) { |
1044 | int fd, r; | |
1045 | ||
9281e703 LP |
1046 | /* Make /dev/console the controlling terminal and stdin/stdout/stderr, if we can. If we can't use |
1047 | * /dev/null instead. This is particularly useful if /dev/console is turned off, e.g. if console=null | |
1048 | * is specified on the kernel command line. */ | |
288a74cc | 1049 | |
8854d795 | 1050 | fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY); |
9281e703 LP |
1051 | if (fd < 0) { |
1052 | log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m"); | |
288a74cc | 1053 | |
9281e703 LP |
1054 | r = make_null_stdio(); |
1055 | if (r < 0) | |
1056 | return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m"); | |
3d18b167 | 1057 | |
9281e703 | 1058 | } else { |
2736295d | 1059 | reset_dev_console_fd(fd, /* switch_to_text= */ true); |
963e25c2 | 1060 | |
9281e703 LP |
1061 | r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */ |
1062 | if (r < 0) | |
1063 | return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m"); | |
1064 | } | |
c6063244 | 1065 | |
9281e703 | 1066 | reset_terminal_feature_caches(); |
288a74cc RC |
1067 | return 0; |
1068 | } | |
1069 | ||
b27f7916 | 1070 | static int vtnr_from_tty_raw(const char *tty, unsigned *ret) { |
288a74cc RC |
1071 | assert(tty); |
1072 | ||
b27f7916 | 1073 | tty = skip_dev_prefix(tty); |
288a74cc | 1074 | |
b27f7916 MY |
1075 | const char *e = startswith(tty, "tty"); |
1076 | if (!e) | |
1077 | return -EINVAL; | |
288a74cc | 1078 | |
b27f7916 | 1079 | return safe_atou(e, ret); |
288a74cc RC |
1080 | } |
1081 | ||
1082 | int vtnr_from_tty(const char *tty) { | |
b27f7916 | 1083 | unsigned u; |
57e55f93 | 1084 | int r; |
288a74cc RC |
1085 | |
1086 | assert(tty); | |
1087 | ||
b27f7916 | 1088 | r = vtnr_from_tty_raw(tty, &u); |
288a74cc RC |
1089 | if (r < 0) |
1090 | return r; | |
57e55f93 LP |
1091 | if (!vtnr_is_valid(u)) |
1092 | return -ERANGE; | |
288a74cc | 1093 | |
57e55f93 | 1094 | return (int) u; |
288a74cc RC |
1095 | } |
1096 | ||
b27f7916 MY |
1097 | bool tty_is_vc(const char *tty) { |
1098 | assert(tty); | |
1099 | ||
1100 | /* NB: for >= 0 values no range check is conducted here, on the assumption that the caller will | |
1101 | * either extract vtnr through vtnr_from_tty() later where ERANGE would be reported, or doesn't care | |
79ef0374 | 1102 | * about whether it's strictly valid, but only asking "does this fall into the vt category?", for which |
b27f7916 MY |
1103 | * "yes" seems to be a better answer. */ |
1104 | ||
1105 | return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0; | |
1106 | } | |
1107 | ||
1108 | bool tty_is_console(const char *tty) { | |
1109 | assert(tty); | |
1110 | ||
1111 | return streq(skip_dev_prefix(tty), "console"); | |
1112 | } | |
1113 | ||
845be16f | 1114 | int resolve_dev_console(char **ret) { |
7b912648 | 1115 | int r; |
288a74cc | 1116 | |
7b912648 LP |
1117 | assert(ret); |
1118 | ||
dffbe1d1 LP |
1119 | /* Resolve where /dev/console is pointing to. If /dev/console is a symlink (like in container |
1120 | * managers), we'll just resolve the symlink. If it's a real device node, we'll use if | |
1121 | * /sys/class/tty/tty0/active, but only if /sys/ is actually ours (i.e. not read-only-mounted which | |
1122 | * is a sign for container setups). */ | |
288a74cc | 1123 | |
dffbe1d1 | 1124 | _cleanup_free_ char *chased = NULL; |
39f46860 | 1125 | r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0, &chased, /* ret_fd= */ NULL); |
dffbe1d1 LP |
1126 | if (r < 0) |
1127 | return r; | |
1128 | if (!path_equal(chased, "/dev/console")) { | |
1129 | *ret = TAKE_PTR(chased); | |
1130 | return 0; | |
1131 | } | |
1132 | ||
1133 | r = path_is_read_only_fs("/sys"); | |
1134 | if (r < 0) | |
1135 | return r; | |
1136 | if (r > 0) | |
7b912648 | 1137 | return -ENOMEDIUM; |
288a74cc | 1138 | |
dffbe1d1 | 1139 | _cleanup_free_ char *active = NULL; |
7b912648 LP |
1140 | r = read_one_line_file("/sys/class/tty/console/active", &active); |
1141 | if (r < 0) | |
1142 | return r; | |
288a74cc | 1143 | |
7b912648 | 1144 | /* If multiple log outputs are configured the last one is what /dev/console points to */ |
dffbe1d1 | 1145 | const char *tty = strrchr(active, ' '); |
288a74cc RC |
1146 | if (tty) |
1147 | tty++; | |
1148 | else | |
7b912648 | 1149 | tty = active; |
288a74cc RC |
1150 | |
1151 | if (streq(tty, "tty0")) { | |
7b912648 | 1152 | active = mfree(active); |
288a74cc RC |
1153 | |
1154 | /* Get the active VC (e.g. tty1) */ | |
7b912648 LP |
1155 | r = read_one_line_file("/sys/class/tty/tty0/active", &active); |
1156 | if (r < 0) | |
1157 | return r; | |
1158 | ||
1159 | tty = active; | |
1160 | } | |
1161 | ||
c7f7d87d DDM |
1162 | _cleanup_free_ char *path = NULL; |
1163 | path = path_join("/dev", tty); | |
1164 | if (!path) | |
1165 | return -ENOMEM; | |
288a74cc | 1166 | |
c7f7d87d | 1167 | *ret = TAKE_PTR(path); |
7b912648 | 1168 | return 0; |
288a74cc RC |
1169 | } |
1170 | ||
bef41af2 LP |
1171 | int get_kernel_consoles(char ***ret) { |
1172 | _cleanup_strv_free_ char **l = NULL; | |
6af62124 | 1173 | _cleanup_free_ char *line = NULL; |
6af62124 WF |
1174 | int r; |
1175 | ||
bef41af2 LP |
1176 | assert(ret); |
1177 | ||
daf13202 MY |
1178 | /* If /sys/ is mounted read-only this means we are running in some kind of container environment. |
1179 | * In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */ | |
bef41af2 LP |
1180 | if (path_is_read_only_fs("/sys") > 0) |
1181 | goto fallback; | |
6af62124 WF |
1182 | |
1183 | r = read_one_line_file("/sys/class/tty/console/active", &line); | |
1184 | if (r < 0) | |
1185 | return r; | |
1186 | ||
daf13202 | 1187 | for (const char *p = line;;) { |
6abdec98 | 1188 | _cleanup_free_ char *tty = NULL, *path = NULL; |
6af62124 | 1189 | |
bef41af2 | 1190 | r = extract_first_word(&p, &tty, NULL, 0); |
6af62124 WF |
1191 | if (r < 0) |
1192 | return r; | |
1193 | if (r == 0) | |
1194 | break; | |
1195 | ||
1196 | if (streq(tty, "tty0")) { | |
1197 | tty = mfree(tty); | |
1198 | r = read_one_line_file("/sys/class/tty/tty0/active", &tty); | |
1199 | if (r < 0) | |
1200 | return r; | |
1201 | } | |
1202 | ||
6abdec98 | 1203 | path = path_join("/dev", tty); |
6af62124 WF |
1204 | if (!path) |
1205 | return -ENOMEM; | |
1206 | ||
1207 | if (access(path, F_OK) < 0) { | |
1208 | log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path); | |
6af62124 WF |
1209 | continue; |
1210 | } | |
1211 | ||
6abdec98 | 1212 | r = strv_consume(&l, TAKE_PTR(path)); |
6af62124 WF |
1213 | if (r < 0) |
1214 | return r; | |
1215 | } | |
1216 | ||
bef41af2 | 1217 | if (strv_isempty(l)) { |
6af62124 | 1218 | log_debug("No devices found for system console"); |
bef41af2 | 1219 | goto fallback; |
6af62124 WF |
1220 | } |
1221 | ||
ae2a15bc | 1222 | *ret = TAKE_PTR(l); |
daf13202 | 1223 | return strv_length(*ret); |
bef41af2 LP |
1224 | |
1225 | fallback: | |
1226 | r = strv_extend(&l, "/dev/console"); | |
1227 | if (r < 0) | |
1228 | return r; | |
1229 | ||
ae2a15bc | 1230 | *ret = TAKE_PTR(l); |
6af62124 WF |
1231 | return 0; |
1232 | } | |
1233 | ||
288a74cc | 1234 | bool tty_is_vc_resolve(const char *tty) { |
7b912648 | 1235 | _cleanup_free_ char *resolved = NULL; |
288a74cc RC |
1236 | |
1237 | assert(tty); | |
1238 | ||
c7f7d87d | 1239 | if (streq(skip_dev_prefix(tty), "console")) { |
7b912648 | 1240 | if (resolve_dev_console(&resolved) < 0) |
288a74cc | 1241 | return false; |
7b912648 LP |
1242 | |
1243 | tty = resolved; | |
288a74cc RC |
1244 | } |
1245 | ||
1246 | return tty_is_vc(tty); | |
1247 | } | |
1248 | ||
288a74cc RC |
1249 | int fd_columns(int fd) { |
1250 | struct winsize ws = {}; | |
1251 | ||
14f594b9 LP |
1252 | if (fd < 0) |
1253 | return -EBADF; | |
1254 | ||
288a74cc RC |
1255 | if (ioctl(fd, TIOCGWINSZ, &ws) < 0) |
1256 | return -errno; | |
1257 | ||
1258 | if (ws.ws_col <= 0) | |
0ea4198f | 1259 | return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */ |
288a74cc RC |
1260 | |
1261 | return ws.ws_col; | |
1262 | } | |
1263 | ||
e8139b15 LP |
1264 | int getenv_columns(void) { |
1265 | int r; | |
1266 | ||
1267 | const char *e = getenv("COLUMNS"); | |
1268 | if (!e) | |
1269 | return -ENXIO; | |
1270 | ||
1271 | unsigned c; | |
1272 | r = safe_atou_bounded(e, 1, USHRT_MAX, &c); | |
1273 | if (r < 0) | |
1274 | return r; | |
1275 | ||
1276 | return (int) c; | |
1277 | } | |
1278 | ||
288a74cc | 1279 | unsigned columns(void) { |
288a74cc | 1280 | |
c6063244 | 1281 | if (cached_columns > 0) |
288a74cc RC |
1282 | return cached_columns; |
1283 | ||
e8139b15 LP |
1284 | int c = getenv_columns(); |
1285 | if (c < 0) { | |
288a74cc | 1286 | c = fd_columns(STDOUT_FILENO); |
e8139b15 | 1287 | if (c < 0) |
d09a7135 LP |
1288 | c = 80; |
1289 | } | |
288a74cc | 1290 | |
e8139b15 LP |
1291 | assert(c > 0); |
1292 | ||
288a74cc RC |
1293 | cached_columns = c; |
1294 | return cached_columns; | |
1295 | } | |
1296 | ||
1297 | int fd_lines(int fd) { | |
1298 | struct winsize ws = {}; | |
1299 | ||
14f594b9 LP |
1300 | if (fd < 0) |
1301 | return -EBADF; | |
1302 | ||
288a74cc RC |
1303 | if (ioctl(fd, TIOCGWINSZ, &ws) < 0) |
1304 | return -errno; | |
1305 | ||
1306 | if (ws.ws_row <= 0) | |
0ea4198f | 1307 | return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */ |
288a74cc RC |
1308 | |
1309 | return ws.ws_row; | |
1310 | } | |
1311 | ||
1312 | unsigned lines(void) { | |
1313 | const char *e; | |
1314 | int l; | |
1315 | ||
c6063244 | 1316 | if (cached_lines > 0) |
288a74cc RC |
1317 | return cached_lines; |
1318 | ||
1319 | l = 0; | |
1320 | e = getenv("LINES"); | |
1321 | if (e) | |
1322 | (void) safe_atoi(e, &l); | |
1323 | ||
d09a7135 | 1324 | if (l <= 0 || l > USHRT_MAX) { |
288a74cc | 1325 | l = fd_lines(STDOUT_FILENO); |
d09a7135 LP |
1326 | if (l <= 0) |
1327 | l = 24; | |
1328 | } | |
288a74cc RC |
1329 | |
1330 | cached_lines = l; | |
1331 | return cached_lines; | |
1332 | } | |
1333 | ||
51462135 DDM |
1334 | int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) { |
1335 | struct winsize ws; | |
1336 | ||
061b4458 LP |
1337 | assert(fd >= 0); |
1338 | ||
1339 | if (!ident) | |
1340 | ident = "TTY"; | |
1341 | ||
51462135 DDM |
1342 | if (rows == UINT_MAX && cols == UINT_MAX) |
1343 | return 0; | |
1344 | ||
1345 | if (ioctl(fd, TIOCGWINSZ, &ws) < 0) | |
1346 | return log_debug_errno(errno, | |
1347 | "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m", | |
061b4458 | 1348 | ident); |
51462135 DDM |
1349 | |
1350 | if (rows == UINT_MAX) | |
1351 | rows = ws.ws_row; | |
1352 | else if (rows > USHRT_MAX) | |
1353 | rows = USHRT_MAX; | |
1354 | ||
1355 | if (cols == UINT_MAX) | |
1356 | cols = ws.ws_col; | |
1357 | else if (cols > USHRT_MAX) | |
1358 | cols = USHRT_MAX; | |
1359 | ||
1360 | if (rows == ws.ws_row && cols == ws.ws_col) | |
1361 | return 0; | |
1362 | ||
1363 | ws.ws_row = rows; | |
1364 | ws.ws_col = cols; | |
1365 | ||
1366 | if (ioctl(fd, TIOCSWINSZ, &ws) < 0) | |
061b4458 | 1367 | return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident); |
51462135 DDM |
1368 | |
1369 | return 0; | |
1370 | } | |
1371 | ||
29f5a5ae DDM |
1372 | int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) { |
1373 | _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL; | |
1374 | unsigned rows = UINT_MAX, cols = UINT_MAX; | |
1375 | int r; | |
1376 | ||
1377 | assert(tty); | |
1378 | ||
1379 | if (!ret_rows && !ret_cols) | |
1380 | return 0; | |
1381 | ||
1382 | tty = skip_dev_prefix(tty); | |
7036dd8b MY |
1383 | if (path_startswith(tty, "pts/")) |
1384 | return -EMEDIUMTYPE; | |
29f5a5ae | 1385 | if (!in_charset(tty, ALPHANUMERICAL)) |
7036dd8b MY |
1386 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), |
1387 | "TTY name '%s' contains non-alphanumeric characters, not searching kernel cmdline for size.", tty); | |
29f5a5ae DDM |
1388 | |
1389 | rowskey = strjoin("systemd.tty.rows.", tty); | |
1390 | if (!rowskey) | |
1391 | return -ENOMEM; | |
1392 | ||
1393 | colskey = strjoin("systemd.tty.columns.", tty); | |
1394 | if (!colskey) | |
1395 | return -ENOMEM; | |
1396 | ||
1397 | r = proc_cmdline_get_key_many(/* flags = */ 0, | |
1398 | rowskey, &rowsvalue, | |
1399 | colskey, &colsvalue); | |
1400 | if (r < 0) | |
1401 | return log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline: %m", tty); | |
1402 | ||
1403 | if (rowsvalue) { | |
1404 | r = safe_atou(rowsvalue, &rows); | |
1405 | if (r < 0) | |
1406 | return log_debug_errno(r, "Failed to parse %s=%s: %m", rowskey, rowsvalue); | |
1407 | } | |
1408 | ||
1409 | if (colsvalue) { | |
1410 | r = safe_atou(colsvalue, &cols); | |
1411 | if (r < 0) | |
1412 | return log_debug_errno(r, "Failed to parse %s=%s: %m", colskey, colsvalue); | |
1413 | } | |
1414 | ||
1415 | if (ret_rows) | |
1416 | *ret_rows = rows; | |
1417 | if (ret_cols) | |
1418 | *ret_cols = cols; | |
1419 | ||
f6927e3c | 1420 | return rows != UINT_MAX || cols != UINT_MAX; |
29f5a5ae DDM |
1421 | } |
1422 | ||
288a74cc RC |
1423 | /* intended to be used as a SIGWINCH sighandler */ |
1424 | void columns_lines_cache_reset(int signum) { | |
1425 | cached_columns = 0; | |
1426 | cached_lines = 0; | |
1427 | } | |
1428 | ||
c6063244 LP |
1429 | void reset_terminal_feature_caches(void) { |
1430 | cached_columns = 0; | |
1431 | cached_lines = 0; | |
1432 | ||
c6063244 | 1433 | cached_on_tty = -1; |
197dd3a9 | 1434 | cached_on_dev_null = -1; |
7227d5bf DDM |
1435 | |
1436 | reset_ansi_feature_caches(); | |
c6063244 | 1437 | } |
288a74cc | 1438 | |
c6063244 | 1439 | bool on_tty(void) { |
8cd0356e LP |
1440 | |
1441 | /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably | |
1442 | * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally | |
1443 | * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive | |
1444 | * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy | |
1445 | * terminal functionality when outputting stuff, even if the input is piped to us. */ | |
1446 | ||
c6063244 | 1447 | if (cached_on_tty < 0) |
8cd0356e | 1448 | cached_on_tty = |
300b7e76 LP |
1449 | isatty_safe(STDOUT_FILENO) && |
1450 | isatty_safe(STDERR_FILENO); | |
288a74cc RC |
1451 | |
1452 | return cached_on_tty; | |
1453 | } | |
1454 | ||
288a74cc | 1455 | int getttyname_malloc(int fd, char **ret) { |
454318d3 | 1456 | char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */ |
288a74cc RC |
1457 | int r; |
1458 | ||
1459 | assert(fd >= 0); | |
1460 | assert(ret); | |
1461 | ||
30222f4b ZJS |
1462 | r = ttyname_r(fd, path, sizeof path); /* positive error */ |
1463 | assert(r >= 0); | |
1464 | if (r == ERANGE) | |
1465 | return -ENAMETOOLONG; | |
1466 | if (r > 0) | |
1467 | return -r; | |
288a74cc | 1468 | |
454318d3 | 1469 | return strdup_to(ret, skip_dev_prefix(path)); |
288a74cc RC |
1470 | } |
1471 | ||
f171decd LP |
1472 | int getttyname_harder(int fd, char **ret) { |
1473 | _cleanup_free_ char *s = NULL; | |
1474 | int r; | |
288a74cc | 1475 | |
f171decd LP |
1476 | r = getttyname_malloc(fd, &s); |
1477 | if (r < 0) | |
1478 | return r; | |
288a74cc | 1479 | |
f171decd LP |
1480 | if (streq(s, "tty")) |
1481 | return get_ctty(0, NULL, ret); | |
288a74cc | 1482 | |
f171decd | 1483 | *ret = TAKE_PTR(s); |
288a74cc RC |
1484 | return 0; |
1485 | } | |
1486 | ||
ac508b11 | 1487 | int get_ctty_devnr(pid_t pid, dev_t *ret) { |
288a74cc | 1488 | _cleanup_free_ char *line = NULL; |
288a74cc | 1489 | unsigned long ttynr; |
ac508b11 LP |
1490 | const char *p; |
1491 | int r; | |
288a74cc RC |
1492 | |
1493 | assert(pid >= 0); | |
1494 | ||
1495 | p = procfs_file_alloca(pid, "stat"); | |
1496 | r = read_one_line_file(p, &line); | |
1497 | if (r < 0) | |
1498 | return r; | |
1499 | ||
1500 | p = strrchr(line, ')'); | |
1501 | if (!p) | |
1502 | return -EIO; | |
1503 | ||
1504 | p++; | |
1505 | ||
1506 | if (sscanf(p, " " | |
1507 | "%*c " /* state */ | |
1508 | "%*d " /* ppid */ | |
1509 | "%*d " /* pgrp */ | |
1510 | "%*d " /* session */ | |
1511 | "%lu ", /* ttynr */ | |
1512 | &ttynr) != 1) | |
1513 | return -EIO; | |
1514 | ||
d80e2a1e | 1515 | if (devnum_is_zero(ttynr)) |
cfeaa44a | 1516 | return -ENXIO; |
288a74cc | 1517 | |
ac508b11 LP |
1518 | if (ret) |
1519 | *ret = (dev_t) ttynr; | |
288a74cc RC |
1520 | |
1521 | return 0; | |
1522 | } | |
1523 | ||
54b22b26 | 1524 | int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) { |
11f3c130 LP |
1525 | char pty[STRLEN("/dev/pts/") + DECIMAL_STR_MAX(dev_t) + 1]; |
1526 | _cleanup_free_ char *buf = NULL; | |
1527 | const char *fn = NULL, *w; | |
288a74cc | 1528 | dev_t devnr; |
54b22b26 | 1529 | int r; |
288a74cc | 1530 | |
54b22b26 LP |
1531 | r = get_ctty_devnr(pid, &devnr); |
1532 | if (r < 0) | |
1533 | return r; | |
288a74cc | 1534 | |
11f3c130 | 1535 | r = device_path_make_canonical(S_IFCHR, devnr, &buf); |
54b22b26 | 1536 | if (r < 0) { |
11f3c130 LP |
1537 | struct stat st; |
1538 | ||
54b22b26 LP |
1539 | if (r != -ENOENT) /* No symlink for this in /dev/char/? */ |
1540 | return r; | |
288a74cc | 1541 | |
11f3c130 LP |
1542 | /* Maybe this is PTY? PTY devices are not listed in /dev/char/, as they don't follow the |
1543 | * Linux device model and hence device_path_make_canonical() doesn't work for them. Let's | |
1544 | * assume this is a PTY for a moment, and check if the device node this would then map to in | |
1545 | * /dev/pts/ matches the one we are looking for. This way we don't have to hardcode the major | |
1546 | * number (which is 136 btw), but we still rely on the fact that PTY numbers map directly to | |
1547 | * the minor number of the pty. */ | |
1548 | xsprintf(pty, "/dev/pts/%u", minor(devnr)); | |
1549 | ||
1550 | if (stat(pty, &st) < 0) { | |
1551 | if (errno != ENOENT) | |
1552 | return -errno; | |
54b22b26 | 1553 | |
11f3c130 LP |
1554 | } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */ |
1555 | fn = pty; | |
288a74cc | 1556 | |
11f3c130 LP |
1557 | if (!fn) { |
1558 | /* Doesn't exist, or not a PTY? Probably something similar to the PTYs which have no | |
1559 | * symlink in /dev/char/. Let's return something vaguely useful. */ | |
1560 | r = device_path_make_major_minor(S_IFCHR, devnr, &buf); | |
54b22b26 LP |
1561 | if (r < 0) |
1562 | return r; | |
11f3c130 LP |
1563 | |
1564 | fn = buf; | |
288a74cc | 1565 | } |
11f3c130 LP |
1566 | } else |
1567 | fn = buf; | |
288a74cc | 1568 | |
11f3c130 LP |
1569 | w = path_startswith(fn, "/dev/"); |
1570 | if (!w) | |
1571 | return -EINVAL; | |
54b22b26 | 1572 | |
11f3c130 | 1573 | if (ret) { |
4f77ddca ZJS |
1574 | r = strdup_to(ret, w); |
1575 | if (r < 0) | |
1576 | return r; | |
11f3c130 | 1577 | } |
54b22b26 LP |
1578 | |
1579 | if (ret_devnr) | |
1580 | *ret_devnr = devnr; | |
288a74cc RC |
1581 | |
1582 | return 0; | |
1583 | } | |
a07c35c3 | 1584 | |
66cb2fde | 1585 | int ptsname_malloc(int fd, char **ret) { |
66cb2fde LP |
1586 | assert(fd >= 0); |
1587 | assert(ret); | |
1588 | ||
deedd5c2 MY |
1589 | for (size_t l = 50;;) { |
1590 | _cleanup_free_ char *c = NULL; | |
66cb2fde LP |
1591 | |
1592 | c = new(char, l); | |
1593 | if (!c) | |
1594 | return -ENOMEM; | |
1595 | ||
deedd5c2 MY |
1596 | if (ptsname_r(fd, c, l) >= 0) { |
1597 | *ret = TAKE_PTR(c); | |
66cb2fde LP |
1598 | return 0; |
1599 | } | |
deedd5c2 | 1600 | if (errno != ERANGE) |
66cb2fde | 1601 | return -errno; |
1fd4c4ed | 1602 | |
deedd5c2 | 1603 | if (!MUL_ASSIGN_SAFE(&l, 2)) |
1fd4c4ed | 1604 | return -ENOMEM; |
66cb2fde LP |
1605 | } |
1606 | } | |
1607 | ||
fbd2679f | 1608 | int openpt_allocate(int flags, char **ret_peer_path) { |
254d1313 | 1609 | _cleanup_close_ int fd = -EBADF; |
ae1d13db FB |
1610 | int r; |
1611 | ||
1612 | fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); | |
1613 | if (fd < 0) | |
1614 | return -errno; | |
1615 | ||
fbd2679f LP |
1616 | _cleanup_free_ char *p = NULL; |
1617 | if (ret_peer_path) { | |
ae1d13db FB |
1618 | r = ptsname_malloc(fd, &p); |
1619 | if (r < 0) | |
1620 | return r; | |
1621 | ||
1622 | if (!path_startswith(p, "/dev/pts/")) | |
1623 | return -EINVAL; | |
1624 | } | |
1625 | ||
1626 | if (unlockpt(fd) < 0) | |
1627 | return -errno; | |
1628 | ||
fbd2679f LP |
1629 | if (ret_peer_path) |
1630 | *ret_peer_path = TAKE_PTR(p); | |
ae1d13db FB |
1631 | |
1632 | return TAKE_FD(fd); | |
1633 | } | |
1634 | ||
1635 | static int ptsname_namespace(int pty, char **ret) { | |
fbd2679f LP |
1636 | int no = -1; |
1637 | ||
1638 | assert(pty >= 0); | |
1639 | assert(ret); | |
a07c35c3 LP |
1640 | |
1641 | /* Like ptsname(), but doesn't assume that the path is | |
1642 | * accessible in the local namespace. */ | |
1643 | ||
fbd2679f | 1644 | if (ioctl(pty, TIOCGPTN, &no) < 0) |
a07c35c3 LP |
1645 | return -errno; |
1646 | ||
1647 | if (no < 0) | |
1648 | return -EIO; | |
1649 | ||
1650 | if (asprintf(ret, "/dev/pts/%i", no) < 0) | |
1651 | return -ENOMEM; | |
1652 | ||
1653 | return 0; | |
1654 | } | |
66cb2fde | 1655 | |
b25430de LP |
1656 | int openpt_allocate_in_namespace( |
1657 | const PidRef *pidref, | |
1658 | int flags, | |
1659 | char **ret_peer_path) { | |
1660 | ||
254d1313 | 1661 | _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF; |
71136404 | 1662 | _cleanup_close_pair_ int pair[2] = EBADF_PAIR; |
66cb2fde LP |
1663 | int r; |
1664 | ||
b25430de | 1665 | r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); |
66cb2fde LP |
1666 | if (r < 0) |
1667 | return r; | |
1668 | ||
fbd2679f | 1669 | if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0) |
66cb2fde LP |
1670 | return -errno; |
1671 | ||
fbd2679f LP |
1672 | r = namespace_fork( |
1673 | "(sd-openptns)", | |
1674 | "(sd-openpt)", | |
1675 | /* except_fds= */ NULL, | |
1676 | /* n_except_fds= */ 0, | |
1677 | FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, | |
1678 | pidnsfd, | |
1679 | mntnsfd, | |
1680 | /* netns_fd= */ -EBADF, | |
1681 | usernsfd, | |
1682 | rootfd, | |
1683 | /* ret_pid= */ NULL); | |
4c253ed1 LP |
1684 | if (r < 0) |
1685 | return r; | |
1686 | if (r == 0) { | |
66cb2fde LP |
1687 | pair[0] = safe_close(pair[0]); |
1688 | ||
fbd2679f | 1689 | fd = openpt_allocate(flags, /* ret_peer_path= */ NULL); |
ae1d13db | 1690 | if (fd < 0) |
66cb2fde LP |
1691 | _exit(EXIT_FAILURE); |
1692 | ||
ae1d13db | 1693 | if (send_one_fd(pair[1], fd, 0) < 0) |
66cb2fde LP |
1694 | _exit(EXIT_FAILURE); |
1695 | ||
1696 | _exit(EXIT_SUCCESS); | |
1697 | } | |
1698 | ||
1699 | pair[1] = safe_close(pair[1]); | |
1700 | ||
ae1d13db FB |
1701 | fd = receive_one_fd(pair[0], 0); |
1702 | if (fd < 0) | |
1703 | return fd; | |
1704 | ||
fbd2679f LP |
1705 | if (ret_peer_path) { |
1706 | r = ptsname_namespace(fd, ret_peer_path); | |
ae1d13db FB |
1707 | if (r < 0) |
1708 | return r; | |
1709 | } | |
1710 | ||
1711 | return TAKE_FD(fd); | |
66cb2fde | 1712 | } |
40e1f4ea | 1713 | |
197dd3a9 DDM |
1714 | static bool on_dev_null(void) { |
1715 | struct stat dst, ost, est; | |
1716 | ||
1717 | if (cached_on_dev_null >= 0) | |
1718 | return cached_on_dev_null; | |
1719 | ||
1720 | if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0) | |
1721 | cached_on_dev_null = false; | |
1722 | else | |
1723 | cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est); | |
1724 | ||
1725 | return cached_on_dev_null; | |
1726 | } | |
1727 | ||
1b889631 | 1728 | bool getenv_terminal_is_dumb(void) { |
ac96418b LP |
1729 | const char *e; |
1730 | ||
ac96418b LP |
1731 | e = getenv("TERM"); |
1732 | if (!e) | |
1733 | return true; | |
1734 | ||
1735 | return streq(e, "dumb"); | |
1736 | } | |
1737 | ||
158fbf76 | 1738 | bool terminal_is_dumb(void) { |
197dd3a9 | 1739 | if (!on_tty() && !on_dev_null()) |
158fbf76 ZJS |
1740 | return true; |
1741 | ||
1742 | return getenv_terminal_is_dumb(); | |
1743 | } | |
1744 | ||
c2b32159 LP |
1745 | bool dev_console_colors_enabled(void) { |
1746 | _cleanup_free_ char *s = NULL; | |
c4fea19a | 1747 | ColorMode m; |
c2b32159 LP |
1748 | |
1749 | /* Returns true if we assume that color is supported on /dev/console. | |
1750 | * | |
1751 | * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that | |
f95dbcc2 ZJS |
1752 | * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command |
1753 | * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular | |
c2b32159 LP |
1754 | * colors_enabled() operates. */ |
1755 | ||
c4fea19a MGR |
1756 | m = parse_systemd_colors(); |
1757 | if (m >= 0) | |
1758 | return m; | |
c2b32159 | 1759 | |
c484315b ZJS |
1760 | if (getenv("NO_COLOR")) |
1761 | return false; | |
1762 | ||
c2b32159 LP |
1763 | if (getenv_for_pid(1, "TERM", &s) <= 0) |
1764 | (void) proc_cmdline_get_key("TERM", 0, &s); | |
1765 | ||
1766 | return !streq_ptr(s, "dumb"); | |
1767 | } | |
1768 | ||
6179ede1 | 1769 | int vt_restore(int fd) { |
d3f818fe | 1770 | |
6179ede1 FB |
1771 | static const struct vt_mode mode = { |
1772 | .mode = VT_AUTO, | |
1773 | }; | |
d3f818fe MY |
1774 | |
1775 | int r, ret = 0; | |
1776 | ||
1777 | assert(fd >= 0); | |
6179ede1 | 1778 | |
dd9c8da8 | 1779 | if (!isatty_safe(fd)) |
aae47bf7 | 1780 | return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to restore the VT for an fd that does not refer to a terminal: %m"); |
e60a4a3c | 1781 | |
1802d5f2 | 1782 | if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) |
d3f818fe | 1783 | RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m")); |
6179ede1 FB |
1784 | |
1785 | r = vt_reset_keyboard(fd); | |
d3f818fe MY |
1786 | if (r < 0) |
1787 | RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m")); | |
6179ede1 | 1788 | |
d3f818fe MY |
1789 | if (ioctl(fd, VT_SETMODE, &mode) < 0) |
1790 | RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m")); | |
6179ede1 | 1791 | |
f5fbe71d | 1792 | r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID); |
d3f818fe MY |
1793 | if (r < 0) |
1794 | RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m")); | |
6179ede1 | 1795 | |
d3f818fe | 1796 | return ret; |
6179ede1 | 1797 | } |
27dafac9 FB |
1798 | |
1799 | int vt_release(int fd, bool restore) { | |
1800 | assert(fd >= 0); | |
1801 | ||
1802 | /* This function releases the VT by acknowledging the VT-switch signal | |
1803 | * sent by the kernel and optionally reset the VT in text and auto | |
1804 | * VT-switching modes. */ | |
1805 | ||
dd9c8da8 | 1806 | if (!isatty_safe(fd)) |
aae47bf7 | 1807 | return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to release the VT for an fd that does not refer to a terminal: %m"); |
e60a4a3c | 1808 | |
27dafac9 FB |
1809 | if (ioctl(fd, VT_RELDISP, 1) < 0) |
1810 | return -errno; | |
1811 | ||
1812 | if (restore) | |
1813 | return vt_restore(fd); | |
1814 | ||
1815 | return 0; | |
1816 | } | |
37b8d2f6 ZJS |
1817 | |
1818 | void get_log_colors(int priority, const char **on, const char **off, const char **highlight) { | |
1819 | /* Note that this will initialize output variables only when there's something to output. | |
5e2b0e1c | 1820 | * The caller must pre-initialize to "" or NULL as appropriate. */ |
37b8d2f6 ZJS |
1821 | |
1822 | if (priority <= LOG_ERR) { | |
1823 | if (on) | |
25e4608b | 1824 | *on = ansi_highlight_red(); |
37b8d2f6 | 1825 | if (off) |
bb146d23 | 1826 | *off = ansi_normal(); |
37b8d2f6 | 1827 | if (highlight) |
bb146d23 | 1828 | *highlight = ansi_highlight(); |
37b8d2f6 | 1829 | |
0d0464d3 ZJS |
1830 | } else if (priority <= LOG_WARNING) { |
1831 | if (on) | |
25e4608b | 1832 | *on = ansi_highlight_yellow(); |
0d0464d3 | 1833 | if (off) |
bb146d23 | 1834 | *off = ansi_normal(); |
0d0464d3 | 1835 | if (highlight) |
bb146d23 | 1836 | *highlight = ansi_highlight(); |
0d0464d3 | 1837 | |
37b8d2f6 ZJS |
1838 | } else if (priority <= LOG_NOTICE) { |
1839 | if (on) | |
bb146d23 | 1840 | *on = ansi_highlight(); |
37b8d2f6 | 1841 | if (off) |
bb146d23 | 1842 | *off = ansi_normal(); |
37b8d2f6 | 1843 | if (highlight) |
25e4608b | 1844 | *highlight = ansi_highlight_red(); |
37b8d2f6 ZJS |
1845 | |
1846 | } else if (priority >= LOG_DEBUG) { | |
1847 | if (on) | |
25e4608b | 1848 | *on = ansi_grey(); |
37b8d2f6 | 1849 | if (off) |
bb146d23 | 1850 | *off = ansi_normal(); |
37b8d2f6 | 1851 | if (highlight) |
25e4608b | 1852 | *highlight = ansi_highlight_red(); |
37b8d2f6 ZJS |
1853 | } |
1854 | } | |
fc7eb132 | 1855 | |
53f0ab51 | 1856 | int terminal_set_cursor_position(int fd, unsigned row, unsigned column) { |
fc7eb132 OJ |
1857 | assert(fd >= 0); |
1858 | ||
53f0ab51 | 1859 | char cursor_position[STRLEN("\x1B[" ";" "H") + DECIMAL_STR_MAX(unsigned) * 2 + 1]; |
fc7eb132 OJ |
1860 | xsprintf(cursor_position, "\x1B[%u;%uH", row, column); |
1861 | ||
53f0ab51 | 1862 | return loop_write(fd, cursor_position, SIZE_MAX); |
fc7eb132 | 1863 | } |
d02d4f83 | 1864 | |
9ab703d8 | 1865 | int terminal_reset_defensive(int fd, TerminalResetFlags flags) { |
cfac0908 LP |
1866 | int r = 0; |
1867 | ||
1868 | assert(fd >= 0); | |
5b3eaf9e | 1869 | assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ)); |
cfac0908 | 1870 | |
5b3eaf9e LP |
1871 | /* Resets the terminal comprehensively, i.e. via both ioctl()s and via ANSI sequences, but do so only |
1872 | * if $TERM is unset or set to "dumb" */ | |
cfac0908 | 1873 | |
e2216800 LP |
1874 | if (!isatty_safe(fd)) |
1875 | return -ENOTTY; | |
1876 | ||
9ab703d8 | 1877 | RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT))); |
cfac0908 | 1878 | |
5b3eaf9e LP |
1879 | if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) && |
1880 | (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb())) | |
cfac0908 LP |
1881 | RET_GATHER(r, terminal_reset_ansi_seq(fd)); |
1882 | ||
1883 | return r; | |
1884 | } | |
1885 | ||
9ab703d8 | 1886 | int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) { |
2cd19499 LP |
1887 | assert(fd >= 0); |
1888 | ||
1889 | _cleanup_close_ int lock_fd = lock_dev_console(); | |
1890 | if (lock_fd < 0) | |
1891 | log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m"); | |
1892 | ||
9ab703d8 | 1893 | return terminal_reset_defensive(fd, flags); |
2cd19499 LP |
1894 | } |
1895 | ||
d02d4f83 LP |
1896 | void termios_disable_echo(struct termios *termios) { |
1897 | assert(termios); | |
1898 | ||
1899 | termios->c_lflag &= ~(ICANON|ECHO); | |
1900 | termios->c_cc[VMIN] = 1; | |
1901 | termios->c_cc[VTIME] = 0; | |
1902 | } | |
63e9c383 | 1903 | |
445e5738 LP |
1904 | static int terminal_verify_same(int input_fd, int output_fd) { |
1905 | assert(input_fd >= 0); | |
1906 | assert(output_fd >= 0); | |
1907 | ||
1908 | /* Validates that the specified fds reference the same TTY */ | |
1909 | ||
1910 | if (input_fd != output_fd) { | |
1911 | struct stat sti; | |
1912 | if (fstat(input_fd, &sti) < 0) | |
1913 | return -errno; | |
1914 | ||
1915 | if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */ | |
1916 | return -ENOTTY; | |
1917 | ||
1918 | struct stat sto; | |
1919 | if (fstat(output_fd, &sto) < 0) | |
1920 | return -errno; | |
1921 | ||
1922 | if (!S_ISCHR(sto.st_mode)) | |
1923 | return -ENOTTY; | |
1924 | ||
1925 | if (sti.st_rdev != sto.st_rdev) | |
1926 | return -ENOLINK; | |
1927 | } | |
1928 | ||
1929 | if (!isatty_safe(input_fd)) /* The check above was just for char device, but now let's ensure it's actually a tty */ | |
1930 | return -ENOTTY; | |
1931 | ||
1932 | return 0; | |
1933 | } | |
1934 | ||
63e9c383 LP |
1935 | typedef enum BackgroundColorState { |
1936 | BACKGROUND_TEXT, | |
1937 | BACKGROUND_ESCAPE, | |
1938 | BACKGROUND_BRACKET, | |
1939 | BACKGROUND_FIRST_ONE, | |
1940 | BACKGROUND_SECOND_ONE, | |
1941 | BACKGROUND_SEMICOLON, | |
1942 | BACKGROUND_R, | |
1943 | BACKGROUND_G, | |
1944 | BACKGROUND_B, | |
1945 | BACKGROUND_RED, | |
1946 | BACKGROUND_GREEN, | |
1947 | BACKGROUND_BLUE, | |
73a72e3a | 1948 | BACKGROUND_STRING_TERMINATOR, |
63e9c383 LP |
1949 | } BackgroundColorState; |
1950 | ||
1951 | typedef struct BackgroundColorContext { | |
1952 | BackgroundColorState state; | |
1953 | uint32_t red, green, blue; | |
1954 | unsigned red_bits, green_bits, blue_bits; | |
1955 | } BackgroundColorContext; | |
1956 | ||
1957 | static int scan_background_color_response( | |
1958 | BackgroundColorContext *context, | |
1959 | const char *buf, | |
1df569b2 LP |
1960 | size_t size, |
1961 | size_t *ret_processed) { | |
63e9c383 LP |
1962 | |
1963 | assert(context); | |
f4bdf373 ZJS |
1964 | assert(buf); |
1965 | assert(ret_processed); | |
63e9c383 LP |
1966 | |
1967 | for (size_t i = 0; i < size; i++) { | |
1968 | char c = buf[i]; | |
1969 | ||
1970 | switch (context->state) { | |
1971 | ||
1972 | case BACKGROUND_TEXT: | |
1973 | context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT; | |
1974 | break; | |
1975 | ||
1976 | case BACKGROUND_ESCAPE: | |
1977 | context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT; | |
1978 | break; | |
1979 | ||
1980 | case BACKGROUND_BRACKET: | |
1981 | context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT; | |
1982 | break; | |
1983 | ||
1984 | case BACKGROUND_FIRST_ONE: | |
1985 | context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT; | |
1986 | break; | |
1987 | ||
1988 | case BACKGROUND_SECOND_ONE: | |
1989 | context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT; | |
1990 | break; | |
1991 | ||
1992 | case BACKGROUND_SEMICOLON: | |
1993 | context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT; | |
1994 | break; | |
1995 | ||
1996 | case BACKGROUND_R: | |
1997 | context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT; | |
1998 | break; | |
1999 | ||
2000 | case BACKGROUND_G: | |
2001 | context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT; | |
2002 | break; | |
2003 | ||
2004 | case BACKGROUND_B: | |
2005 | context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT; | |
2006 | break; | |
2007 | ||
2008 | case BACKGROUND_RED: | |
2009 | if (c == '/') | |
2010 | context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT; | |
2011 | else { | |
2012 | int d = unhexchar(c); | |
2013 | if (d < 0 || context->red_bits >= sizeof(context->red)*8) | |
2014 | context->state = BACKGROUND_TEXT; | |
2015 | else { | |
2016 | context->red = (context->red << 4) | d; | |
2017 | context->red_bits += 4; | |
2018 | } | |
2019 | } | |
2020 | break; | |
2021 | ||
2022 | case BACKGROUND_GREEN: | |
2023 | if (c == '/') | |
2024 | context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT; | |
2025 | else { | |
2026 | int d = unhexchar(c); | |
2027 | if (d < 0 || context->green_bits >= sizeof(context->green)*8) | |
2028 | context->state = BACKGROUND_TEXT; | |
2029 | else { | |
2030 | context->green = (context->green << 4) | d; | |
2031 | context->green_bits += 4; | |
2032 | } | |
2033 | } | |
2034 | break; | |
2035 | ||
2036 | case BACKGROUND_BLUE: | |
2037 | if (c == '\x07') { | |
1df569b2 | 2038 | if (context->blue_bits > 0) { |
f4bdf373 | 2039 | *ret_processed = i + 1; |
63e9c383 | 2040 | return 1; /* success! */ |
1df569b2 | 2041 | } |
63e9c383 LP |
2042 | |
2043 | context->state = BACKGROUND_TEXT; | |
73a72e3a SL |
2044 | } else if (c == '\x1b') |
2045 | context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT; | |
2046 | else { | |
63e9c383 LP |
2047 | int d = unhexchar(c); |
2048 | if (d < 0 || context->blue_bits >= sizeof(context->blue)*8) | |
2049 | context->state = BACKGROUND_TEXT; | |
2050 | else { | |
2051 | context->blue = (context->blue << 4) | d; | |
2052 | context->blue_bits += 4; | |
2053 | } | |
2054 | } | |
2055 | break; | |
73a72e3a SL |
2056 | |
2057 | case BACKGROUND_STRING_TERMINATOR: | |
1df569b2 | 2058 | if (c == '\\') { |
f4bdf373 | 2059 | *ret_processed = i + 1; |
73a72e3a | 2060 | return 1; /* success! */ |
1df569b2 | 2061 | } |
73a72e3a SL |
2062 | |
2063 | context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT; | |
2064 | break; | |
2065 | ||
63e9c383 LP |
2066 | } |
2067 | ||
2068 | /* Reset any colors we might have picked up */ | |
73a72e3a | 2069 | if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) { |
63e9c383 LP |
2070 | /* reset color */ |
2071 | context->red = context->green = context->blue = 0; | |
2072 | context->red_bits = context->green_bits = context->blue_bits = 0; | |
2073 | } | |
2074 | } | |
2075 | ||
f4bdf373 | 2076 | *ret_processed = size; |
63e9c383 LP |
2077 | return 0; /* all good, but not enough data yet */ |
2078 | } | |
2079 | ||
2080 | int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) { | |
a0c314d6 | 2081 | _cleanup_close_ int nonblock_input_fd = -EBADF; |
63e9c383 LP |
2082 | int r; |
2083 | ||
2084 | assert(ret_red); | |
2085 | assert(ret_green); | |
2086 | assert(ret_blue); | |
2087 | ||
2088 | if (!colors_enabled()) | |
2089 | return -EOPNOTSUPP; | |
2090 | ||
445e5738 LP |
2091 | r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO); |
2092 | if (r < 0) | |
2093 | return r; | |
63e9c383 LP |
2094 | |
2095 | if (streq_ptr(getenv("TERM"), "linux")) { | |
2096 | /* Linux console is black */ | |
2097 | *ret_red = *ret_green = *ret_blue = 0.0; | |
2098 | return 0; | |
2099 | } | |
2100 | ||
2101 | struct termios old_termios; | |
2102 | if (tcgetattr(STDIN_FILENO, &old_termios) < 0) | |
2103 | return -errno; | |
2104 | ||
2105 | struct termios new_termios = old_termios; | |
2106 | termios_disable_echo(&new_termios); | |
2107 | ||
f789b17e | 2108 | if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) < 0) |
63e9c383 LP |
2109 | return -errno; |
2110 | ||
03674247 | 2111 | r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX); |
63e9c383 LP |
2112 | if (r < 0) |
2113 | goto finish; | |
2114 | ||
a0c314d6 LP |
2115 | /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone |
2116 | * else process the POLLIN. */ | |
2117 | ||
13cb6641 MY |
2118 | nonblock_input_fd = r = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); |
2119 | if (r < 0) | |
2120 | goto finish; | |
a0c314d6 | 2121 | |
5321b957 | 2122 | usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); |
03674247 | 2123 | char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */ |
63e9c383 LP |
2124 | size_t buf_full = 0; |
2125 | BackgroundColorContext context = {}; | |
2126 | ||
abe8e99e | 2127 | for (bool first = true;; first = false) { |
1df569b2 LP |
2128 | if (buf_full == 0) { |
2129 | usec_t n = now(CLOCK_MONOTONIC); | |
1df569b2 LP |
2130 | if (n >= end) { |
2131 | r = -EOPNOTSUPP; | |
2132 | goto finish; | |
2133 | } | |
63e9c383 | 2134 | |
a0c314d6 | 2135 | r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); |
1df569b2 LP |
2136 | if (r < 0) |
2137 | goto finish; | |
2138 | if (r == 0) { | |
2139 | r = -EOPNOTSUPP; | |
2140 | goto finish; | |
2141 | } | |
63e9c383 | 2142 | |
abe8e99e LP |
2143 | /* On the first try, read multiple characters, i.e. the shortest valid |
2144 | * reply. Afterwards read byte-wise, since we don't want to read too much, and | |
2145 | * unnecessarily drop too many characters from the input queue. */ | |
a0c314d6 | 2146 | ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1); |
1df569b2 | 2147 | if (l < 0) { |
a0c314d6 LP |
2148 | if (errno == EAGAIN) |
2149 | continue; | |
1df569b2 LP |
2150 | r = -errno; |
2151 | goto finish; | |
2152 | } | |
63e9c383 | 2153 | |
1df569b2 LP |
2154 | assert((size_t) l <= sizeof(buf)); |
2155 | buf_full = l; | |
2156 | } | |
63e9c383 | 2157 | |
1df569b2 LP |
2158 | size_t processed; |
2159 | r = scan_background_color_response(&context, buf, buf_full, &processed); | |
63e9c383 LP |
2160 | if (r < 0) |
2161 | goto finish; | |
1df569b2 LP |
2162 | |
2163 | assert(processed <= buf_full); | |
2164 | buf_full -= processed; | |
2165 | memmove(buf, buf + processed, buf_full); | |
2166 | ||
63e9c383 LP |
2167 | if (r > 0) { |
2168 | assert(context.red_bits > 0); | |
2169 | *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1); | |
2170 | assert(context.green_bits > 0); | |
2171 | *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1); | |
2172 | assert(context.blue_bits > 0); | |
2173 | *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1); | |
2174 | r = 0; | |
2175 | goto finish; | |
2176 | } | |
2177 | } | |
2178 | ||
2179 | finish: | |
f789b17e | 2180 | RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSANOW, &old_termios))); |
63e9c383 LP |
2181 | return r; |
2182 | } | |
3390be38 LP |
2183 | |
2184 | typedef enum CursorPositionState { | |
2185 | CURSOR_TEXT, | |
2186 | CURSOR_ESCAPE, | |
2187 | CURSOR_ROW, | |
2188 | CURSOR_COLUMN, | |
2189 | } CursorPositionState; | |
2190 | ||
2191 | typedef struct CursorPositionContext { | |
2192 | CursorPositionState state; | |
2193 | unsigned row, column; | |
2194 | } CursorPositionContext; | |
2195 | ||
2196 | static int scan_cursor_position_response( | |
2197 | CursorPositionContext *context, | |
2198 | const char *buf, | |
2199 | size_t size, | |
2200 | size_t *ret_processed) { | |
2201 | ||
2202 | assert(context); | |
f4bdf373 ZJS |
2203 | assert(buf); |
2204 | assert(ret_processed); | |
3390be38 LP |
2205 | |
2206 | for (size_t i = 0; i < size; i++) { | |
2207 | char c = buf[i]; | |
2208 | ||
2209 | switch (context->state) { | |
2210 | ||
2211 | case CURSOR_TEXT: | |
2212 | context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT; | |
2213 | break; | |
2214 | ||
2215 | case CURSOR_ESCAPE: | |
2216 | context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT; | |
2217 | break; | |
2218 | ||
2219 | case CURSOR_ROW: | |
2220 | if (c == ';') | |
2221 | context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT; | |
2222 | else { | |
2223 | int d = undecchar(c); | |
2224 | ||
2225 | /* We read a decimal character, let's suffix it to the number we so far read, | |
2226 | * but let's do an overflow check first. */ | |
2227 | if (d < 0 || context->row > (UINT_MAX-d)/10) | |
2228 | context->state = CURSOR_TEXT; | |
2229 | else | |
2230 | context->row = context->row * 10 + d; | |
2231 | } | |
2232 | break; | |
2233 | ||
2234 | case CURSOR_COLUMN: | |
2235 | if (c == 'R') { | |
2236 | if (context->column > 0) { | |
f4bdf373 | 2237 | *ret_processed = i + 1; |
3390be38 LP |
2238 | return 1; /* success! */ |
2239 | } | |
2240 | ||
2241 | context->state = CURSOR_TEXT; | |
2242 | } else { | |
2243 | int d = undecchar(c); | |
2244 | ||
77c796d4 | 2245 | /* As above, add the decimal character to our column number */ |
3390be38 LP |
2246 | if (d < 0 || context->column > (UINT_MAX-d)/10) |
2247 | context->state = CURSOR_TEXT; | |
2248 | else | |
2249 | context->column = context->column * 10 + d; | |
2250 | } | |
2251 | ||
2252 | break; | |
2253 | } | |
2254 | ||
2255 | /* Reset any positions we might have picked up */ | |
2256 | if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE)) | |
2257 | context->row = context->column = 0; | |
2258 | } | |
2259 | ||
f4bdf373 | 2260 | *ret_processed = size; |
3390be38 LP |
2261 | return 0; /* all good, but not enough data yet */ |
2262 | } | |
2263 | ||
2264 | int terminal_get_size_by_dsr( | |
2265 | int input_fd, | |
2266 | int output_fd, | |
2267 | unsigned *ret_rows, | |
2268 | unsigned *ret_columns) { | |
2269 | ||
a0c314d6 | 2270 | _cleanup_close_ int nonblock_input_fd = -EBADF; |
13cb6641 | 2271 | int r; |
a0c314d6 | 2272 | |
3390be38 LP |
2273 | assert(input_fd >= 0); |
2274 | assert(output_fd >= 0); | |
2275 | ||
3390be38 LP |
2276 | /* Tries to determine the terminal dimension by means of ANSI sequences rather than TIOCGWINSZ |
2277 | * ioctl(). Why bother with this? The ioctl() information is often incorrect on serial terminals | |
2278 | * (since there's no handshake or protocol to determine the right dimensions in RS232), but since the | |
2279 | * ANSI sequences are interpreted by the final terminal instead of an intermediary tty driver they | |
2280 | * should be more accurate. | |
2281 | * | |
2282 | * Unfortunately there's no direct ANSI sequence to query terminal dimensions. But we can hack around | |
2283 | * it: we position the cursor briefly at an absolute location very far down and very far to the | |
2284 | * right, and then read back where we actually ended up. Because cursor locations are capped at the | |
2285 | * terminal width/height we should then see the right values. In order to not risk integer overflows | |
2286 | * in terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is | |
2287 | * large enough for any real-life terminals, but small enough to not overflow anything or be | |
2288 | * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only, | |
2289 | * too). */ | |
2290 | ||
2291 | if (terminal_is_dumb()) | |
2292 | return -EOPNOTSUPP; | |
2293 | ||
2294 | r = terminal_verify_same(input_fd, output_fd); | |
2295 | if (r < 0) | |
2296 | return log_debug_errno(r, "Called with distinct input/output fds: %m"); | |
2297 | ||
2298 | struct termios old_termios; | |
2299 | if (tcgetattr(input_fd, &old_termios) < 0) | |
a4eb5094 | 2300 | return log_debug_errno(errno, "Failed to get terminal settings: %m"); |
3390be38 LP |
2301 | |
2302 | struct termios new_termios = old_termios; | |
2303 | termios_disable_echo(&new_termios); | |
2304 | ||
f789b17e | 2305 | if (tcsetattr(input_fd, TCSANOW, &new_termios) < 0) |
a4eb5094 | 2306 | return log_debug_errno(errno, "Failed to set new terminal settings: %m"); |
3390be38 LP |
2307 | |
2308 | unsigned saved_row = 0, saved_column = 0; | |
2309 | ||
2310 | r = loop_write(output_fd, | |
2311 | "\x1B[6n" /* Request cursor position (DSR/CPR) */ | |
2312 | "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */ | |
2313 | "\x1B[6n", /* Request cursor position again */ | |
2314 | SIZE_MAX); | |
2315 | if (r < 0) | |
2316 | goto finish; | |
2317 | ||
a0c314d6 LP |
2318 | /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone |
2319 | * else process the POLLIN. */ | |
2320 | ||
13cb6641 MY |
2321 | nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); |
2322 | if (r < 0) | |
2323 | goto finish; | |
a0c314d6 | 2324 | |
5321b957 | 2325 | usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); |
3390be38 LP |
2326 | char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ |
2327 | size_t buf_full = 0; | |
2328 | CursorPositionContext context = {}; | |
2329 | ||
2330 | for (bool first = true;; first = false) { | |
2331 | if (buf_full == 0) { | |
2332 | usec_t n = now(CLOCK_MONOTONIC); | |
3390be38 LP |
2333 | if (n >= end) { |
2334 | r = -EOPNOTSUPP; | |
2335 | goto finish; | |
2336 | } | |
2337 | ||
a0c314d6 | 2338 | r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); |
3390be38 LP |
2339 | if (r < 0) |
2340 | goto finish; | |
2341 | if (r == 0) { | |
2342 | r = -EOPNOTSUPP; | |
2343 | goto finish; | |
2344 | } | |
2345 | ||
2346 | /* On the first try, read multiple characters, i.e. the shortest valid | |
2347 | * reply. Afterwards read byte-wise, since we don't want to read too much, and | |
2348 | * unnecessarily drop too many characters from the input queue. */ | |
a0c314d6 | 2349 | ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1); |
3390be38 | 2350 | if (l < 0) { |
a0c314d6 LP |
2351 | if (errno == EAGAIN) |
2352 | continue; | |
2353 | ||
3390be38 LP |
2354 | r = -errno; |
2355 | goto finish; | |
2356 | } | |
2357 | ||
2358 | assert((size_t) l <= sizeof(buf)); | |
2359 | buf_full = l; | |
2360 | } | |
2361 | ||
2362 | size_t processed; | |
2363 | r = scan_cursor_position_response(&context, buf, buf_full, &processed); | |
2364 | if (r < 0) | |
2365 | goto finish; | |
2366 | ||
2367 | assert(processed <= buf_full); | |
2368 | buf_full -= processed; | |
2369 | memmove(buf, buf + processed, buf_full); | |
2370 | ||
2371 | if (r > 0) { | |
2372 | if (saved_row == 0) { | |
2373 | assert(saved_column == 0); | |
2374 | ||
2375 | /* First sequence, this is the cursor position before we set it somewhere | |
2376 | * into the void at the bottom right. Let's save where we are so that we can | |
2377 | * return later. */ | |
2378 | ||
2379 | /* Superficial validity checks */ | |
2380 | if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) { | |
2381 | r = -ENODATA; | |
2382 | goto finish; | |
2383 | } | |
2384 | ||
2385 | saved_row = context.row; | |
2386 | saved_column = context.column; | |
2387 | ||
2388 | /* Reset state */ | |
2389 | context = (CursorPositionContext) {}; | |
2390 | } else { | |
2391 | /* Second sequence, this is the cursor position after we set it somewhere | |
2392 | * into the void at the bottom right. */ | |
2393 | ||
2394 | /* Superficial validity checks (no particular reason to check for < 4, it's | |
2395 | * just a way to look for unreasonably small values) */ | |
2396 | if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) { | |
2397 | r = -ENODATA; | |
2398 | goto finish; | |
2399 | } | |
2400 | ||
2401 | if (ret_rows) | |
2402 | *ret_rows = context.row; | |
2403 | if (ret_columns) | |
2404 | *ret_columns = context.column; | |
2405 | ||
2406 | r = 0; | |
2407 | goto finish; | |
2408 | } | |
2409 | } | |
2410 | } | |
2411 | ||
2412 | finish: | |
2413 | /* Restore cursor position */ | |
2414 | if (saved_row > 0 && saved_column > 0) | |
2415 | RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column)); | |
2416 | ||
f789b17e | 2417 | RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSANOW, &old_termios))); |
3390be38 LP |
2418 | return r; |
2419 | } | |
63c631d7 LP |
2420 | |
2421 | int terminal_fix_size(int input_fd, int output_fd) { | |
2422 | unsigned rows, columns; | |
2423 | int r; | |
2424 | ||
2425 | /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */ | |
2426 | ||
2427 | r = terminal_verify_same(input_fd, output_fd); | |
2428 | if (r < 0) | |
2429 | return r; | |
2430 | ||
2431 | struct winsize ws = {}; | |
2432 | if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0) | |
2433 | return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m"); | |
2434 | ||
2435 | r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns); | |
2436 | if (r < 0) | |
2437 | return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m"); | |
2438 | ||
2439 | if (ws.ws_row == rows && ws.ws_col == columns) { | |
2440 | log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing."); | |
2441 | return 0; | |
2442 | } | |
2443 | ||
2444 | ws.ws_col = columns; | |
2445 | ws.ws_row = rows; | |
2446 | ||
2447 | if (ioctl(output_fd, TIOCSWINSZ, &ws) < 0) | |
2448 | return log_debug_errno(errno, "Failed to update terminal dimensions, ignoring: %m"); | |
2449 | ||
2450 | log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows); | |
2451 | return 1; | |
2452 | } | |
ce3a1593 | 2453 | |
5321b957 ZJS |
2454 | #define MAX_TERMINFO_LENGTH 64 |
2455 | /* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */ | |
2456 | #define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST | |
2457 | /* The answer is either 0+r… (invalid) or 1+r… (OK). */ | |
2458 | #define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST | |
2459 | #define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */ | |
2460 | assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST)); | |
2461 | ||
2462 | static int scan_terminfo_response( | |
2463 | const char *buf, | |
2464 | size_t size, | |
2465 | char **ret_name) { | |
2466 | int r; | |
2467 | ||
2468 | assert(buf); | |
2469 | assert(ret_name); | |
2470 | ||
2471 | /* Check if we have enough space for the shortest possible answer. */ | |
2472 | if (size < STRLEN(DCS_TERMINFO_R0)) | |
2473 | return -EAGAIN; | |
2474 | ||
2475 | /* Check if the terminating sequence is present */ | |
2476 | if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0) | |
2477 | return -EAGAIN; | |
2478 | ||
2479 | if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST)) | |
2480 | return -EINVAL; /* The answer is invalid or empty */ | |
2481 | ||
2482 | if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0) | |
2483 | return -EINVAL; /* The answer is not valid */ | |
2484 | ||
2485 | _cleanup_free_ void *dec = NULL; | |
2486 | size_t dec_size; | |
2487 | r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST), | |
2488 | /* secure= */ false, | |
2489 | &dec, &dec_size); | |
2490 | if (r < 0) | |
2491 | return r; | |
2492 | ||
2493 | assert(((const char *) dec)[dec_size] == '\0'); /* unhexmem appends NUL for our convenience */ | |
2494 | if (memchr(dec, '\0', dec_size) || string_has_cc(dec, NULL) || !filename_is_valid(dec)) | |
2495 | return -EUCLEAN; | |
2496 | ||
2497 | *ret_name = TAKE_PTR(dec); | |
2498 | return 0; | |
2499 | } | |
2500 | ||
2501 | int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { | |
2502 | int r; | |
2503 | ||
2504 | assert(fd >= 0); | |
2505 | assert(ret_name); | |
2506 | ||
2507 | /* Note: fd must be in non-blocking read-write mode! */ | |
2508 | ||
2509 | struct termios old_termios; | |
2510 | if (tcgetattr(fd, &old_termios) < 0) | |
2511 | return -errno; | |
2512 | ||
2513 | struct termios new_termios = old_termios; | |
2514 | termios_disable_echo(&new_termios); | |
2515 | ||
f789b17e | 2516 | if (tcsetattr(fd, TCSANOW, &new_termios) < 0) |
5321b957 ZJS |
2517 | return -errno; |
2518 | ||
2519 | r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX); | |
2520 | if (r < 0) | |
2521 | goto finish; | |
2522 | ||
2523 | usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); | |
2524 | char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)]; | |
2525 | size_t bytes = 0; | |
2526 | ||
2527 | for (;;) { | |
2528 | usec_t n = now(CLOCK_MONOTONIC); | |
2529 | if (n >= end) { | |
2530 | r = -EOPNOTSUPP; | |
2531 | break; | |
2532 | } | |
2533 | ||
2534 | r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n)); | |
2535 | if (r < 0) | |
2536 | break; | |
2537 | if (r == 0) { | |
2538 | r = -EOPNOTSUPP; | |
2539 | break; | |
2540 | } | |
2541 | ||
2542 | /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards | |
2543 | * read byte by byte, since we don't want to read too much and drop characters from the input | |
2544 | * queue. */ | |
2545 | ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1); | |
2546 | if (l < 0) { | |
2547 | if (errno == EAGAIN) | |
2548 | continue; | |
2549 | r = -errno; | |
2550 | break; | |
2551 | } | |
2552 | ||
2553 | assert((size_t) l <= sizeof(buf) - bytes); | |
2554 | bytes += l; | |
2555 | ||
2556 | r = scan_terminfo_response(buf, bytes, ret_name); | |
2557 | if (r != -EAGAIN) | |
2558 | break; | |
2559 | ||
2560 | if (bytes == sizeof(buf)) { | |
2561 | r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid | |
3ec92f2f | 2562 | * answer with a terminator in the allotted space. Something is |
5321b957 ZJS |
2563 | * wrong, possibly some unrelated bytes got injected into the |
2564 | * answer. */ | |
2565 | break; | |
2566 | } | |
2567 | } | |
2568 | ||
2569 | finish: | |
2570 | /* We ignore failure here. We already got a reply and if cleanup fails, we can't help that. */ | |
f789b17e | 2571 | (void) tcsetattr(fd, TCSANOW, &old_termios); |
5321b957 ZJS |
2572 | return r; |
2573 | } | |
2574 | ||
e3b050a5 ZJS |
2575 | int have_terminfo_file(const char *name) { |
2576 | /* This is a heuristic check if we have the file, using the directory layout used on | |
2577 | * current Linux systems. Checks for other layouts can be added later if appropriate. */ | |
2578 | int r; | |
2579 | ||
2580 | assert(filename_is_valid(name)); | |
2581 | ||
2582 | _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name); | |
2583 | if (!p) | |
2584 | return log_oom_debug(); | |
2585 | ||
2586 | r = RET_NERRNO(access(p, F_OK)); | |
2587 | if (r == -ENOENT) | |
2588 | return false; | |
2589 | if (r < 0) | |
2590 | return r; | |
2591 | return true; | |
2592 | } | |
2593 | ||
2594 | int query_term_for_tty(const char *tty, char **ret_term) { | |
2595 | _cleanup_free_ char *dcs_term = NULL; | |
2596 | int r; | |
2597 | ||
2598 | assert(tty); | |
2599 | assert(ret_term); | |
2600 | ||
2601 | if (tty_is_vc_resolve(tty)) | |
2602 | return strdup_to(ret_term, "linux"); | |
2603 | ||
2604 | /* Try to query the terminal implementation that we're on. This will not work in all | |
2605 | * cases, which is fine, since this is intended to be used as a fallback. */ | |
2606 | ||
2607 | _cleanup_close_ int tty_fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); | |
2608 | if (tty_fd < 0) | |
2609 | return log_debug_errno(tty_fd, "Failed to open %s to query terminfo: %m", tty); | |
2610 | ||
2611 | r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term); | |
2612 | if (r < 0) | |
2613 | return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty); | |
2614 | ||
2615 | r = have_terminfo_file(dcs_term); | |
2616 | if (r < 0) | |
2617 | return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term); | |
2618 | if (r == 0) | |
2619 | return log_info_errno(SYNTHETIC_ERRNO(ENODATA), | |
2620 | "Terminfo %s not found for %s.", dcs_term, tty); | |
2621 | ||
2622 | *ret_term = TAKE_PTR(dcs_term); | |
2623 | return 0; | |
2624 | } | |
2625 | ||
ce3a1593 LP |
2626 | int terminal_is_pty_fd(int fd) { |
2627 | int r; | |
2628 | ||
2629 | assert(fd >= 0); | |
2630 | ||
2631 | /* Returns true if we are looking at a pty, i.e. if it's backed by the /dev/pts/ file system */ | |
2632 | ||
2633 | if (!isatty_safe(fd)) | |
2634 | return false; | |
2635 | ||
2636 | r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC); | |
2637 | if (r != 0) | |
2638 | return r; | |
2639 | ||
2640 | /* The ptmx device is weird, it exists twice, once inside and once outside devpts. To detect the | |
2641 | * latter case, let's fire off an ioctl() that only works on ptmx devices. */ | |
2642 | ||
2643 | int v; | |
2644 | if (ioctl(fd, TIOCGPKT, &v) < 0) { | |
2645 | if (ERRNO_IS_NOT_SUPPORTED(errno)) | |
2646 | return false; | |
2647 | ||
2648 | return -errno; | |
2649 | } | |
2650 | ||
2651 | return true; | |
2652 | } | |
fc9dc71a | 2653 | |
1d522f1a | 2654 | int pty_open_peer(int fd, int mode) { |
fc9dc71a LP |
2655 | assert(fd >= 0); |
2656 | ||
2657 | /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13). | |
2658 | * | |
2659 | * This is safe to be called on TTYs from other namespaces. */ | |
2660 | ||
2661 | assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0); | |
2662 | ||
2663 | /* This replicates the EIO retry logic of open_terminal() in a modified way. */ | |
2664 | for (unsigned c = 0;; c++) { | |
2665 | int peer_fd = ioctl(fd, TIOCGPTPEER, mode); | |
2666 | if (peer_fd >= 0) | |
2667 | return peer_fd; | |
2668 | ||
fc9dc71a LP |
2669 | if (errno != EIO) |
2670 | return -errno; | |
2671 | ||
2672 | /* Max 1s in total */ | |
2673 | if (c >= 20) | |
2674 | return -EIO; | |
2675 | ||
2676 | (void) usleep_safe(50 * USEC_PER_MSEC); | |
2677 | } | |
2678 | } |