1 /*        $NetBSD: pkill.c,v 1.34 2024/10/07 06:14:05 roy Exp $       */
2 
3 /*-
4  * Copyright (c) 2002, 2022 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Andrew Doran.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: pkill.c,v 1.34 2024/10/07 06:14:05 roy Exp $");
35 #endif /* !lint */
36 
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/sysctl.h>
40 #include <sys/proc.h>
41 #include <sys/queue.h>
42 #include <sys/resource.h>
43 #include <sys/stat.h>
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <limits.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <signal.h>
51 #include <regex.h>
52 #include <ctype.h>
53 #include <kvm.h>
54 #include <err.h>
55 #include <fcntl.h>
56 #include <pwd.h>
57 #include <grp.h>
58 #include <errno.h>
59 #include <paths.h>
60 
61 #define   STATUS_MATCH        0
62 #define   STATUS_NOMATCH      1
63 #define   STATUS_BADUSAGE     2
64 #define   STATUS_ERROR        3
65 
66 #define   MIN_PID             5
67 #define   MAX_PID             30000 // XXX PID_MAX from sys/proc.h?
68 
69 enum listtype {
70           LT_GENERIC,
71           LT_USER,
72           LT_GROUP,
73           LT_TTY,
74           LT_PGRP,
75           LT_SID
76 };
77 
78 struct list {
79           SLIST_ENTRY(list) li_chain;
80           long      li_number;
81 };
82 
83 SLIST_HEAD(listhead, list);
84 
85 static struct kinfo_proc2     *plist;
86 static char         *selected;
87 static const char *delim = "\n";
88 static int          nproc;
89 static int          pgrep;
90 static int          prenice;
91 static int          signum = SIGTERM;
92 static int          nicenum;
93 static int          newest;
94 static int          quiet;
95 static int          inverse;
96 static int          longfmt;
97 static int          matchargs;
98 static int          fullmatch;
99 static int          cflags = REG_EXTENDED;
100 static kvm_t        *kd;
101 static pid_t        mypid;
102 
103 static struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
104 static struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
105 static struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
106 static struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
107 static struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
108 static struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
109 static struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
110 
111 static void         usage(void) __dead;
112 static int          killact(const struct kinfo_proc2 *);
113 static int          reniceact(const struct kinfo_proc2 *);
114 static int          grepact(const struct kinfo_proc2 *);
115 static void         makelist(struct listhead *, enum listtype, char *);
116 static int          takepid(const char *, int);
117 
118 int
main(int argc,char ** argv)119 main(int argc, char **argv)
120 {
121           char buf[_POSIX2_LINE_MAX], **pargv, *q, *pidfile = NULL;
122           int i, j, ch, bestidx, rv, criteria, pidfromfile, pidfilelock = 0;
123           int (*action)(const struct kinfo_proc2 *);
124           const struct kinfo_proc2 *kp;
125           struct list *li;
126           const char *p;
127           u_int32_t bestsec, bestusec;
128           regex_t reg;
129           regmatch_t regmatch;
130 
131           setprogname(argv[0]);
132 
133           if (strcmp(getprogname(), "pgrep") == 0) {
134                     action = grepact;
135                     pgrep = 1;
136           } else if (strcmp(getprogname(), "prenice") == 0) {
137                     action = reniceact;
138                     prenice = 1;
139           } else {
140                     action = killact;
141                     p = argv[1];
142 
143                     if (argc > 1 && p[0] == '-') {
144                               p++;
145                               i = (int)strtol(p, &q, 10);
146                               if (*q == '\0') {
147                                         signum = i;
148                                         argv++;
149                                         argc--;
150                               } else {
151                                         if (strncasecmp(p, "sig", 3) == 0)
152                                                   p += 3;
153                                         for (i = 1; i < NSIG; i++)
154                                                   if (strcasecmp(sys_signame[i], p) == 0)
155                                                             break;
156                                         if (i != NSIG) {
157                                                   signum = i;
158                                                   argv++;
159                                                   argc--;
160                                         }
161                               }
162                     }
163           }
164 
165           criteria = 0;
166 
167           if (prenice) {
168                     if (argc < 2)
169                               usage();
170 
171                     if (strcmp(argv[1], "-l") == 0) {
172                               longfmt = 1;
173                               argv++;
174                               argc--;
175                     }
176 
177                     if (argc < 2)
178                               usage();
179 
180                     p = argv[1];
181 
182                     i = (int)strtol(p, &q, 10);
183                     if (*q == '\0') {
184                               nicenum = i;
185                               argv++;
186                               argc--;
187                     } else
188                               usage();
189           } else {
190                     while ((ch = getopt(argc, argv, "F:G:LP:U:d:fg:ilnqs:t:u:vx")) != -1)
191                               switch (ch) {
192                               case 'F':
193                                         pidfile = optarg;
194                                         criteria = 1;
195                                         break;
196                               case 'G':
197                                         makelist(&rgidlist, LT_GROUP, optarg);
198                                         criteria = 1;
199                                         break;
200                               case 'L':
201                                         pidfilelock = 1;
202                                         break;
203                               case 'P':
204                                         makelist(&ppidlist, LT_GENERIC, optarg);
205                                         criteria = 1;
206                                         break;
207                               case 'U':
208                                         makelist(&ruidlist, LT_USER, optarg);
209                                         criteria = 1;
210                                         break;
211                               case 'd':
212                                         if (!pgrep)
213                                                   usage();
214                                         delim = optarg;
215                                         break;
216                               case 'f':
217                                         matchargs = 1;
218                                         break;
219                               case 'g':
220                                         makelist(&pgrplist, LT_PGRP, optarg);
221                                         criteria = 1;
222                                         break;
223                               case 'i':
224                                         cflags |= REG_ICASE;
225                                         break;
226                               case 'l':
227                                         longfmt = 1;
228                                         break;
229                               case 'n':
230                                         newest = 1;
231                                         criteria = 1;
232                                         break;
233                               case 'q':
234                                         if (!pgrep)
235                                                   usage();
236                                         quiet = 1;
237                                         break;
238                               case 's':
239                                         makelist(&sidlist, LT_SID, optarg);
240                                         criteria = 1;
241                                         break;
242                               case 't':
243                                         makelist(&tdevlist, LT_TTY, optarg);
244                                         criteria = 1;
245                                         break;
246                               case 'u':
247                                         makelist(&euidlist, LT_USER, optarg);
248                                         criteria = 1;
249                                         break;
250                               case 'v':
251                                         inverse = 1;
252                                         break;
253                               case 'x':
254                                         fullmatch = 1;
255                                         break;
256                               default:
257                                         usage();
258                                         /* NOTREACHED */
259                               }
260                     argc -= optind;
261                     argv += optind;
262           }
263 
264           if (argc != 0)
265                     criteria = 1;
266           if (!criteria)
267                     usage();
268           if (pidfile != NULL)
269                     pidfromfile = takepid(pidfile, pidfilelock);
270           else {
271                     if (pidfilelock) {
272                               errx(STATUS_ERROR,
273                                   "Option -L doesn't make sense without -F");
274                     }
275                     pidfromfile = -1;
276           }
277 
278           mypid = getpid();
279 
280           /*
281            * Retrieve the list of running processes from the kernel.
282            */
283           kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, buf);
284           if (kd == NULL)
285                     errx(STATUS_ERROR, "Cannot open kernel files (%s)", buf);
286 
287           plist = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(*plist), &nproc);
288           if (plist == NULL)
289                     errx(STATUS_ERROR, "Cannot get process list (%s)",
290                         kvm_geterr(kd));
291 
292           /*
293            * Allocate memory which will be used to keep track of the
294            * selection.
295            */
296           if ((selected = calloc(sizeof(*selected), (size_t)nproc)) == NULL)
297                     err(STATUS_ERROR, "Cannot allocate memory for %d processes",
298                         nproc);
299 
300           /*
301            * Refine the selection.
302            */
303           for (; *argv != NULL; argv++) {
304                     if ((rv = regcomp(&reg, *argv, cflags)) != 0) {
305                               (void)regerror(rv, &reg, buf, sizeof(buf));
306                               errx(STATUS_BADUSAGE,
307                                   "Cannot compile regular expression `%s' (%s)",
308                                   *argv, buf);
309                     }
310 
311                     for (i = 0, kp = plist; i < nproc; i++, kp++) {
312                               if ((kp->p_flag & P_SYSTEM) != 0 || kp->p_pid == mypid)
313                                         continue;
314 
315                               if ((pargv = kvm_getargv2(kd, kp, 0)) == NULL)
316                                         continue;
317                               if (matchargs) {
318 
319                                         j = 0;
320                                         while (j < (int)sizeof(buf) && *pargv != NULL) {
321                                                   j += snprintf(buf + j, sizeof(buf) - j,
322                                                       pargv[1] != NULL ? "%s " : "%s",
323                                                       pargv[0]);
324                                                   pargv++;
325                                         }
326                               } else if (pargv[0] != NULL)
327                                         strlcpy(buf, pargv[0], sizeof(buf));
328                               else
329                                         strlcpy(buf, kp->p_comm, sizeof(buf));
330 
331                               rv = regexec(&reg, buf, 1, &regmatch, 0);
332                               if (rv == 0) {
333                                         if (fullmatch) {
334                                                   if (regmatch.rm_so == 0 &&
335                                                       regmatch.rm_eo ==
336                                                       (regoff_t)strlen(buf))
337                                                             selected[i] = 1;
338                                         } else
339                                                   selected[i] = 1;
340                               } else if (rv != REG_NOMATCH) {
341                                         (void)regerror(rv, &reg, buf, sizeof(buf));
342                                         errx(STATUS_ERROR,
343                                             "Regular expression evaluation error (%s)",
344                                             buf);
345                               }
346                     }
347 
348                     regfree(&reg);
349           }
350 
351           for (i = 0, kp = plist; i < nproc; i++, kp++) {
352                     if ((kp->p_flag & P_SYSTEM) != 0)
353                               continue;
354 
355                     if (pidfromfile >= 0 && kp->p_pid != pidfromfile) {
356                               selected[i] = 0;
357                               continue;
358                     }
359 
360                     SLIST_FOREACH(li, &ruidlist, li_chain)
361                               if (kp->p_ruid == (uid_t)li->li_number)
362                                         break;
363                     if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
364                               selected[i] = 0;
365                               continue;
366                     }
367 
368                     SLIST_FOREACH(li, &rgidlist, li_chain)
369                               if (kp->p_rgid == (gid_t)li->li_number)
370                                         break;
371                     if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
372                               selected[i] = 0;
373                               continue;
374                     }
375 
376                     SLIST_FOREACH(li, &euidlist, li_chain)
377                               if (kp->p_uid == (uid_t)li->li_number)
378                                         break;
379                     if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
380                               selected[i] = 0;
381                               continue;
382                     }
383 
384                     SLIST_FOREACH(li, &ppidlist, li_chain)
385                               if ((uid_t)kp->p_ppid == (uid_t)li->li_number)
386                                         break;
387                     if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
388                               selected[i] = 0;
389                               continue;
390                     }
391 
392                     SLIST_FOREACH(li, &pgrplist, li_chain)
393                               if (kp->p__pgid == (pid_t)li->li_number)
394                                         break;
395                     if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
396                               selected[i] = 0;
397                               continue;
398                     }
399 
400                     SLIST_FOREACH(li, &tdevlist, li_chain) {
401                               if (li->li_number == -1 &&
402                                   (kp->p_flag & P_CONTROLT) == 0)
403                                         break;
404                               if (kp->p_tdev == (uid_t)li->li_number)
405                                         break;
406                     }
407                     if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
408                               selected[i] = 0;
409                               continue;
410                     }
411 
412                     SLIST_FOREACH(li, &sidlist, li_chain)
413                               if (kp->p_sid == (pid_t)li->li_number)
414                                         break;
415                     if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
416                               selected[i] = 0;
417                               continue;
418                     }
419 
420                     if (argc == 0)
421                               selected[i] = 1;
422           }
423 
424           if (newest) {
425                     bestsec = 0;
426                     bestusec = 0;
427                     bestidx = -1;
428 
429                     for (i = 0, kp = plist; i < nproc; i++, kp++) {
430                               if (!selected[i])
431                                         continue;
432 
433                               if (kp->p_ustart_sec > bestsec ||
434                                   (kp->p_ustart_sec == bestsec
435                                   && kp->p_ustart_usec > bestusec)) {
436                                         bestsec = kp->p_ustart_sec;
437                                         bestusec = kp->p_ustart_usec;
438                                         bestidx = i;
439                               }
440                     }
441 
442                     (void)memset(selected, 0, (size_t)nproc);
443                     if (bestidx != -1)
444                               selected[bestidx] = 1;
445           }
446 
447           /*
448            * Take the appropriate action for each matched process, if any.
449            */
450           for (i = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
451                     if (kp->p_pid == mypid)
452                               continue;
453                     if (selected[i]) {
454                               if (inverse)
455                                         continue;
456                     } else if (!inverse)
457                               continue;
458 
459                     if ((kp->p_flag & P_SYSTEM) != 0)
460                               continue;
461 
462                     rv |= (*action)(kp);
463           }
464 
465           return rv ? STATUS_MATCH : STATUS_NOMATCH;
466 }
467 
468 static void
usage(void)469 usage(void)
470 {
471           const char *ustr;
472 
473           if (prenice)
474                     fprintf(stderr, "Usage: %s [-l] priority pattern ...\n",
475                         getprogname());
476           else {
477                     if (pgrep)
478                               ustr = "[-Lfilnqvx] [-d delim]";
479                     else
480                               ustr = "[-signal] [-Lfilnvx]";
481 
482                     (void)fprintf(stderr,
483                         "Usage: %s %s [-F pidfile] [-G gid] [-g pgrp] [-P ppid] [-s sid] "
484                                  "[-t tty]\n"
485                         "             [-U uid] [-u euid] pattern ...\n",
486                                     getprogname(), ustr);
487           }
488 
489           exit(STATUS_BADUSAGE);
490 }
491 
492 static int
killact(const struct kinfo_proc2 * kp)493 killact(const struct kinfo_proc2 *kp)
494 {
495           if (longfmt)
496                     grepact(kp);
497           if (kill(kp->p_pid, signum) == -1) {
498 
499                     /*
500                      * Check for ESRCH, which indicates that the process
501                      * disappeared between us matching it and us
502                      * signalling it; don't issue a warning about it.
503                      */
504                     if (errno != ESRCH)
505                               warn("signalling pid %d", (int)kp->p_pid);
506 
507                     /*
508                      * Return 0 to indicate that the process should not be
509                      * considered a match, since we didn't actually get to
510                      * signal it.
511                      */
512                     return 0;
513           }
514 
515           return 1;
516 }
517 
518 static int
reniceact(const struct kinfo_proc2 * kp)519 reniceact(const struct kinfo_proc2 *kp)
520 {
521           int oldprio;
522 
523           if (longfmt)
524                     grepact(kp);
525 
526           errno = 0;
527           if ((oldprio = getpriority(PRIO_PROCESS, kp->p_pid)) == -1 &&
528               errno != 0) {
529                     warn("%d: getpriority", kp->p_pid);
530                     return 0;
531           }
532 
533           if (setpriority(PRIO_PROCESS, kp->p_pid, nicenum) == -1) {
534                     warn("%d: setpriority", kp->p_pid);
535                     return 0;
536           }
537 
538           (void)printf("%d: old priority %d, new priority %d\n",
539               kp->p_pid, oldprio, nicenum);
540 
541           return 1;
542 }
543 
544 static int
grepact(const struct kinfo_proc2 * kp)545 grepact(const struct kinfo_proc2 *kp)
546 {
547           char **argv;
548 
549           if (quiet)
550                     return 1;
551 
552           if (longfmt && matchargs) {
553 
554                     /*
555                      * If kvm_getargv2() failed the process has probably
556                      * disappeared.  Return 0 to indicate that the process
557                      * should not be considered a match, since we are no
558                      * longer in a position to output it as a match.
559                      */
560                     if ((argv = kvm_getargv2(kd, kp, 0)) == NULL)
561                               return 0;
562 
563                     (void)printf("%d ", (int)kp->p_pid);
564                     for (; *argv != NULL; argv++) {
565                               (void)printf("%s", *argv);
566                               if (argv[1] != NULL)
567                                         (void)putchar(' ');
568                     }
569           } else if (longfmt)
570                     (void)printf("%d %s", (int)kp->p_pid, kp->p_comm);
571           else
572                     (void)printf("%d", (int)kp->p_pid);
573 
574           (void)printf("%s", delim);
575 
576           return 1;
577 }
578 
579 static void
makelist(struct listhead * head,enum listtype type,char * src)580 makelist(struct listhead *head, enum listtype type, char *src)
581 {
582           struct list *li;
583           struct passwd *pw;
584           struct group *gr;
585           struct stat st;
586           char *sp, *ep, buf[MAXPATHLEN];
587           const char *p;
588           int empty;
589           const char *prefix = _PATH_DEV;
590 
591           empty = 1;
592 
593           while ((sp = strsep(&src, ",")) != NULL) {
594                     if (*sp == '\0')
595                               usage();
596 
597                     if ((li = malloc(sizeof(*li))) == NULL)
598                               err(STATUS_ERROR, "Cannot allocate %zu bytes",
599                                   sizeof(*li));
600                     SLIST_INSERT_HEAD(head, li, li_chain);
601                     empty = 0;
602 
603                     li->li_number = (uid_t)strtol(sp, &ep, 0);
604                     if (*ep == '\0' && type != LT_TTY) {
605                               switch (type) {
606                               case LT_PGRP:
607                                         if (li->li_number == 0)
608                                                   li->li_number = getpgrp();
609                                         break;
610                               case LT_SID:
611                                         if (li->li_number == 0)
612                                                   li->li_number = getsid(mypid);
613                                         break;
614                               default:
615                                         break;
616                               }
617                               continue;
618                     }
619 
620                     switch (type) {
621                     case LT_USER:
622                               if ((pw = getpwnam(sp)) == NULL)
623                                         errx(STATUS_BADUSAGE, "Unknown user `%s'",
624                                             sp);
625                               li->li_number = pw->pw_uid;
626                               break;
627                     case LT_GROUP:
628                               if ((gr = getgrnam(sp)) == NULL)
629                                         errx(STATUS_BADUSAGE, "Unknown group `%s'",
630                                             sp);
631                               li->li_number = gr->gr_gid;
632                               break;
633                     case LT_TTY:
634                               p = sp;
635                               if (*sp == '/')
636                                         prefix = "";
637                               else if (strcmp(sp, "-") == 0) {
638                                         li->li_number = -1;
639                                         break;
640                               } else if (strcmp(sp, "co") == 0)
641                                         p = "console";
642                               else if (strncmp(sp, "tty", 3) == 0)
643                                         /* all set */;
644                               else if (strncmp(sp, "pts/", 4) == 0)
645                                         /* all set */;
646                               else if (*ep != '\0' || (strlen(sp) == 2 && *sp == '0'))
647                                         prefix = _PATH_TTY;
648                               else
649                                         prefix = _PATH_DEV_PTS;
650 
651                               (void)snprintf(buf, sizeof(buf), "%s%s", prefix, p);
652 
653                               if (stat(buf, &st) == -1) {
654                                         if (errno == ENOENT)
655                                                   errx(STATUS_BADUSAGE,
656                                                       "No such tty: `%s'", buf);
657                                         err(STATUS_ERROR, "Cannot access `%s'", buf);
658                               }
659 
660                               if ((st.st_mode & S_IFCHR) == 0)
661                                         errx(STATUS_BADUSAGE, "Not a tty: `%s'", buf);
662 
663                               li->li_number = st.st_rdev;
664                               break;
665                     default:
666                               usage();
667                     }
668           }
669 
670           if (empty)
671                     usage();
672 }
673 
674 static int
takepid(const char * pidfile,int pidfilelock)675 takepid(const char *pidfile, int pidfilelock)
676 {
677           char *endp, line[BUFSIZ];
678           FILE *fh;
679           long rval;
680 
681           fh = fopen(pidfile, "r");
682           if (fh == NULL)
683                     err(STATUS_ERROR, "Cannot open pidfile `%s'", pidfile);
684 
685           if (pidfilelock) {
686                     /*
687                      * If we can lock pidfile, this means that daemon is not
688                      * running, so would be better not to kill some random process.
689                      */
690                     if (flock(fileno(fh), LOCK_EX | LOCK_NB) == 0) {
691                               (void)fclose(fh);
692                               errx(STATUS_ERROR, "File '%s' can be locked", pidfile);
693                     } else {
694                               if (errno != EWOULDBLOCK) {
695                                         errx(STATUS_ERROR,
696                                             "Error while locking file '%s'", pidfile);
697                               }
698                     }
699           }
700 
701           if (fgets(line, sizeof(line), fh) == NULL) {
702                     if (feof(fh)) {
703                               (void)fclose(fh);
704                               errx(STATUS_ERROR, "Pidfile `%s' is empty", pidfile);
705                     }
706                     (void)fclose(fh);
707                     err(STATUS_ERROR, "Cannot read from pid file `%s'", pidfile);
708           }
709           (void)fclose(fh);
710 
711           rval = strtol(line, &endp, 10);
712           if (*endp != '\0' && !isspace((unsigned char)*endp))
713                     errx(STATUS_ERROR, "Invalid pid in file `%s'", pidfile);
714           else if (rval < MIN_PID || rval > MAX_PID)
715                     errx(STATUS_ERROR, "Invalid pid in file `%s'", pidfile);
716           return (rval);
717 }
718