2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 #include <sys/param.h>
30 #include <sys/select.h>
31 #include <sys/socket.h>
32 #include <sys/types.h>
70 static void killer_start(struct killer *, struct mux *);
71 static void *killer_run(void *);
72 static void killer_stop(struct killer *);
74 static int proto_waitconnect(int);
75 static int proto_greet(struct config *);
76 static int proto_negproto(struct config *);
77 static int proto_login(struct config *);
78 static int proto_fileattr(struct config *);
79 static int proto_xchgcoll(struct config *);
80 static struct mux *proto_mux(struct config *);
82 static int proto_escape(struct stream *, const char *);
83 static void proto_unescape(char *);
86 proto_waitconnect(int s)
90 int error, rv, soerror;
96 rv = select(s + 1, &readfd, NULL, NULL, NULL);
97 } while (rv == -1 && errno == EINTR);
100 /* Check that the connection was really successful. */
101 len = sizeof(soerror);
102 error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len);
104 /* We have no choice but faking an error here. */
105 errno = ECONNREFUSED;
115 /* Connect to the CVSup server. */
117 proto_connect(struct config *config, int family, uint16_t port)
119 char addrbuf[NI_MAXHOST];
120 /* Enough to hold sizeof("cvsup") or any port number. */
122 struct addrinfo *res, *ai, hints;
127 snprintf(servname, sizeof(servname), "%d", port);
129 strncpy(servname, "cvsup", sizeof(servname) - 1);
130 servname[sizeof(servname) - 1] = '\0';
132 memset(&hints, 0, sizeof(hints));
133 hints.ai_family = family;
134 hints.ai_socktype = SOCK_STREAM;
135 error = getaddrinfo(config->host, servname, &hints, &res);
137 * Try with the hardcoded port number for OSes that don't
138 * have cvsup defined in the /etc/services file.
140 if (error == EAI_SERVICE) {
141 strncpy(servname, "5999", sizeof(servname) - 1);
142 servname[sizeof(servname) - 1] = '\0';
143 error = getaddrinfo(config->host, servname, &hints, &res);
146 lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host,
147 gai_strerror(error));
148 return (STATUS_TRANSIENTFAILURE);
150 for (ai = res; ai != NULL; ai = ai->ai_next) {
151 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
154 if (config->laddr != NULL) {
156 (void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
158 error = bind(s, config->laddr,
162 error = connect(s, ai->ai_addr, ai->ai_addrlen);
163 if (error && errno == EINTR)
164 error = proto_waitconnect(s);
169 (void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf,
170 sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
171 if (s == -1 || error) {
172 lprintf(0, "Cannot connect to %s: %s\n", addrbuf,
176 lprintf(1, "Connected to %s\n", addrbuf);
179 return (STATUS_SUCCESS);
182 return (STATUS_TRANSIENTFAILURE);
185 /* Greet the server. */
187 proto_greet(struct config *config)
189 char *line, *cmd, *msg, *swver;
193 line = stream_getln(s, NULL);
194 cmd = proto_get_ascii(&line);
197 if (strcmp(cmd, "OK") == 0) {
198 (void)proto_get_ascii(&line); /* major number */
199 (void)proto_get_ascii(&line); /* minor number */
200 swver = proto_get_ascii(&line);
201 } else if (strcmp(cmd, "!") == 0) {
202 msg = proto_get_rest(&line);
205 lprintf(-1, "Rejected by server: %s\n", msg);
206 return (STATUS_TRANSIENTFAILURE);
209 lprintf(2, "Server software version: %s\n",
210 swver != NULL ? swver : ".");
211 return (STATUS_SUCCESS);
213 lprintf(-1, "Invalid greeting from server\n");
214 return (STATUS_FAILURE);
217 /* Negotiate protocol version with the server. */
219 proto_negproto(struct config *config)
222 char *cmd, *line, *msg;
226 proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER);
228 line = stream_getln(s, NULL);
229 cmd = proto_get_ascii(&line);
230 if (cmd == NULL || line == NULL)
232 if (strcmp(cmd, "!") == 0) {
233 msg = proto_get_rest(&line);
234 lprintf(-1, "Protocol negotiation failed: %s\n", msg);
236 } else if (strcmp(cmd, "PROTO") != 0)
238 error = proto_get_int(&line, &maj, 10);
240 error = proto_get_int(&line, &min, 10);
243 if (maj != PROTO_MAJ || min != PROTO_MIN) {
244 lprintf(-1, "Server protocol version %d.%d not supported "
245 "by client\n", maj, min);
246 return (STATUS_FAILURE);
248 return (STATUS_SUCCESS);
250 lprintf(-1, "Invalid PROTO command from server\n");
251 return (STATUS_FAILURE);
255 proto_login(struct config *config)
258 char hostbuf[MAXHOSTNAMELEN];
259 char *line, *login, *host, *cmd, *realm, *challenge, *msg;
263 error = gethostname(hostbuf, sizeof(hostbuf));
264 hostbuf[sizeof(hostbuf) - 1] = '\0';
270 proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
271 host != NULL ? host : "?");
273 line = stream_getln(s, NULL);
274 cmd = proto_get_ascii(&line);
275 realm = proto_get_ascii(&line);
276 challenge = proto_get_ascii(&line);
277 if (challenge == NULL || line != NULL)
279 if (strcmp(realm, ".") != 0 || strcmp(challenge, ".") != 0) {
280 lprintf(-1, "Authentication required by the server and not "
281 "supported by client\n");
282 return (STATUS_FAILURE);
284 proto_printf(s, "AUTHMD5 . . .\n");
286 line = stream_getln(s, NULL);
287 cmd = proto_get_ascii(&line);
288 if (cmd == NULL || line == NULL)
290 if (strcmp(cmd, "OK") == 0)
291 return (STATUS_SUCCESS);
292 if (strcmp(cmd, "!") == 0) {
293 msg = proto_get_rest(&line);
296 lprintf(-1, "Server error: %s\n", msg);
297 return (STATUS_FAILURE);
300 lprintf(-1, "Invalid server reply to AUTHMD5\n");
301 return (STATUS_FAILURE);
305 * File attribute support negotiation.
308 proto_fileattr(struct config *config)
310 fattr_support_t support;
313 int error, i, n, attr;
316 lprintf(2, "Negotiating file attribute support\n");
317 proto_printf(s, "ATTR %d\n", FT_NUMBER);
318 for (i = 0; i < FT_NUMBER; i++)
319 proto_printf(s, "%x\n", fattr_supported(i));
320 proto_printf(s, ".\n");
322 line = stream_getln(s, NULL);
325 cmd = proto_get_ascii(&line);
326 error = proto_get_int(&line, &n, 10);
327 if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER)
329 for (i = 0; i < n; i++) {
330 line = stream_getln(s, NULL);
333 error = proto_get_int(&line, &attr, 16);
336 support[i] = fattr_supported(i) & attr;
338 for (i = n; i < FT_NUMBER; i++)
340 line = stream_getln(s, NULL);
341 if (line == NULL || strcmp(line, ".") != 0)
343 memcpy(config->fasupport, support, sizeof(config->fasupport));
344 return (STATUS_SUCCESS);
346 lprintf(-1, "Protocol error negotiating attribute support\n");
347 return (STATUS_FAILURE);
351 * Exchange collection information.
354 proto_xchgcoll(struct config *config)
358 struct globtree *diraccept, *dirrefuse;
359 struct globtree *fileaccept, *filerefuse;
360 char *line, *cmd, *collname, *pat;
361 char *msg, *release, *ident, *rcskey, *prefix;
363 int error, flags, options;
366 lprintf(2, "Exchanging collection information\n");
367 STAILQ_FOREACH(coll, &config->colls, co_next) {
368 if (coll->co_options & CO_SKIP)
370 proto_printf(s, "COLL %s %s %o %d\n", coll->co_name,
371 coll->co_release, coll->co_umask, coll->co_options);
372 for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
373 proto_printf(s, "ACC %s\n",
374 pattlist_get(coll->co_accepts, i));
376 for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
377 proto_printf(s, "REF %s\n",
378 pattlist_get(coll->co_refusals, i));
380 proto_printf(s, ".\n");
382 proto_printf(s, ".\n");
385 STAILQ_FOREACH(coll, &config->colls, co_next) {
386 if (coll->co_options & CO_SKIP)
388 coll->co_norsync = globtree_false();
389 line = stream_getln(s, NULL);
392 cmd = proto_get_ascii(&line);
393 collname = proto_get_ascii(&line);
394 release = proto_get_ascii(&line);
395 error = proto_get_int(&line, &options, 10);
396 if (error || line != NULL)
398 if (strcmp(cmd, "COLL") != 0 ||
399 strcmp(collname, coll->co_name) != 0 ||
400 strcmp(release, coll->co_release) != 0)
403 (coll->co_options | (options & CO_SERVMAYSET)) &
404 ~(~options & CO_SERVMAYCLEAR);
405 while ((line = stream_getln(s, NULL)) != NULL) {
406 if (strcmp(line, ".") == 0)
408 cmd = proto_get_ascii(&line);
411 if (strcmp(cmd, "!") == 0) {
412 msg = proto_get_rest(&line);
415 lprintf(-1, "Server message: %s\n", msg);
416 } else if (strcmp(cmd, "PRFX") == 0) {
417 prefix = proto_get_ascii(&line);
418 if (prefix == NULL || line != NULL)
420 coll->co_cvsroot = xstrdup(prefix);
421 } else if (strcmp(cmd, "KEYALIAS") == 0) {
422 ident = proto_get_ascii(&line);
423 rcskey = proto_get_ascii(&line);
424 if (rcskey == NULL || line != NULL)
426 error = keyword_alias(coll->co_keyword, ident,
430 } else if (strcmp(cmd, "KEYON") == 0) {
431 ident = proto_get_ascii(&line);
432 if (ident == NULL || line != NULL)
434 error = keyword_enable(coll->co_keyword, ident);
437 } else if (strcmp(cmd, "KEYOFF") == 0) {
438 ident = proto_get_ascii(&line);
439 if (ident == NULL || line != NULL)
441 error = keyword_disable(coll->co_keyword,
445 } else if (strcmp(cmd, "NORS") == 0) {
446 pat = proto_get_ascii(&line);
447 if (pat == NULL || line != NULL)
449 coll->co_norsync = globtree_or(coll->co_norsync,
450 globtree_match(pat, FNM_PATHNAME));
451 } else if (strcmp(cmd, "RNORS") == 0) {
452 pat = proto_get_ascii(&line);
453 if (pat == NULL || line != NULL)
455 coll->co_norsync = globtree_or(coll->co_norsync,
456 globtree_match(pat, FNM_PATHNAME |
463 keyword_prepare(coll->co_keyword);
465 diraccept = globtree_true();
466 fileaccept = globtree_true();
467 dirrefuse = globtree_false();
468 filerefuse = globtree_false();
470 if (pattlist_size(coll->co_accepts) > 0) {
471 globtree_free(diraccept);
472 globtree_free(fileaccept);
473 diraccept = globtree_false();
474 fileaccept = globtree_false();
475 flags = FNM_PATHNAME | FNM_LEADING_DIR |
477 for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
478 pat = pattlist_get(coll->co_accepts, i);
479 diraccept = globtree_or(diraccept,
480 globtree_match(pat, flags));
483 if (coll->co_options & CO_CHECKOUTMODE &&
484 (len == 0 || pat[len - 1] != '*')) {
485 /* We must modify the pattern so that it
486 refers to the RCS file, rather than
487 the checked-out file. */
488 xasprintf(&pat, "%s,v", pat);
489 fileaccept = globtree_or(fileaccept,
490 globtree_match(pat, flags));
493 fileaccept = globtree_or(fileaccept,
494 globtree_match(pat, flags));
499 for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
500 pat = pattlist_get(coll->co_refusals, i);
501 dirrefuse = globtree_or(dirrefuse,
502 globtree_match(pat, 0));
504 if (coll->co_options & CO_CHECKOUTMODE &&
505 (len == 0 || pat[len - 1] != '*')) {
506 /* We must modify the pattern so that it refers
507 to the RCS file, rather than the checked-out
509 xasprintf(&pat, "%s,v", pat);
510 filerefuse = globtree_or(filerefuse,
511 globtree_match(pat, 0));
514 filerefuse = globtree_or(filerefuse,
515 globtree_match(pat, 0));
519 coll->co_dirfilter = globtree_and(diraccept,
520 globtree_not(dirrefuse));
521 coll->co_filefilter = globtree_and(fileaccept,
522 globtree_not(filerefuse));
524 /* Set up a mask of file attributes that we don't want to sync
526 if (!(coll->co_options & CO_SETOWNER))
527 coll->co_attrignore |= FA_OWNER | FA_GROUP;
528 if (!(coll->co_options & CO_SETMODE))
529 coll->co_attrignore |= FA_MODE;
530 if (!(coll->co_options & CO_SETFLAGS))
531 coll->co_attrignore |= FA_FLAGS;
533 return (STATUS_SUCCESS);
535 lprintf(-1, "Protocol error during collection exchange\n");
536 return (STATUS_FAILURE);
540 proto_mux(struct config *config)
543 struct stream *s, *wr;
544 struct chan *chan0, *chan1;
548 lprintf(2, "Establishing multiplexed-mode data connection\n");
549 proto_printf(s, "MUX\n");
551 m = mux_open(config->socket, &chan0);
553 lprintf(-1, "Cannot open the multiplexer\n");
558 lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
562 wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
563 proto_printf(wr, "CHAN %d\n", id);
565 chan1 = chan_accept(m, id);
567 lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
571 config->chan0 = chan0;
572 config->chan1 = chan1;
577 * Initializes the connection to the CVSup server, that is handle
578 * the protocol negotiation, logging in, exchanging file attributes
579 * support and collections information, and finally run the update
583 proto_run(struct config *config)
585 struct thread_args lister_args;
586 struct thread_args detailer_args;
587 struct thread_args updater_args;
588 struct thread_args *args;
589 struct killer killer;
590 struct threads *workers;
595 * We pass NULL for the close() function because we'll reuse
596 * the socket after the stream is closed.
598 config->server = stream_open_fd(config->socket, stream_read_fd,
599 stream_write_fd, NULL);
600 status = proto_greet(config);
601 if (status == STATUS_SUCCESS)
602 status = proto_negproto(config);
603 if (status == STATUS_SUCCESS)
604 status = proto_login(config);
605 if (status == STATUS_SUCCESS)
606 status = proto_fileattr(config);
607 if (status == STATUS_SUCCESS)
608 status = proto_xchgcoll(config);
609 if (status != STATUS_SUCCESS)
612 /* Multi-threaded action starts here. */
613 m = proto_mux(config);
615 return (STATUS_FAILURE);
617 stream_close(config->server);
618 config->server = NULL;
619 config->fixups = fixups_new();
620 killer_start(&killer, m);
622 /* Start the worker threads. */
623 workers = threads_new();
625 args->config = config;
629 args->wr = stream_open(config->chan0,
630 NULL, (stream_writefn_t *)chan_write, NULL);
631 threads_create(workers, lister, args);
633 args = &detailer_args;
634 args->config = config;
637 args->rd = stream_open(config->chan0,
638 (stream_readfn_t *)chan_read, NULL, NULL);
639 args->wr = stream_open(config->chan1,
640 NULL, (stream_writefn_t *)chan_write, NULL);
641 threads_create(workers, detailer, args);
643 args = &updater_args;
644 args->config = config;
647 args->rd = stream_open(config->chan1,
648 (stream_readfn_t *)chan_read, NULL, NULL);
650 threads_create(workers, updater, args);
652 lprintf(2, "Running\n");
653 /* Wait for all the worker threads to finish. */
654 status = STATUS_SUCCESS;
655 for (i = 0; i < 3; i++) {
656 args = threads_wait(workers);
657 if (args->rd != NULL)
658 stream_close(args->rd);
659 if (args->wr != NULL)
660 stream_close(args->wr);
661 if (args->status != STATUS_SUCCESS) {
662 assert(args->errmsg != NULL);
663 if (status == STATUS_SUCCESS) {
664 status = args->status;
665 /* Shutdown the multiplexer to wake up all
666 the other threads. */
667 mux_shutdown(m, args->errmsg, status);
672 threads_free(workers);
673 if (status == STATUS_SUCCESS) {
674 lprintf(2, "Shutting down connection to server\n");
675 chan_close(config->chan0);
676 chan_close(config->chan1);
677 chan_wait(config->chan0);
678 chan_wait(config->chan1);
679 mux_shutdown(m, NULL, STATUS_SUCCESS);
681 killer_stop(&killer);
682 fixups_free(config->fixups);
683 status = mux_close(m);
684 if (status == STATUS_SUCCESS) {
685 lprintf(1, "Finished successfully\n");
686 } else if (status == STATUS_INTERRUPTED) {
687 lprintf(-1, "Interrupted\n");
688 if (killer.killedby != -1)
689 kill(getpid(), killer.killedby);
695 * Write a string into the stream, escaping characters as needed.
696 * Characters escaped:
705 proto_escape(struct stream *wr, const char *s)
711 /* Handle characters that need escaping. */
713 len = strcspn(s, " \t\r\n\\");
714 n = stream_write(wr, s, len);
720 n = stream_write(wr, "\\_", 2);
723 n = stream_write(wr, "\\t", 2);
726 n = stream_write(wr, "\\r", 2);
729 n = stream_write(wr, "\\n", 2);
732 n = stream_write(wr, "\\\\", 2);
743 * A simple printf() implementation specifically tailored for csup.
744 * List of the supported formats:
747 * %d or %i Print an int as decimal.
748 * %x Print an int as hexadecimal.
749 * %o Print an int as octal.
750 * %t Print a time_t as decimal.
751 * %s Print a char * escaping some characters as needed.
752 * %S Print a char * without escaping.
753 * %f Print an encoded struct fattr *.
754 * %F Print an encoded struct fattr *, specifying the supported
758 proto_printf(struct stream *wr, const char *format, ...)
760 fattr_support_t *support;
775 va_start(ap, format);
776 while ((cp = strchr(fmt, '%')) != NULL) {
778 n = stream_write(wr, fmt, cp - fmt);
787 rv = stream_printf(wr, "%c", c);
791 val = va_arg(ap, int);
792 rv = stream_printf(wr, "%d", val);
795 val = va_arg(ap, int);
796 rv = stream_printf(wr, "%x", val);
799 val = va_arg(ap, int);
800 rv = stream_printf(wr, "%o", val);
803 off = va_arg(ap, off_t);
804 rv = stream_printf(wr, "%llu", off);
807 s = va_arg(ap, char *);
809 rv = stream_printf(wr, "%s", s);
812 s = va_arg(ap, char *);
814 rv = proto_escape(wr, s);
817 longval = (long long)va_arg(ap, time_t);
818 rv = stream_printf(wr, "%lld", longval);
821 fa = va_arg(ap, struct fattr *);
822 attr = fattr_encode(fa, NULL, 0);
823 rv = proto_escape(wr, attr);
827 fa = va_arg(ap, struct fattr *);
828 support = va_arg(ap, fattr_support_t *);
829 ignore = va_arg(ap, int);
830 attr = fattr_encode(fa, *support, ignore);
831 rv = proto_escape(wr, attr);
835 size = va_arg(ap, size_t);
836 rv = stream_printf(wr, "%zu", size);
840 n = stream_write(wr, "%", 1);
850 rv = stream_printf(wr, "%s", fmt);
860 * Unescape the string, see proto_escape().
863 proto_unescape(char *s)
868 while ((cp = strchr(cp, '\\')) != NULL) {
889 while (*cp2 != '\0') {
897 * Get an ascii token in the string.
900 proto_get_ascii(char **s)
904 ret = strsep(s, " ");
907 /* Make sure we disallow 0-length fields. */
917 * Get the rest of the string.
920 proto_get_rest(char **s)
936 proto_get_int(char **s, int *val, int base)
941 cp = proto_get_ascii(s);
944 error = asciitoint(cp, val, base);
949 * Get a size_t token.
952 proto_get_sizet(char **s, size_t *val, int base)
954 unsigned long long tmp;
957 cp = proto_get_ascii(s);
961 tmp = strtoll(cp, &end, base);
962 if (errno || *end != '\0')
969 * Get a time_t token.
971 * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
972 * is more portable and 64bits should be enough for a timestamp.
975 proto_get_time(char **s, time_t *val)
980 cp = proto_get_ascii(s);
984 tmp = strtoll(cp, &end, 10);
985 if (errno || *end != '\0')
991 /* Start the killer thread. It is used to protect against some signals
992 during the multi-threaded run so that we can gracefully fail. */
994 killer_start(struct killer *k, struct mux *m)
1000 sigemptyset(&k->sigset);
1001 sigaddset(&k->sigset, SIGINT);
1002 sigaddset(&k->sigset, SIGHUP);
1003 sigaddset(&k->sigset, SIGTERM);
1004 sigaddset(&k->sigset, SIGPIPE);
1005 pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
1006 error = pthread_create(&k->thread, NULL, killer_run, k);
1008 err(1, "pthread_create");
1011 /* The main loop of the killer thread. */
1013 killer_run(void *arg)
1016 int error, sig, old;
1020 error = sigwait(&k->sigset, &sig);
1022 if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
1023 if (k->killedby == -1) {
1025 /* Ensure we don't get canceled during the shutdown. */
1026 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
1027 mux_shutdown(k->mux, "Cleaning up ...",
1028 STATUS_INTERRUPTED);
1029 pthread_setcancelstate(old, NULL);
1035 /* Stop the killer thread. */
1037 killer_stop(struct killer *k)
1042 error = pthread_cancel(k->thread);
1044 pthread_join(k->thread, &val);
1045 assert(val == PTHREAD_CANCELED);
1046 pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);