Change GPL-2.0+/LGPL-2.0+ to GPL-2.0-or-later/LGPL-2.0-or-later
[platform/upstream/procps-ng.git] / w.c
1 /*
2  * w - show what logged in users are doing.
3  *
4  * Almost entirely rewritten from scratch by Charles Blake circa
5  * June 1996. Some vestigal traces of the original may exist.
6  * That was done in 1993 by Larry Greenfield with some fixes by
7  * Michael K. Johnson.
8  *
9  * Changes by Albert Cahalan, 2002.
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
24  */
25
26 #include "config.h"
27 #include "c.h"
28 #include "fileutils.h"
29 #include "nls.h"
30 #include "proc/devname.h"
31 #include "proc/escape.h"
32 #include "proc/procps.h"
33 #include "proc/readproc.h"
34 #include "proc/sysinfo.h"
35 #include "proc/version.h"
36 #include "proc/whattime.h"
37
38 #include <ctype.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <getopt.h>
42 #include <limits.h>
43 #include <locale.h>
44 #include <locale.h>
45 #include <pwd.h>
46 #include <pwd.h>
47 #include <signal.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sys/ioctl.h>
52 #include <sys/mman.h>
53 #include <sys/stat.h>
54 #include <sys/types.h>
55 #include <termios.h>
56 #include <time.h>
57 #include <unistd.h>
58 #ifdef HAVE_UTMPX_H
59 #       include <utmpx.h>
60 #else
61 #       include <utmp.h>
62 #endif
63 #include <arpa/inet.h>
64
65 static int ignoreuser = 0;      /* for '-u' */
66 static int oldstyle = 0;        /* for '-o' */
67 static proc_t **procs;          /* our snapshot of the process table */
68
69 #ifdef HAVE_UTMPX_H
70 typedef struct utmpx utmp_t;
71 #else
72 typedef struct utmp utmp_t;
73 #endif
74
75 #if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE)
76 #       define UT_HOSTSIZE __UT_HOSTSIZE
77 #       define UT_LINESIZE __UT_LINESIZE
78 #       define UT_NAMESIZE __UT_NAMESIZE
79 #endif
80
81 #ifdef W_SHOWFROM
82 # define FROM_STRING "on"
83 #else
84 # define FROM_STRING "off"
85 #endif
86
87 #define MAX_CMD_WIDTH   512
88 #define MIN_CMD_WIDTH   7
89
90 /*
91  * This routine is careful since some programs leave utmp strings
92  * unprintable. Always outputs at least 16 chars padded with
93  * spaces on the right if necessary.
94  */
95 static void print_host(const char *restrict host, int len, const int fromlen)
96 {
97         const char *last;
98         int width = 0;
99
100         if (len > fromlen)
101                 len = fromlen;
102         last = host + len;
103         for (; host < last; host++) {
104                 if (*host == '\0') break;
105                 if (isprint(*host) && *host != ' ') {
106                         fputc(*host, stdout);
107                         ++width;
108                 } else {
109                         fputc('-', stdout);
110                         ++width;
111                         break;
112                 }
113         }
114
115         /*
116          * space-fill, and a '-' too if needed to ensure the
117          * column exists
118          */
119         if (!width) {
120                 fputc('-', stdout);
121                 ++width;
122         }
123         while (width++ < fromlen)
124                 fputc(' ', stdout);
125 }
126
127
128 /* This routine prints the display part of the host or IPv6 link address interface */
129 static void print_display_or_interface(const char *restrict host, int len, int restlen)
130 {
131         const char *const end = host + (len > 0 ? len : 0);
132         const char *disp, *tmp;
133
134         if (restlen <= 0) return; /* not enough space for printing anything */
135
136         /* search for a collon (might be a display) */
137         disp = host;
138         while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++;
139
140         /* colon found */
141         if (disp < end && *disp == ':') {
142                 /* detect multiple colons -> IPv6 in the host (not a display) */
143                 tmp = disp+1;
144                 while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++;
145
146                 if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */
147
148                         /* number of chars till the end of the input field */
149                         len -= (disp - host);
150
151                         /* if it is still longer than the rest of the output field, then cut it */
152                         if (len > restlen) len = restlen;
153
154                         /* print the display */
155                         while ((len > 0) && isprint(*disp) && (*disp != ' ')) {
156                                 len--; restlen--;
157                                 fputc(*disp, stdout);
158                                 disp++;
159                         }
160
161                         if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
162                                 restlen--;
163                                 fputc('-', stdout);
164                         }
165                 } else { /* multiple colons found - it's an IPv6 address */
166
167                         /* search for % (interface separator in case of IPv6 link address) */
168                         while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++;
169
170                         if (tmp < end && *tmp == '%') { /* interface separator found */
171
172                                 /* number of chars till the end of the input field */
173                                 len -= (tmp - host);
174
175                                 /* if it is still longer than the rest of the output field, then cut it */
176                                 if (len > restlen) len = restlen;
177
178                                 /* print the interface */
179                                 while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) {
180                                         len--; restlen--;
181                                         fputc(*tmp, stdout);
182                                         tmp++;
183                                 }
184                                 if ((len > 0) && (*tmp != '\0')) {  /* space or nonprintable found - replace with dash and stop printing */
185                                         restlen--;
186                                         fputc('-', stdout);
187                                 }
188                         }
189                 }
190         }
191
192         /* padding with spaces */
193         while (restlen > 0) {
194                 fputc(' ', stdout);
195                 restlen--;
196         }
197 }
198
199
200 /* This routine prints either the hostname or the IP address of the remote */
201 static void print_from(const utmp_t *restrict const u, const int ip_addresses, const int fromlen) {
202         char buf[fromlen + 1];
203         char buf_ipv6[INET6_ADDRSTRLEN];
204         int len;
205 #ifndef __CYGWIN__
206         int32_t ut_addr_v6[4];      /* IP address of the remote host */
207
208         if (ip_addresses) { /* -i switch used */
209                 memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6));
210                 if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) {
211                         /* map back */
212                         ut_addr_v6[0] = ut_addr_v6[3];
213                         ut_addr_v6[1] = 0;
214                         ut_addr_v6[2] = 0;
215                         ut_addr_v6[3] = 0;
216                 }
217                 if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) {
218                         /* IPv6 */
219                         if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) {
220                                 strcpy(buf, ""); /* invalid address, clean the buffer */
221                         } else {
222                                 strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */
223                         }
224                 } else {
225                         /* IPv4 */
226                         if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) {
227                                 strcpy(buf, ""); /* invalid address, clean the buffer */
228                         }
229                 }
230                 buf[fromlen] = '\0';
231
232                 len = strlen(buf);
233                 if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */
234                         fputs(buf, stdout);
235                         /* show the display part of the host or IPv6 link addr. interface, if present */
236                         print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len);
237                 } else { /* IP address is empty, print the host instead */
238                         print_host(u->ut_host, UT_HOSTSIZE, fromlen);
239                 }
240         } else {  /* -i switch NOT used */
241                 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
242         }
243 #else
244         print_host(u->ut_host, UT_HOSTSIZE, fromlen);
245 #endif
246 }
247
248
249 /* compact 7 char format for time intervals (belongs in libproc?) */
250 static void print_time_ival7(time_t t, int centi_sec, FILE * fout)
251 {
252         if ((long)t < (long)0) {
253                 /* system clock changed? */
254                 fprintf(fout, "   ?   ");
255                 return;
256         }
257         if (oldstyle) {
258                 if (t >= 48 * 60 * 60)
259                         /* > 2 days */
260                         fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
261                 else if (t >= 60 * 60)
262                         /* > 1 hour */
263                         /* Translation Hint: Hours:Minutes */
264                         fprintf(fout, " %2lu:%02u ", t / (60 * 60),
265                                 (unsigned)((t / 60) % 60));
266                 else if (t > 60)
267                         /* > 1 minute */
268                         /* Translation Hint: Minutes:Seconds */
269                         fprintf(fout, _(" %2lu:%02um"), t / 60, (unsigned)t % 60);
270                 else
271                         fprintf(fout, "       ");
272         } else {
273                 if (t >= 48 * 60 * 60)
274                         /* 2 days or more */
275                         fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
276                 else if (t >= 60 * 60)
277                         /* 1 hour or more */
278                         /* Translation Hint: Hours:Minutes */
279                         fprintf(fout, _(" %2lu:%02um"), t / (60 * 60),
280                                 (unsigned)((t / 60) % 60));
281                 else if (t > 60)
282                         /* 1 minute or more */
283                         /* Translation Hint: Minutes:Seconds */
284                         fprintf(fout, " %2lu:%02u ", t / 60, (unsigned)t % 60);
285                 else
286                         /* Translation Hint: Seconds:Centiseconds */
287                         fprintf(fout, _(" %2lu.%02us"), t, centi_sec);
288         }
289 }
290
291 /* stat the device file to get an idle time */
292 static time_t idletime(const char *restrict const tty)
293 {
294         struct stat sbuf;
295         if (stat(tty, &sbuf) != 0)
296                 return 0;
297         return time(NULL) - sbuf.st_atime;
298 }
299
300 /* 7 character formatted login time */
301 static void print_logintime(time_t logt, FILE * fout)
302 {
303         /* Abbreviated of weekday can be longer than 3 characters,
304          * see for instance hu_HU.  Using 16 is few bytes more than
305          * enough.  */
306         char time_str[16];
307         time_t curt;
308         struct tm *logtm, *curtm;
309         int today;
310
311         curt = time(NULL);
312         if (curt == (time_t)(-1)) goto error;
313         curtm = localtime(&curt);
314         if (!curtm) goto error;
315         /* localtime returns a pointer to static memory */
316         today = curtm->tm_yday;
317         logtm = localtime(&logt);
318         if (!logtm) goto error;
319         if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) {
320                 if (curt - logt > 6 * 24 * 60 * 60) {
321                         if (!strftime(time_str, sizeof(time_str), "%b", logtm)) goto error;
322                         fprintf(fout, " %02d%3s%02d", logtm->tm_mday,
323                                 time_str, logtm->tm_year % 100);
324                 } else {
325                         if (!strftime(time_str, sizeof(time_str), "%a", logtm)) goto error;
326                         fprintf(fout, " %3s%02d  ", time_str,
327                                 logtm->tm_hour);
328                 }
329         } else {
330                 fprintf(fout, " %02d:%02d  ", logtm->tm_hour, logtm->tm_min);
331         }
332         return;
333 error:
334         fprintf(fout, " ???????");
335 }
336
337 /*
338  * This function scans the process table accumulating total cpu
339  * times for any processes "associated" with this login session.
340  * It also searches for the "best" process to report as "(w)hat"
341  * the user for that login session is doing currently. This the
342  * essential core of 'w'.
343  */
344 static const proc_t *getproc(const utmp_t * restrict const u,
345                              const char *restrict const tty,
346                              unsigned long long *restrict const jcpu,
347                              int *restrict const found_utpid)
348 {
349         int line;
350         proc_t **pptr = procs;
351         const proc_t *best = NULL;
352         const proc_t *secondbest = NULL;
353         unsigned uid = ~0U;
354
355         *found_utpid = 0;
356         if (!ignoreuser) {
357                 char buf[UT_NAMESIZE + 1];
358                 /* pointer to static data */
359                 struct passwd *passwd_data;
360                 strncpy(buf, u->ut_user, UT_NAMESIZE);
361                 buf[UT_NAMESIZE] = '\0';
362                 passwd_data = getpwnam(buf);
363                 if (!passwd_data)
364                         return NULL;
365                 uid = passwd_data->pw_uid;
366                 /* OK to have passwd_data go out of scope here */
367         }
368         line = tty_to_dev(tty);
369         *jcpu = 0;
370         for (; *pptr; pptr++) {
371                 const proc_t *restrict const tmp = *pptr;
372                 if (unlikely(tmp->tgid == u->ut_pid)) {
373                         *found_utpid = 1;
374             if (!best)
375                 best = tmp;
376                 }
377                 if (tmp->tty != line)
378                         continue;
379                 (*jcpu) += tmp->utime + tmp->stime;
380                 secondbest = tmp;
381                 /* same time-logic here as for "best" below */
382                 if (!(secondbest && tmp->start_time <= secondbest->start_time)) {
383                         secondbest = tmp;
384                 }
385                 if (!ignoreuser && uid != tmp->euid && uid != tmp->ruid)
386                         continue;
387                 if (tmp->pgrp != tmp->tpgid)
388                         continue;
389                 if (best && tmp->start_time <= best->start_time)
390                         continue;
391                 best = tmp;
392         }
393         return best ? best : secondbest;
394 }
395
396 static void showinfo(utmp_t * u, int formtype, int maxcmd, int from,
397                      const int userlen, const int fromlen, const int ip_addresses)
398 {
399         unsigned long long jcpu;
400         int ut_pid_found;
401         unsigned i;
402         char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/";
403         const proc_t *best;
404
405         for (i = 0; i < UT_LINESIZE; i++)
406                 /* clean up tty if garbled */
407                 if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/'))
408                         tty[i + 5] = u->ut_line[i];
409                 else
410                         tty[i + 5] = '\0';
411
412         best = getproc(u, tty + 5, &jcpu, &ut_pid_found);
413
414         /*
415          * just skip if stale utmp entry (i.e. login proc doesn't
416          * exist). If there is a desire a cmdline flag could be
417          * added to optionally show it with a prefix of (stale)
418          * in front of cmd or something like that.
419          */
420         if (!ut_pid_found)
421                 return;
422
423         /* force NUL term for printf */
424         strncpy(uname, u->ut_user, UT_NAMESIZE);
425
426         if (formtype) {
427                 printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, u->ut_line);
428                 if (from)
429                         print_from(u, ip_addresses, fromlen);
430 #ifdef HAVE_UTMPX_H
431                 print_logintime(u->ut_tv.tv_sec, stdout);
432 #else
433                 print_logintime(u->ut_time, stdout);
434 #endif
435                 if (*u->ut_line == ':')
436                         /* idle unknown for xdm logins */
437                         printf(" ?xdm? ");
438                 else
439                         print_time_ival7(idletime(tty), 0, stdout);
440                 print_time_ival7(jcpu / Hertz, (jcpu % Hertz) * (100. / Hertz),
441                                  stdout);
442                 if (best) {
443                         unsigned long long pcpu = best->utime + best->stime;
444                         print_time_ival7(pcpu / Hertz,
445                                          (pcpu % Hertz) * (100. / Hertz),
446                                          stdout);
447                 } else
448                         printf("   ?   ");
449         } else {
450                 printf("%-*.*s%-9.8s", userlen + 1, userlen, u->ut_user,
451                        u->ut_line);
452                 if (from)
453                         print_from(u, ip_addresses, fromlen);
454                 if (*u->ut_line == ':')
455                         /* idle unknown for xdm logins */
456                         printf(" ?xdm? ");
457                 else
458                         print_time_ival7(idletime(tty), 0, stdout);
459         }
460         fputs(" ", stdout);
461         if (likely(best)) {
462                 char cmdbuf[MAX_CMD_WIDTH];
463                 escape_command(cmdbuf, best, sizeof cmdbuf, &maxcmd, ESC_ARGS);
464                 fputs(cmdbuf, stdout);
465         } else {
466                 printf("-");
467         }
468         fputc('\n', stdout);
469 }
470
471 static void __attribute__ ((__noreturn__))
472     usage(FILE * out)
473 {
474         fputs(USAGE_HEADER, out);
475         fprintf(out,
476               _(" %s [options]\n"), program_invocation_short_name);
477         fputs(USAGE_OPTIONS, out);
478         fputs(_(" -h, --no-header     do not print header\n"),out);
479         fputs(_(" -u, --no-current    ignore current process username\n"),out);
480         fputs(_(" -s, --short         short format\n"),out);
481         fputs(_(" -f, --from          show remote hostname field\n"),out);
482         fputs(_(" -o, --old-style     old style output\n"),out);
483         fputs(_(" -i, --ip-addr       display IP address instead of hostname (if possible)\n"), out);
484         fputs(USAGE_SEPARATOR, out);
485         fputs(_("     --help     display this help and exit\n"), out);
486         fputs(USAGE_VERSION, out);
487         fprintf(out, USAGE_MAN_TAIL("w(1)"));
488
489         exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
490 }
491
492 int main(int argc, char **argv)
493 {
494         char *user = NULL, *p;
495         utmp_t *u;
496         struct winsize win;
497         int ch;
498         int maxcmd = 80;
499         int userlen = 8;
500         int fromlen = 16;
501         char *env_var;
502
503         /* switches (defaults) */
504         int header = 1;
505         int longform = 1;
506         int from = 1;
507         int ip_addresses = 0;
508
509         enum {
510                 HELP_OPTION = CHAR_MAX + 1
511         };
512
513         static const struct option longopts[] = {
514                 {"no-header", no_argument, NULL, 'h'},
515                 {"no-current", no_argument, NULL, 'u'},
516                 {"short", no_argument, NULL, 's'},
517                 {"from", no_argument, NULL, 'f'},
518                 {"old-style", no_argument, NULL, 'o'},
519                 {"ip-addr", no_argument, NULL, 'i'},
520                 {"help", no_argument, NULL, HELP_OPTION},
521                 {"version", no_argument, NULL, 'V'},
522                 {NULL, 0, NULL, 0}
523         };
524
525 #ifdef HAVE_PROGRAM_INVOCATION_NAME
526         program_invocation_name = program_invocation_short_name;
527 #endif
528         setlocale (LC_ALL, "");
529         bindtextdomain(PACKAGE, LOCALEDIR);
530         textdomain(PACKAGE);
531         atexit(close_stdout);
532
533 #ifndef W_SHOWFROM
534         from = 0;
535 #endif
536
537         while ((ch =
538                 getopt_long(argc, argv, "husfoVi", longopts, NULL)) != -1)
539                 switch (ch) {
540                 case 'h':
541                         header = 0;
542                         break;
543                 case 'l':
544                         longform = 1;
545                         break;
546                 case 's':
547                         longform = 0;
548                         break;
549                 case 'f':
550                         from = !from;
551                         break;
552                 case 'V':
553                         printf(PROCPS_NG_VERSION);
554                         exit(0);
555                 case 'u':
556                         ignoreuser = 1;
557                         break;
558                 case 'o':
559                         oldstyle = 1;
560                         break;
561                 case 'i':
562                         ip_addresses = 1;
563                         from = 1;
564                         break;
565                 case HELP_OPTION:
566                         usage(stdout);
567                 default:
568                         usage(stderr);
569                 }
570
571         if ((argv[optind]))
572                 user = (argv[optind]);
573
574         /* Get user field length from environment */
575         if ((env_var = getenv("PROCPS_USERLEN")) != NULL) {
576                 int ut_namesize = UT_NAMESIZE;
577                 userlen = atoi(env_var);
578                 if (userlen < 8 || ut_namesize < userlen) {
579                         xwarnx
580                             (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"),
581                              ut_namesize);
582                         userlen = 8;
583                 }
584         }
585         /* Get from field length from environment */
586         if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) {
587                 fromlen = atoi(env_var);
588                 if (fromlen < 8 || UT_HOSTSIZE < fromlen) {
589                         xwarnx
590                             (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"),
591                              UT_HOSTSIZE);
592                         fromlen = 16;
593                 }
594         }
595         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
596                 maxcmd = win.ws_col;
597         else if ((p = getenv("COLUMNS")))
598                 maxcmd = atoi(p);
599         else
600                 maxcmd = MAX_CMD_WIDTH;
601 #define CLAMP_CMD_WIDTH(cw) do { \
602         if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \
603         if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \
604 } while (0)
605         CLAMP_CMD_WIDTH(maxcmd);
606         maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0);
607         CLAMP_CMD_WIDTH(maxcmd);
608 #undef CLAMP_CMD_WIDTH
609
610         procs = readproctab(PROC_FILLCOM | PROC_FILLUSR | PROC_FILLSTAT);
611
612         if (header) {
613                 /* print uptime and headers */
614                 print_uptime(0);
615                 /* Translation Hint: Following five uppercase messages are
616                  * headers. Try to keep alignment intact.  */
617                 printf(_("%-*s TTY      "), userlen, _("USER"));
618                 if (from)
619                         printf("%-*s", fromlen - 1, _("FROM"));
620                 if (longform)
621                         printf(_("  LOGIN@   IDLE   JCPU   PCPU WHAT\n"));
622                 else
623                         printf(_("   IDLE WHAT\n"));
624         }
625
626 #ifdef HAVE_UTMPX_H
627         setutxent();
628 #else
629         utmpname(UTMP_FILE);
630         setutent();
631 #endif
632         if (user) {
633                 for (;;) {
634 #ifdef HAVE_UTMPX_H
635                         u = getutxent();
636 #else
637                         u = getutent();
638 #endif
639                         if (unlikely(!u))
640                                 break;
641                         if (u->ut_type != USER_PROCESS)
642                                 continue;
643                         if (!strncmp(u->ut_user, user, UT_NAMESIZE))
644                                 showinfo(u, longform, maxcmd, from, userlen,
645                                          fromlen, ip_addresses);
646                 }
647         } else {
648                 for (;;) {
649 #ifdef HAVE_UTMPX_H
650                         u = getutxent();
651 #else
652                         u = getutent();
653 #endif
654                         if (unlikely(!u))
655                                 break;
656                         if (u->ut_type != USER_PROCESS)
657                                 continue;
658                         if (*u->ut_user)
659                                 showinfo(u, longform, maxcmd, from, userlen,
660                                          fromlen, ip_addresses);
661                 }
662         }
663 #ifdef HAVE_UTMPX_H
664         endutxent();
665 #else
666         endutent();
667 #endif
668
669         return EXIT_SUCCESS;
670 }