]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/sulogin-consoles.c
Merge branch 'PR/libmount-exec-errors' of github.com:karelzak/util-linux-work
[thirdparty/util-linux.git] / login-utils / sulogin-consoles.c
1 /*
2 * consoles.c Routines to detect the system consoles
3 *
4 * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved.
5 * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
6 * Copyright (C) 2012 Werner Fink <werner@suse.de>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program (see the file COPYING); if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
21 * MA 02110-1301, USA.
22 *
23 * Author: Werner Fink <werner@suse.de>
24 */
25
26 #include <limits.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/ioctl.h>
33 #ifdef __linux__
34 # include <sys/vt.h>
35 # include <sys/kd.h>
36 # include <linux/serial.h>
37 # include <linux/major.h>
38 #endif
39 #include <dirent.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43
44 #if defined(USE_SULOGIN_EMERGENCY_MOUNT)
45 # include <sys/mount.h>
46 # ifndef MS_RELATIME
47 # define MS_RELATIME (1<<21)
48 # endif
49 # ifndef MNT_DETACH
50 # define MNT_DETACH 2
51 # endif
52 #endif
53
54 #include "c.h"
55 #include "canonicalize.h"
56 #include "sulogin-consoles.h"
57
58 #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
59 # ifndef typeof
60 # define typeof __typeof__
61 # endif
62 # ifndef restrict
63 # define restrict __restrict__
64 # endif
65 #endif
66
67 #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
68 #define strsize(string) (strlen((string))+1)
69
70 static int consoles_debug;
71 #define DBG(x) do { \
72 if (consoles_debug) { \
73 fputs("consoles debug: ", stderr); \
74 x; \
75 } \
76 } while (0)
77
78 static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
79 dbgprint(const char * const mesg, ...)
80 {
81 va_list ap;
82 va_start(ap, mesg);
83 vfprintf(stderr, mesg, ap);
84 va_end(ap);
85 fputc('\n', stderr);
86 }
87
88 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
89 /*
90 * Make C library standard calls such like ttyname(3) work
91 * even if the system does not show any of the standard
92 * directories.
93 */
94
95 static uint32_t emergency_flags;
96 # define MNT_PROCFS 0x0001
97 # define MNT_DEVTMPFS 0x0002
98
99 void emergency_do_umounts(void)
100 {
101 if (emergency_flags & MNT_DEVTMPFS)
102 umount2("/dev", MNT_DETACH);
103 if (emergency_flags & MNT_PROCFS)
104 umount2("/proc", MNT_DETACH);
105 }
106
107 void emergency_do_mounts(void)
108 {
109 struct stat rt, xt;
110
111 if (emergency_flags) {
112 emergency_flags = 0;
113 return;
114 }
115
116 if (stat("/", &rt) != 0) {
117 warn("cannot get file status of root file system\n");
118 return;
119 }
120
121 if (stat("/proc", &xt) == 0
122 && rt.st_dev == xt.st_dev
123 && mount("proc", "/proc", "proc", MS_RELATIME, NULL) == 0)
124 emergency_flags |= MNT_PROCFS;
125
126 if (stat("/dev", &xt) == 0
127 && rt.st_dev == xt.st_dev
128 && mount("devtmpfs", "/dev", "devtmpfs",
129 MS_RELATIME, "mode=0755,nr_inodes=0") == 0) {
130
131 emergency_flags |= MNT_DEVTMPFS;
132 mknod("/dev/console", S_IFCHR|S_IRUSR|S_IWUSR,
133 makedev(TTYAUX_MAJOR, 1));
134
135 if (symlink("/proc/self/fd", "/dev/fd") == 0) {
136 ignore_result( symlink("fd/0", "/dev/stdin") );
137 ignore_result( symlink("fd/1", "/dev/stdout") );
138 ignore_result( symlink("fd/2", "/dev/stderr") );
139 }
140 }
141 }
142
143 #else /* !USE_SULOGIN_EMERGENCY_MOUNT */
144
145 void emergency_do_umounts(void) { }
146 void emergency_do_mounts(void) { }
147
148 #endif /* USE_SULOGIN_EMERGENCY_MOUNT */
149
150 /*
151 * Read and allocate one line from file,
152 * the caller has to free the result
153 */
154 static __attribute__((__nonnull__))
155 char *oneline(const char * const file)
156 {
157 FILE *fp;
158 char *ret = NULL;
159 size_t dummy = 0;
160 ssize_t len;
161
162 DBG(dbgprint("reading %s", file));
163
164 if (!(fp = fopen(file, "r" UL_CLOEXECSTR)))
165 return NULL;
166 len = getline(&ret, &dummy, fp);
167 if (len >= 0) {
168 char *nl;
169
170 if (len)
171 ret[len-1] = '\0';
172 if ((nl = strchr(ret, '\n')))
173 *nl = '\0';
174 }
175
176 fclose(fp);
177 return ret;
178 }
179
180 #ifdef __linux__
181 /*
182 * Read and determine active attribute for tty below
183 * /sys/class/tty, the caller has to free the result.
184 */
185 static __attribute__((__malloc__))
186 char *actattr(const char * const tty)
187 {
188 char *ret, *path;
189
190 if (!tty || !*tty)
191 return NULL;
192 if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0)
193 return NULL;
194
195 ret = oneline(path);
196 free(path);
197 return ret;
198 }
199
200 /*
201 * Read and determine device attribute for tty below
202 * /sys/class/tty.
203 */
204 static
205 dev_t devattr(const char * const tty)
206 {
207 dev_t dev = 0;
208 char *path, *value;
209
210 if (!tty || !*tty)
211 return 0;
212 if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0)
213 return 0;
214
215 value = oneline(path);
216 if (value) {
217 unsigned int maj, min;
218
219 if (sscanf(value, "%u:%u", &maj, &min) == 2)
220 dev = makedev(maj, min);
221 free(value);
222 }
223
224 free(path);
225 return dev;
226 }
227 #endif /* __linux__ */
228
229 /*
230 * Search below /dev for the character device in `dev_t comparedev' variable.
231 * Note that realpath(3) is used here to avoid not existent devices due the
232 * strdup(3) used in our canonicalize_path()!
233 */
234 static
235 #ifdef __GNUC__
236 __attribute__((__nonnull__,__malloc__,__hot__))
237 #endif
238 char* scandev(DIR *dir, const dev_t comparedev)
239 {
240 char path[PATH_MAX];
241 char *name = NULL;
242 const struct dirent *dent;
243 int len, fd;
244
245 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev)));
246
247 /*
248 * Try udev links on character devices first.
249 */
250 if ((len = snprintf(path, sizeof(path),
251 "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 &&
252 (size_t)len < sizeof(path)) {
253
254 name = realpath(path, NULL);
255 if (name)
256 goto out;
257 }
258
259 fd = dirfd(dir);
260 rewinddir(dir);
261 while ((dent = readdir(dir))) {
262 struct stat st;
263
264 #ifdef _DIRENT_HAVE_D_TYPE
265 if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR)
266 continue;
267 #endif
268 if (fstatat(fd, dent->d_name, &st, 0) < 0)
269 continue;
270 if (!S_ISCHR(st.st_mode))
271 continue;
272 if (comparedev != st.st_rdev)
273 continue;
274 if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 ||
275 (size_t)len >= sizeof(path))
276 continue;
277
278 name = realpath(path, NULL);
279 if (name)
280 goto out;
281 }
282
283 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
284 /*
285 * There was no /dev mounted hence and no device was found hence we create our own.
286 */
287 if (!name && (emergency_flags & MNT_DEVTMPFS)) {
288
289 if ((len = snprintf(path, sizeof(path),
290 "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 ||
291 (size_t)len >= sizeof(path))
292 goto out;
293
294 if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST)
295 goto out;
296
297 name = realpath(path, NULL);
298 }
299 #endif
300 out:
301 return name;
302 }
303
304 /*
305 * Default control characters for an unknown terminal line.
306 */
307
308 /*
309 * Allocate an aligned `struct console' memory area,
310 * initialize its default values, and append it to
311 * the global linked list.
312 */
313 static
314 #ifdef __GNUC__
315 __attribute__((__hot__))
316 #endif
317 int append_console(struct list_head *consoles, const char * const name)
318 {
319 struct console *restrict tail;
320 const struct console *last = NULL;
321
322 DBG(dbgprint("appending %s", name));
323
324 if (!list_empty(consoles))
325 last = list_last_entry(consoles, struct console, entry);
326
327 if (posix_memalign((void *) &tail, sizeof(void *),
328 alignof(struct console) + strsize(name)) != 0)
329 return -ENOMEM;
330
331 INIT_LIST_HEAD(&tail->entry);
332 INIT_CHARDATA(&tail->cp);
333
334 list_add_tail(&tail->entry, consoles);
335 tail->tty = ((char *) tail) + alignof(struct console);
336 strcpy(tail->tty, name);
337
338 tail->file = (FILE*)0;
339 tail->flags = 0;
340 tail->fd = -1;
341 tail->id = last ? last->id + 1 : 0;
342 tail->pid = -1;
343 memset(&tail->tio, 0, sizeof(tail->tio));
344 #ifdef HAVE_LIBSELINUX
345 tail->reset_tty_context = NULL;
346 tail->user_tty_context = NULL;
347 #endif
348
349 return 0;
350 }
351
352 #ifdef __linux__
353 /*
354 * return codes:
355 * < 0 - fatal error (no mem or so... )
356 * 0 - success
357 * 1 - recoverable error
358 * 2 - detection not available
359 */
360 static int detect_consoles_from_proc(struct list_head *consoles)
361 {
362 char fbuf[16 + 1];
363 DIR *dir = NULL;
364 FILE *fc = NULL;
365 int maj, min, rc = 1, matches;
366
367 DBG(dbgprint("trying /proc"));
368
369 fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR);
370 if (!fc) {
371 rc = 2;
372 goto done;
373 }
374 dir = opendir("/dev");
375 if (!dir)
376 goto done;
377
378 while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) {
379 char *name;
380 dev_t comparedev;
381
382 if (matches != 3)
383 continue;
384 if (!strchr(fbuf, 'E'))
385 continue;
386 comparedev = makedev(maj, min);
387 name = scandev(dir, comparedev);
388 if (!name)
389 continue;
390 rc = append_console(consoles, name);
391 free(name);
392 if (rc < 0)
393 goto done;
394 }
395
396 rc = list_empty(consoles) ? 1 : 0;
397 done:
398 if (dir)
399 closedir(dir);
400 if (fc)
401 fclose(fc);
402 DBG(dbgprint("[/proc rc=%d]", rc));
403 return rc;
404 }
405
406 /*
407 * return codes:
408 * < 0 - fatal error (no mem or so... )
409 * 0 - success
410 * 1 - recoverable error
411 * 2 - detection not available
412 */
413 static int detect_consoles_from_sysfs(struct list_head *consoles)
414 {
415 char *attrib = NULL, *words, *token;
416 DIR *dir = NULL;
417 int rc = 1;
418
419 DBG(dbgprint("trying /sys"));
420
421 attrib = actattr("console");
422 if (!attrib) {
423 rc = 2;
424 goto done;
425 }
426
427 words = attrib;
428
429 dir = opendir("/dev");
430 if (!dir)
431 goto done;
432
433 while ((token = strsep(&words, " \t\r\n"))) {
434 char *name;
435 dev_t comparedev;
436
437 if (*token == '\0')
438 continue;
439
440 comparedev = devattr(token);
441 if (comparedev == makedev(TTY_MAJOR, 0)) {
442 char *tmp = actattr(token);
443 if (!tmp)
444 continue;
445 comparedev = devattr(tmp);
446 free(tmp);
447 }
448
449 name = scandev(dir, comparedev);
450 if (!name)
451 continue;
452 rc = append_console(consoles, name);
453 free(name);
454 if (rc < 0)
455 goto done;
456 }
457
458 rc = list_empty(consoles) ? 1 : 0;
459 done:
460 free(attrib);
461 if (dir)
462 closedir(dir);
463 DBG(dbgprint("[/sys rc=%d]", rc));
464 return rc;
465 }
466
467
468 static int detect_consoles_from_cmdline(struct list_head *consoles)
469 {
470 char *cmdline, *words, *token;
471 dev_t comparedev;
472 DIR *dir = NULL;
473 int rc = 1, fd;
474
475 DBG(dbgprint("trying kernel cmdline"));
476
477 cmdline = oneline("/proc/cmdline");
478 if (!cmdline) {
479 rc = 2;
480 goto done;
481 }
482
483 words = cmdline;
484 dir = opendir("/dev");
485 if (!dir)
486 goto done;
487
488 while ((token = strsep(&words, " \t\r\n"))) {
489 #ifdef TIOCGDEV
490 unsigned int devnum;
491 #else
492 struct vt_stat vt;
493 struct stat st;
494 #endif
495 char *colon, *name;
496
497 if (*token != 'c')
498 continue;
499 if (strncmp(token, "console=", 8) != 0)
500 continue;
501 token += 8;
502
503 if (strcmp(token, "brl") == 0)
504 token += 4;
505 if ((colon = strchr(token, ',')))
506 *colon = '\0';
507
508 if (asprintf(&name, "/dev/%s", token) < 0)
509 continue;
510 if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) {
511 free(name);
512 continue;
513 }
514 free(name);
515 #ifdef TIOCGDEV
516 if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
517 close(fd);
518 continue;
519 }
520 comparedev = (dev_t) devnum;
521 #else
522 if (fstat(fd, &st) < 0) {
523 close(fd);
524 continue;
525 }
526 comparedev = st.st_rdev;
527 if (comparedev == makedev(TTY_MAJOR, 0)) {
528 if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
529 close(fd);
530 continue;
531 }
532 comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
533 }
534 #endif
535 close(fd);
536
537 name = scandev(dir, comparedev);
538 if (!name)
539 continue;
540 rc = append_console(consoles, name);
541 free(name);
542 if (rc < 0)
543 goto done;
544 }
545
546 rc = list_empty(consoles) ? 1 : 0;
547 done:
548 if (dir)
549 closedir(dir);
550 free(cmdline);
551 DBG(dbgprint("[kernel cmdline rc=%d]", rc));
552 return rc;
553 }
554
555 #ifdef TIOCGDEV
556 static int detect_consoles_from_tiocgdev(struct list_head *consoles,
557 const int fallback,
558 const char *device)
559 {
560 unsigned int devnum;
561 char *name;
562 int rc = 1, fd = -1;
563 dev_t comparedev;
564 DIR *dir = NULL;
565 struct console *console;
566
567 DBG(dbgprint("trying tiocgdev"));
568
569 if (!device || !*device)
570 fd = dup(fallback);
571 else
572 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
573
574 if (fd < 0)
575 goto done;
576 if (ioctl (fd, TIOCGDEV, &devnum) < 0)
577 goto done;
578
579 comparedev = (dev_t) devnum;
580 dir = opendir("/dev");
581 if (!dir)
582 goto done;
583
584 name = scandev(dir, comparedev);
585 closedir(dir);
586
587 if (!name) {
588 name = (char *) (device && *device ? device : ttyname(fallback));
589 if (!name)
590 name = "/dev/tty1";
591
592 name = strdup(name);
593 if (!name) {
594 rc = -ENOMEM;
595 goto done;
596 }
597 }
598 rc = append_console(consoles, name);
599 free(name);
600 if (rc < 0)
601 goto done;
602 if (list_empty(consoles)) {
603 rc = 1;
604 goto done;
605 }
606 console = list_last_entry(consoles, struct console, entry);
607 if (console && (!device || !*device))
608 console->fd = fallback;
609 done:
610 if (fd >= 0)
611 close(fd);
612 DBG(dbgprint("[tiocgdev rc=%d]", rc));
613 return rc;
614 }
615 #endif /* TIOCGDEV */
616 #endif /* __linux__ */
617
618 /*
619 * Try to detect the real device(s) used for the system console
620 * /dev/console if but only if /dev/console is used. On Linux
621 * this can be more than one device, e.g. a serial line as well
622 * as a virtual console as well as a simple printer.
623 *
624 * Returns 1 if stdout and stderr should be reconnected and 0
625 * otherwise or less than zero on error.
626 */
627 int detect_consoles(const char *device, const int fallback, struct list_head *consoles)
628 {
629 int fd, reconnect = 0, rc;
630 dev_t comparedev = 0;
631
632 consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0;
633
634 if (!device || !*device)
635 fd = fallback >= 0 ? dup(fallback) : - 1;
636 else {
637 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
638 reconnect = 1;
639 }
640
641 DBG(dbgprint("detection started [device=%s, fallback=%d]",
642 device, fallback));
643
644 if (fd >= 0) {
645 DIR *dir;
646 char *name;
647 struct stat st;
648 #ifdef TIOCGDEV
649 unsigned int devnum;
650 #endif
651 #ifdef __GNU__
652 /*
653 * The Hurd always gives st_rdev as 0, which causes this
654 * method to select the first terminal it finds.
655 */
656 close(fd);
657 goto fallback;
658 #endif
659 DBG(dbgprint("trying device/fallback file descriptor"));
660
661 if (fstat(fd, &st) < 0) {
662 close(fd);
663 goto fallback;
664 }
665 comparedev = st.st_rdev;
666
667 if (reconnect &&
668 (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
669 dup2(fd, fallback);
670 #ifdef __linux__
671 /*
672 * Check if the device detection for Linux system console should be used.
673 */
674 if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */
675 close(fd);
676 device = "/dev/tty";
677 goto fallback;
678 }
679 if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */
680 close(fd);
681 goto console;
682 }
683 if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */
684 close(fd);
685 device = "/dev/tty";
686 goto fallback;
687 }
688 if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */
689 struct vt_stat vt;
690 if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
691 close(fd);
692 goto fallback;
693 }
694 comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
695 }
696 #endif
697 #ifdef TIOCGDEV
698 if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
699 close(fd);
700 goto fallback;
701 }
702 comparedev = (dev_t)devnum;
703 #endif
704 close(fd);
705 dir = opendir("/dev");
706 if (!dir)
707 goto fallback;
708 name = scandev(dir, comparedev);
709 closedir(dir);
710
711 if (name) {
712 rc = append_console(consoles, name);
713 free(name);
714 if (rc < 0)
715 return rc;
716 }
717 if (list_empty(consoles))
718 goto fallback;
719
720 DBG(dbgprint("detection success [rc=%d]", reconnect));
721 return reconnect;
722 }
723 #ifdef __linux__
724 console:
725 /*
726 * Detection of devices used for Linux system console using
727 * the /proc/consoles API with kernel 2.6.38 and higher.
728 */
729 rc = detect_consoles_from_proc(consoles);
730 if (rc == 0)
731 return reconnect; /* success */
732 if (rc < 0)
733 return rc; /* fatal error */
734
735 /*
736 * Detection of devices used for Linux system console using
737 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
738 */
739 rc = detect_consoles_from_sysfs(consoles);
740 if (rc == 0)
741 return reconnect; /* success */
742 if (rc < 0)
743 return rc; /* fatal error */
744
745 /*
746 * Detection of devices used for Linux system console using
747 * kernel parameter on the kernels command line.
748 */
749 rc = detect_consoles_from_cmdline(consoles);
750 if (rc == 0)
751 return reconnect; /* success */
752 if (rc < 0)
753 return rc; /* fatal error */
754
755 /*
756 * Detection of the device used for Linux system console using
757 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
758 */
759 #ifdef TIOCGDEV
760 rc = detect_consoles_from_tiocgdev(consoles, fallback, device);
761 if (rc == 0)
762 return reconnect; /* success */
763 if (rc < 0)
764 return rc; /* fatal error */
765 #endif
766 if (!list_empty(consoles)) {
767 DBG(dbgprint("detection success [rc=%d]", reconnect));
768 return reconnect;
769 }
770
771 #endif /* __linux __ */
772
773 fallback:
774 if (fallback >= 0) {
775 const char *name;
776 char *n;
777 struct console *console;
778
779 if (device && *device != '\0')
780 name = device;
781 else name = ttyname(fallback);
782
783 if (!name)
784 name = "/dev/tty";
785
786 n = strdup(name);
787 if (!n)
788 return -ENOMEM;
789 rc = append_console(consoles, n);
790 free(n);
791 if (rc < 0)
792 return rc;
793 if (list_empty(consoles))
794 return 1;
795 console = list_last_entry(consoles, struct console, entry);
796 if (console)
797 console->fd = fallback;
798 }
799
800 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect));
801 return reconnect;
802 }
803
804
805 #ifdef TEST_PROGRAM
806 int main(int argc, char *argv[])
807 {
808 char *name = NULL;
809 int fd, re;
810 struct list_head *p, consoles;
811
812 if (argc == 2) {
813 name = argv[1];
814 fd = open(name, O_RDWR);
815 } else {
816 name = ttyname(STDIN_FILENO);
817 fd = STDIN_FILENO;
818 }
819
820 if (!name)
821 errx(EXIT_FAILURE, "usage: %s [<tty>]\n", program_invocation_short_name);
822
823 INIT_LIST_HEAD(&consoles);
824 re = detect_consoles(name, fd, &consoles);
825
826 list_for_each(p, &consoles) {
827 struct console *c = list_entry(p, struct console, entry);
828 printf("%s: id=%d %s\n", c->tty, c->id, re ? "(reconnect) " : "");
829 }
830
831 return 0;
832 }
833 #endif