]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-daemon/sd-daemon.c
sd-daemon: Add vsock fallback to SOCK_STREAM
[thirdparty/systemd.git] / src / libsystemd / sd-daemon / sd-daemon.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <limits.h>
5 #include <mqueue.h>
6 #include <netinet/in.h>
7 #include <poll.h>
8 #include <stdarg.h>
9 #include <stddef.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <sys/stat.h>
13 #include <sys/un.h>
14 #include <unistd.h>
15
16 #include "sd-daemon.h"
17
18 #include "alloc-util.h"
19 #include "fd-util.h"
20 #include "fs-util.h"
21 #include "io-util.h"
22 #include "parse-util.h"
23 #include "path-util.h"
24 #include "process-util.h"
25 #include "socket-util.h"
26 #include "stat-util.h"
27 #include "strv.h"
28 #include "time-util.h"
29
30 #define SNDBUF_SIZE (8*1024*1024)
31
32 static void unsetenv_all(bool unset_environment) {
33 if (!unset_environment)
34 return;
35
36 assert_se(unsetenv("LISTEN_PID") == 0);
37 assert_se(unsetenv("LISTEN_FDS") == 0);
38 assert_se(unsetenv("LISTEN_FDNAMES") == 0);
39 }
40
41 _public_ int sd_listen_fds(int unset_environment) {
42 const char *e;
43 int n, r;
44 pid_t pid;
45
46 e = getenv("LISTEN_PID");
47 if (!e) {
48 r = 0;
49 goto finish;
50 }
51
52 r = parse_pid(e, &pid);
53 if (r < 0)
54 goto finish;
55
56 /* Is this for us? */
57 if (getpid_cached() != pid) {
58 r = 0;
59 goto finish;
60 }
61
62 e = getenv("LISTEN_FDS");
63 if (!e) {
64 r = 0;
65 goto finish;
66 }
67
68 r = safe_atoi(e, &n);
69 if (r < 0)
70 goto finish;
71
72 assert_cc(SD_LISTEN_FDS_START < INT_MAX);
73 if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) {
74 r = -EINVAL;
75 goto finish;
76 }
77
78 for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
79 r = fd_cloexec(fd, true);
80 if (r < 0)
81 goto finish;
82 }
83
84 r = n;
85
86 finish:
87 unsetenv_all(unset_environment);
88 return r;
89 }
90
91 _public_ int sd_listen_fds_with_names(int unset_environment, char ***names) {
92 _cleanup_strv_free_ char **l = NULL;
93 bool have_names;
94 int n_names = 0, n_fds;
95 const char *e;
96 int r;
97
98 if (!names)
99 return sd_listen_fds(unset_environment);
100
101 e = getenv("LISTEN_FDNAMES");
102 if (e) {
103 n_names = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
104 if (n_names < 0) {
105 unsetenv_all(unset_environment);
106 return n_names;
107 }
108
109 have_names = true;
110 } else
111 have_names = false;
112
113 n_fds = sd_listen_fds(unset_environment);
114 if (n_fds <= 0)
115 return n_fds;
116
117 if (have_names) {
118 if (n_names != n_fds)
119 return -EINVAL;
120 } else {
121 r = strv_extend_n(&l, "unknown", n_fds);
122 if (r < 0)
123 return r;
124 }
125
126 *names = TAKE_PTR(l);
127
128 return n_fds;
129 }
130
131 _public_ int sd_is_fifo(int fd, const char *path) {
132 struct stat st_fd;
133
134 assert_return(fd >= 0, -EBADF);
135
136 if (fstat(fd, &st_fd) < 0)
137 return -errno;
138
139 if (!S_ISFIFO(st_fd.st_mode))
140 return 0;
141
142 if (path) {
143 struct stat st_path;
144
145 if (stat(path, &st_path) < 0) {
146
147 if (IN_SET(errno, ENOENT, ENOTDIR))
148 return 0;
149
150 return -errno;
151 }
152
153 return stat_inode_same(&st_path, &st_fd);
154 }
155
156 return 1;
157 }
158
159 _public_ int sd_is_special(int fd, const char *path) {
160 struct stat st_fd;
161
162 assert_return(fd >= 0, -EBADF);
163
164 if (fstat(fd, &st_fd) < 0)
165 return -errno;
166
167 if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode))
168 return 0;
169
170 if (path) {
171 struct stat st_path;
172
173 if (stat(path, &st_path) < 0) {
174
175 if (IN_SET(errno, ENOENT, ENOTDIR))
176 return 0;
177
178 return -errno;
179 }
180
181 if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode))
182 return stat_inode_same(&st_path, &st_fd);
183 else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode))
184 return st_path.st_rdev == st_fd.st_rdev;
185 else
186 return 0;
187 }
188
189 return 1;
190 }
191
192 static int is_socket_internal(int fd, int type, int listening) {
193 struct stat st_fd;
194
195 assert_return(fd >= 0, -EBADF);
196 assert_return(type >= 0, -EINVAL);
197
198 if (fstat(fd, &st_fd) < 0)
199 return -errno;
200
201 if (!S_ISSOCK(st_fd.st_mode))
202 return 0;
203
204 if (type != 0) {
205 int other_type = 0;
206 socklen_t l = sizeof(other_type);
207
208 if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
209 return -errno;
210
211 if (l != sizeof(other_type))
212 return -EINVAL;
213
214 if (other_type != type)
215 return 0;
216 }
217
218 if (listening >= 0) {
219 int accepting = 0;
220 socklen_t l = sizeof(accepting);
221
222 if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
223 return -errno;
224
225 if (l != sizeof(accepting))
226 return -EINVAL;
227
228 if (!accepting != !listening)
229 return 0;
230 }
231
232 return 1;
233 }
234
235 _public_ int sd_is_socket(int fd, int family, int type, int listening) {
236 int r;
237
238 assert_return(fd >= 0, -EBADF);
239 assert_return(family >= 0, -EINVAL);
240
241 r = is_socket_internal(fd, type, listening);
242 if (r <= 0)
243 return r;
244
245 if (family > 0) {
246 union sockaddr_union sockaddr = {};
247 socklen_t l = sizeof(sockaddr);
248
249 if (getsockname(fd, &sockaddr.sa, &l) < 0)
250 return -errno;
251
252 if (l < sizeof(sa_family_t))
253 return -EINVAL;
254
255 return sockaddr.sa.sa_family == family;
256 }
257
258 return 1;
259 }
260
261 _public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
262 union sockaddr_union sockaddr = {};
263 socklen_t l = sizeof(sockaddr);
264 int r;
265
266 assert_return(fd >= 0, -EBADF);
267 assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL);
268
269 r = is_socket_internal(fd, type, listening);
270 if (r <= 0)
271 return r;
272
273 if (getsockname(fd, &sockaddr.sa, &l) < 0)
274 return -errno;
275
276 if (l < sizeof(sa_family_t))
277 return -EINVAL;
278
279 if (!IN_SET(sockaddr.sa.sa_family, AF_INET, AF_INET6))
280 return 0;
281
282 if (family != 0)
283 if (sockaddr.sa.sa_family != family)
284 return 0;
285
286 if (port > 0) {
287 unsigned sa_port;
288
289 r = sockaddr_port(&sockaddr.sa, &sa_port);
290 if (r < 0)
291 return r;
292
293 return port == sa_port;
294 }
295
296 return 1;
297 }
298
299 _public_ int sd_is_socket_sockaddr(int fd, int type, const struct sockaddr* addr, unsigned addr_len, int listening) {
300 union sockaddr_union sockaddr = {};
301 socklen_t l = sizeof(sockaddr);
302 int r;
303
304 assert_return(fd >= 0, -EBADF);
305 assert_return(addr, -EINVAL);
306 assert_return(addr_len >= sizeof(sa_family_t), -ENOBUFS);
307 assert_return(IN_SET(addr->sa_family, AF_INET, AF_INET6), -EPFNOSUPPORT);
308
309 r = is_socket_internal(fd, type, listening);
310 if (r <= 0)
311 return r;
312
313 if (getsockname(fd, &sockaddr.sa, &l) < 0)
314 return -errno;
315
316 if (l < sizeof(sa_family_t))
317 return -EINVAL;
318
319 if (sockaddr.sa.sa_family != addr->sa_family)
320 return 0;
321
322 if (sockaddr.sa.sa_family == AF_INET) {
323 const struct sockaddr_in *in = (const struct sockaddr_in *) addr;
324
325 if (l < sizeof(struct sockaddr_in) || addr_len < sizeof(struct sockaddr_in))
326 return -EINVAL;
327
328 if (in->sin_port != 0 &&
329 sockaddr.in.sin_port != in->sin_port)
330 return false;
331
332 return sockaddr.in.sin_addr.s_addr == in->sin_addr.s_addr;
333
334 } else {
335 const struct sockaddr_in6 *in = (const struct sockaddr_in6 *) addr;
336
337 if (l < sizeof(struct sockaddr_in6) || addr_len < sizeof(struct sockaddr_in6))
338 return -EINVAL;
339
340 if (in->sin6_port != 0 &&
341 sockaddr.in6.sin6_port != in->sin6_port)
342 return false;
343
344 if (in->sin6_flowinfo != 0 &&
345 sockaddr.in6.sin6_flowinfo != in->sin6_flowinfo)
346 return false;
347
348 if (in->sin6_scope_id != 0 &&
349 sockaddr.in6.sin6_scope_id != in->sin6_scope_id)
350 return false;
351
352 return memcmp(sockaddr.in6.sin6_addr.s6_addr, in->sin6_addr.s6_addr,
353 sizeof(in->sin6_addr.s6_addr)) == 0;
354 }
355 }
356
357 _public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
358 union sockaddr_union sockaddr = {};
359 socklen_t l = sizeof(sockaddr);
360 int r;
361
362 assert_return(fd >= 0, -EBADF);
363
364 r = is_socket_internal(fd, type, listening);
365 if (r <= 0)
366 return r;
367
368 if (getsockname(fd, &sockaddr.sa, &l) < 0)
369 return -errno;
370
371 if (l < sizeof(sa_family_t))
372 return -EINVAL;
373
374 if (sockaddr.sa.sa_family != AF_UNIX)
375 return 0;
376
377 if (path) {
378 if (length == 0)
379 length = strlen(path);
380
381 if (length == 0)
382 /* Unnamed socket */
383 return l == offsetof(struct sockaddr_un, sun_path);
384
385 if (path[0])
386 /* Normal path socket */
387 return
388 (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
389 memcmp(path, sockaddr.un.sun_path, length+1) == 0;
390 else
391 /* Abstract namespace socket */
392 return
393 (l == offsetof(struct sockaddr_un, sun_path) + length) &&
394 memcmp(path, sockaddr.un.sun_path, length) == 0;
395 }
396
397 return 1;
398 }
399
400 _public_ int sd_is_mq(int fd, const char *path) {
401 struct mq_attr attr;
402
403 /* Check that the fd is valid */
404 assert_return(fcntl(fd, F_GETFD) >= 0, -errno);
405
406 if (mq_getattr(fd, &attr) < 0) {
407 if (errno == EBADF)
408 /* A non-mq fd (or an invalid one, but we ruled that out above) */
409 return 0;
410 return -errno;
411 }
412
413 if (path) {
414 _cleanup_free_ char *fpath = NULL;
415 struct stat a, b;
416
417 assert_return(path_is_absolute(path), -EINVAL);
418
419 if (fstat(fd, &a) < 0)
420 return -errno;
421
422 fpath = path_join("/dev/mqueue", path);
423 if (!fpath)
424 return -ENOMEM;
425
426 if (stat(fpath, &b) < 0)
427 return -errno;
428
429 if (!stat_inode_same(&a, &b))
430 return 0;
431 }
432
433 return 1;
434 }
435
436 static int vsock_bind_privileged_port(int fd) {
437 union sockaddr_union sa = {
438 .vm.svm_family = AF_VSOCK,
439 .vm.svm_cid = VMADDR_CID_ANY,
440 .vm.svm_port = 1023,
441 };
442 int r;
443
444 assert(fd >= 0);
445
446 do
447 r = RET_NERRNO(bind(fd, &sa.sa, sizeof(sa.vm)));
448 while (r == -EADDRINUSE && --sa.vm.svm_port > 0);
449
450 return r;
451 }
452
453 static int pid_notify_with_fds_internal(
454 pid_t pid,
455 const char *state,
456 const int *fds,
457 unsigned n_fds) {
458 SocketAddress address;
459 struct iovec iovec;
460 struct msghdr msghdr = {
461 .msg_iov = &iovec,
462 .msg_iovlen = 1,
463 .msg_name = &address.sockaddr,
464 };
465 _cleanup_close_ int fd = -EBADF;
466 struct cmsghdr *cmsg = NULL;
467 const char *e;
468 bool send_ucred;
469 ssize_t n;
470 int type, r;
471
472 if (!state)
473 return -EINVAL;
474
475 if (n_fds > 0 && !fds)
476 return -EINVAL;
477
478 e = getenv("NOTIFY_SOCKET");
479 if (!e)
480 return 0;
481
482 /* Allow AF_UNIX and AF_VSOCK, reject the rest. */
483 r = socket_address_parse_unix(&address, e);
484 if (r == -EPROTO)
485 r = socket_address_parse_vsock(&address, e);
486 if (r < 0)
487 return r;
488 msghdr.msg_namelen = address.size;
489
490 /* If we didn't get an address (which is a normal pattern when specifying VSOCK tuples) error out,
491 * we always require a specific CID. */
492 if (address.sockaddr.vm.svm_family == AF_VSOCK && address.sockaddr.vm.svm_cid == VMADDR_CID_ANY)
493 return -EINVAL;
494
495 type = address.type == 0 ? SOCK_DGRAM : address.type;
496
497 /* At the time of writing QEMU does not yet support AF_VSOCK + SOCK_DGRAM and returns
498 * ENODEV. Fallback to SOCK_SEQPACKET in that case. */
499 fd = socket(address.sockaddr.sa.sa_family, type|SOCK_CLOEXEC, 0);
500 if (fd < 0) {
501 if (!(ERRNO_IS_NOT_SUPPORTED(errno) || errno == ENODEV) || address.sockaddr.sa.sa_family != AF_VSOCK || address.type > 0)
502 return log_debug_errno(errno, "Failed to open %s notify socket to '%s': %m", socket_address_type_to_string(type), e);
503
504 type = SOCK_SEQPACKET;
505 fd = socket(address.sockaddr.sa.sa_family, type|SOCK_CLOEXEC, 0);
506 if (fd < 0 && ERRNO_IS_NOT_SUPPORTED(errno)) {
507 type = SOCK_STREAM;
508 fd = socket(address.sockaddr.sa.sa_family, type|SOCK_CLOEXEC, 0);
509 }
510 if (fd < 0)
511 return log_debug_errno(errno, "Failed to open %s socket to '%s': %m", socket_address_type_to_string(type), e);
512 }
513
514 if (address.sockaddr.sa.sa_family == AF_VSOCK) {
515 r = vsock_bind_privileged_port(fd);
516 if (r < 0 && !ERRNO_IS_PRIVILEGE(r))
517 return log_debug_errno(r, "Failed to bind socket to privileged port: %m");
518 }
519
520 if (IN_SET(type, SOCK_STREAM, SOCK_SEQPACKET)) {
521 if (connect(fd, &address.sockaddr.sa, address.size) < 0)
522 return log_debug_errno(errno, "Failed to connect socket to '%s': %m", e);
523
524 msghdr.msg_name = NULL;
525 msghdr.msg_namelen = 0;
526 }
527
528 (void) fd_inc_sndbuf(fd, SNDBUF_SIZE);
529
530 iovec = IOVEC_MAKE_STRING(state);
531
532 send_ucred =
533 (pid != 0 && pid != getpid_cached()) ||
534 getuid() != geteuid() ||
535 getgid() != getegid();
536
537 if (n_fds > 0 || send_ucred) {
538 /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */
539 msghdr.msg_controllen =
540 (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) +
541 (send_ucred ? CMSG_SPACE(sizeof(struct ucred)) : 0);
542
543 msghdr.msg_control = alloca0(msghdr.msg_controllen);
544
545 cmsg = CMSG_FIRSTHDR(&msghdr);
546 if (n_fds > 0) {
547 cmsg->cmsg_level = SOL_SOCKET;
548 cmsg->cmsg_type = SCM_RIGHTS;
549 cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds);
550
551 memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds);
552
553 if (send_ucred)
554 assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg));
555 }
556
557 if (send_ucred) {
558 struct ucred *ucred;
559
560 cmsg->cmsg_level = SOL_SOCKET;
561 cmsg->cmsg_type = SCM_CREDENTIALS;
562 cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
563
564 ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
565 ucred->pid = pid != 0 ? pid : getpid_cached();
566 ucred->uid = getuid();
567 ucred->gid = getgid();
568 }
569 }
570
571 do {
572 /* First try with fake ucred data, as requested */
573 n = sendmsg(fd, &msghdr, MSG_NOSIGNAL);
574 if (n < 0) {
575 if (!send_ucred)
576 return log_debug_errno(errno, "Failed to send notify message to '%s': %m", e);
577
578 /* If that failed, try with our own ucred instead */
579 msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred));
580 if (msghdr.msg_controllen == 0)
581 msghdr.msg_control = NULL;
582
583 n = 0;
584 send_ucred = false;
585 } else {
586 /* Unless we're using SOCK_STREAM, we expect to write all the contents immediately. */
587 if (type != SOCK_STREAM && (size_t) n < IOVEC_TOTAL_SIZE(msghdr.msg_iov, msghdr.msg_iovlen))
588 return -EIO;
589
590 /* Make sure we only send fds and ucred once, even if we're using SOCK_STREAM. */
591 msghdr.msg_control = NULL;
592 msghdr.msg_controllen = 0;
593 }
594 } while (!IOVEC_INCREMENT(msghdr.msg_iov, msghdr.msg_iovlen, n));
595
596 return 1;
597 }
598
599 _public_ int sd_pid_notify_with_fds(
600 pid_t pid,
601 int unset_environment,
602 const char *state,
603 const int *fds,
604 unsigned n_fds) {
605
606 int r;
607
608 r = pid_notify_with_fds_internal(pid, state, fds, n_fds);
609
610 if (unset_environment)
611 assert_se(unsetenv("NOTIFY_SOCKET") == 0);
612
613 return r;
614 }
615
616 _public_ int sd_pid_notify_barrier(pid_t pid, int unset_environment, uint64_t timeout) {
617 _cleanup_close_pair_ int pipe_fd[2] = PIPE_EBADF;
618 int r;
619
620 if (pipe2(pipe_fd, O_CLOEXEC) < 0)
621 return -errno;
622
623 r = sd_pid_notify_with_fds(pid, unset_environment, "BARRIER=1", &pipe_fd[1], 1);
624 if (r <= 0)
625 return r;
626
627 pipe_fd[1] = safe_close(pipe_fd[1]);
628
629 r = fd_wait_for_event(pipe_fd[0], 0 /* POLLHUP is implicit */, timeout);
630 if (r < 0)
631 return r;
632 if (r == 0)
633 return -ETIMEDOUT;
634
635 return 1;
636 }
637
638 _public_ int sd_notify_barrier(int unset_environment, uint64_t timeout) {
639 return sd_pid_notify_barrier(0, unset_environment, timeout);
640 }
641
642 _public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) {
643 return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0);
644 }
645
646 _public_ int sd_notify(int unset_environment, const char *state) {
647 return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0);
648 }
649
650 _public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) {
651 _cleanup_free_ char *p = NULL;
652 int r;
653
654 if (format) {
655 va_list ap;
656
657 va_start(ap, format);
658 r = vasprintf(&p, format, ap);
659 va_end(ap);
660
661 if (r < 0 || !p)
662 return -ENOMEM;
663 }
664
665 return sd_pid_notify(pid, unset_environment, p);
666 }
667
668 _public_ int sd_notifyf(int unset_environment, const char *format, ...) {
669 _cleanup_free_ char *p = NULL;
670 int r;
671
672 if (format) {
673 va_list ap;
674
675 va_start(ap, format);
676 r = vasprintf(&p, format, ap);
677 va_end(ap);
678
679 if (r < 0 || !p)
680 return -ENOMEM;
681 }
682
683 return sd_pid_notify(0, unset_environment, p);
684 }
685
686 _public_ int sd_pid_notifyf_with_fds(
687 pid_t pid,
688 int unset_environment,
689 const int *fds, size_t n_fds,
690 const char *format, ...) {
691
692 _cleanup_free_ char *p = NULL;
693 int r;
694
695 /* Paranoia check: we traditionally used 'unsigned' as array size, but we nowadays more correctly use
696 * 'size_t'. sd_pid_notifyf_with_fds() and sd_pid_notify_with_fds() are from different eras, hence
697 * differ in this. Let's catch resulting incompatibilites early, even though they are pretty much
698 * theoretic only */
699 if (n_fds > UINT_MAX)
700 return -E2BIG;
701
702 if (format) {
703 va_list ap;
704
705 va_start(ap, format);
706 r = vasprintf(&p, format, ap);
707 va_end(ap);
708
709 if (r < 0 || !p)
710 return -ENOMEM;
711 }
712
713 return sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds);
714 }
715
716 _public_ int sd_booted(void) {
717 /* We test whether the runtime unit file directory has been
718 * created. This takes place in mount-setup.c, so is
719 * guaranteed to happen very early during boot. */
720
721 if (laccess("/run/systemd/system/", F_OK) >= 0)
722 return true;
723
724 if (errno == ENOENT)
725 return false;
726
727 return -errno;
728 }
729
730 _public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) {
731 const char *s, *p = ""; /* p is set to dummy value to do unsetting */
732 uint64_t u;
733 int r = 0;
734
735 s = getenv("WATCHDOG_USEC");
736 if (!s)
737 goto finish;
738
739 r = safe_atou64(s, &u);
740 if (r < 0)
741 goto finish;
742 if (!timestamp_is_set(u)) {
743 r = -EINVAL;
744 goto finish;
745 }
746
747 p = getenv("WATCHDOG_PID");
748 if (p) {
749 pid_t pid;
750
751 r = parse_pid(p, &pid);
752 if (r < 0)
753 goto finish;
754
755 /* Is this for us? */
756 if (getpid_cached() != pid) {
757 r = 0;
758 goto finish;
759 }
760 }
761
762 if (usec)
763 *usec = u;
764
765 r = 1;
766
767 finish:
768 if (unset_environment && s)
769 assert_se(unsetenv("WATCHDOG_USEC") == 0);
770 if (unset_environment && p)
771 assert_se(unsetenv("WATCHDOG_PID") == 0);
772
773 return r;
774 }