source: rtems/cpukit/libmisc/shell/main_rm.c @ b079fe33

4.104.115
Last change on this file since b079fe33 was b079fe33, checked in by Joel Sherrill <joel.sherrill@…>, on 10/02/08 at 20:16:16

2008-10-02 Joel Sherrill <joel.sherrill@…>

  • libmisc/shell/main_cp.c, libmisc/shell/main_ls.c, libmisc/shell/main_mv.c, libmisc/shell/main_netstats.c, libmisc/shell/main_rm.c, libmisc/shell/shell_script.c: Newlib > 1.16.0 requires need_getopt_newlib to be defined to get visibility on the reentrancy extensions.
  • Property mode set to 100644
File size: 16.8 KB
Line 
1/*-
2 * Copyright (c) 1990, 1993, 1994
3 *      The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#ifdef HAVE_CONFIG_H
31#include "config.h"
32#endif
33
34#if 0
35#ifndef lint
36static const char copyright[] =
37"@(#) Copyright (c) 1990, 1993, 1994\n\
38        The Regents of the University of California.  All rights reserved.\n";
39#endif /* not lint */
40
41#ifndef lint
42static char sccsid[] = "@(#)rm.c        8.5 (Berkeley) 4/18/94";
43#endif /* not lint */
44#endif
45#if 0
46#include <sys/cdefs.h>
47__FBSDID("$FreeBSD: src/bin/rm/rm.c,v 1.58 2006/10/31 02:22:36 delphij Exp $");
48#endif
49
50#include <rtems.h>
51#include <rtems/shell.h>
52#include <rtems/shellconfig.h>
53#define __need_getopt_newlib
54#include <getopt.h>
55
56#include <sys/stat.h>
57#include <sys/param.h>
58
59#include <err.h>
60#include <errno.h>
61#include <fcntl.h>
62#include <fts.h>
63#include <grp.h>
64#include <pwd.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#include <unistd.h>
69
70/* RTEMS specific changes */
71typedef struct {
72  int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
73  int rflag, Iflag;
74  uid_t uid;
75  int exit_code;
76  jmp_buf exit_jmp;
77} rtems_shell_rm_globals;
78
79#define dflag     globals->dflag
80#define eval      globals->eval
81#define fflag     globals->fflag
82#define iflag     globals->iflag
83#define Pflag     globals->Pflag
84#define vflag     globals->vflag
85#define Wflag     globals->Wflag
86#define stdin_ok  globals->stdin_ok
87#define rflag     globals->rflag
88#define Iflag     globals->Iflag
89#define xuid       globals->uid
90#define exit_jump &(globals->exit_jmp)
91
92#include <setjmp.h>
93
94#define exit(ec) rtems_shell_rm_exit(globals, ec)
95void
96rtems_shell_rm_exit (rtems_shell_rm_globals* globals, int code)
97{
98  globals->exit_code = code;
99  longjmp (globals->exit_jmp, 1);
100}
101
102static int main_rm(rtems_shell_rm_globals* globals, int argc, char *argv[]);
103
104int
105rtems_shell_main_rm(int argc, char *argv[])
106{
107  rtems_shell_rm_globals rm_globals;
108  memset (&rm_globals, 0, sizeof (rm_globals));
109  rm_globals.exit_code = 1;
110  if (setjmp (rm_globals.exit_jmp) == 0)
111    return main_rm (&rm_globals, argc, argv);
112  return rm_globals.exit_code;
113}
114
115#define check(a1, a2, a3)    check_rm(globals, a1, a2, a3)
116#define check2(a1)           check2_rm(globals, a1)
117#define checkdot(a1)         checkdot_rm(globals, a1)
118#define checkslash(a1)       checkslash_rm(globals, a1)
119#define rm_file(a1)          rm_file_rm(globals, a1)
120#define rm_overwrite(a1, a2) rm_overwrite_rm(globals, a1, a2)
121#define rm_tree(a1)          rm_tree_rm(globals, a1)
122#define usage()              usage_rm(globals)
123
124
125/* RTEMS changes */
126
127static int      check_rm(rtems_shell_rm_globals* globals, char *, char *, struct stat *);
128static int      check2_rm(rtems_shell_rm_globals* globals, char **);
129static void     checkdot_rm(rtems_shell_rm_globals* globals, char **);
130static void     checkslash_rm(rtems_shell_rm_globals* globals, char **);
131static void     rm_file_rm(rtems_shell_rm_globals* globals, char **);
132static int      rm_overwrite_rm(rtems_shell_rm_globals* globals, char *, struct stat *);
133static void     rm_tree_rm(rtems_shell_rm_globals* globals, char **);
134static void     usage_rm(rtems_shell_rm_globals* globals);
135
136/*
137 * rm --
138 *      This rm is different from historic rm's, but is expected to match
139 *      POSIX 1003.2 behavior.  The most visible difference is that -f
140 *      has two specific effects now, ignore non-existent files and force
141 *      file removal.
142 */
143int
144main_rm(rtems_shell_rm_globals* globals, int argc, char *argv[])
145{
146        int ch;
147        char *p;
148 
149  struct getopt_data getopt_reent;
150  memset(&getopt_reent, 0, sizeof(getopt_data));
151 
152        /*
153         * Test for the special case where the utility is called as
154         * "unlink", for which the functionality provided is greatly
155         * simplified.
156         */
157        if ((p = rindex(argv[0], '/')) == NULL)
158                p = argv[0];
159        else
160                ++p;
161        if (strcmp(p, "unlink") == 0) {
162                while (getopt_r(argc, argv, "", &getopt_reent) != -1)
163                        usage();
164                argc -= getopt_reent.optind;
165                argv += getopt_reent.optind;
166                if (argc != 1)
167                        usage();
168                rm_file(&argv[0]);
169                exit(eval);
170        }
171
172        Pflag = rflag = 0;
173        while ((ch = getopt_r(argc, argv, "dfiIPRrvW", &getopt_reent)) != -1)
174                switch(ch) {
175                case 'd':
176                        dflag = 1;
177                        break;
178                case 'f':
179                        fflag = 1;
180                        iflag = 0;
181                        break;
182                case 'i':
183                        fflag = 0;
184                        iflag = 1;
185                        break;
186                case 'I':
187                        Iflag = 1;
188                        break;
189                case 'P':
190                        Pflag = 1;
191                        break;
192                case 'R':
193                case 'r':                       /* Compatibility. */
194                        rflag = 1;
195                        break;
196                case 'v':
197                        vflag = 1;
198                        break;
199                case 'W':
200                        Wflag = 1;
201                        break;
202                default:
203                        usage();
204                }
205        argc -= getopt_reent.optind;
206        argv += getopt_reent.optind;
207
208        if (argc < 1) {
209                if (fflag)
210                        return (0);
211                usage();
212        }
213
214        checkdot(argv);
215        if (getenv("POSIXLY_CORRECT") == NULL)
216                checkslash(argv);
217        xuid = geteuid();
218
219        if (*argv) {
220                stdin_ok = isatty(STDIN_FILENO);
221
222                if (Iflag) {
223                        if (check2(argv) == 0)
224                                exit (1);
225                }
226                if (rflag)
227                        rm_tree(argv);
228                else
229                        rm_file(argv);
230        }
231
232        exit (eval);
233  return 0;
234}
235
236void
237rm_tree_rm(rtems_shell_rm_globals* globals, char **argv)
238{
239        FTS *fts;
240        FTSENT *p;
241        int needstat;
242        int flags;
243        int rval;
244
245        /*
246         * Remove a file hierarchy.  If forcing removal (-f), or interactive
247         * (-i) or can't ask anyway (stdin_ok), don't stat the file.
248         */
249        needstat = !xuid || (!fflag && !iflag && stdin_ok);
250
251        /*
252         * If the -i option is specified, the user can skip on the pre-order
253         * visit.  The fts_number field flags skipped directories.
254         */
255#define SKIPPED 1
256
257        flags = FTS_PHYSICAL;
258        if (!needstat)
259                flags |= FTS_NOSTAT;
260        if (Wflag)
261                flags |= FTS_WHITEOUT;
262        if (!(fts = fts_open(argv, flags, NULL))) {
263                if (fflag && errno == ENOENT)
264                        return;
265                err(exit_jump, 1, "fts_open");
266        }
267        while ((p = fts_read(fts)) != NULL) {
268                switch (p->fts_info) {
269                case FTS_DNR:
270                        if (!fflag || p->fts_errno != ENOENT) {
271                                warnx("%s: %s",
272                                    p->fts_path, strerror(p->fts_errno));
273                                eval = 1;
274                        }
275                        continue;
276                case FTS_ERR:
277                        errx(exit_jump, 1, "%s: %s", p->fts_path, strerror(p->fts_errno));
278                case FTS_NS:
279                        /*
280                         * Assume that since fts_read() couldn't stat the
281                         * file, it can't be unlinked.
282                         */
283                        if (!needstat)
284                                break;
285                        if (!fflag || p->fts_errno != ENOENT) {
286                                warnx("%s: %s",
287                                    p->fts_path, strerror(p->fts_errno));
288                                eval = 1;
289                        }
290                        continue;
291                case FTS_D:
292                        /* Pre-order: give user chance to skip. */
293                        if (!fflag && !check(p->fts_path, p->fts_accpath,
294                            p->fts_statp)) {
295                                (void)fts_set(fts, p, FTS_SKIP);
296                                p->fts_number = SKIPPED;
297                        }
298#if RTEMS_REMOVED
299                        else if (!xuid &&
300                                 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
301                                 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
302                                 chflags(p->fts_accpath,
303                                         p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
304                                goto err;
305#endif
306                        continue;
307                case FTS_DP:
308                        /* Post-order: see if user skipped. */
309                        if (p->fts_number == SKIPPED)
310                                continue;
311                        break;
312                default:
313                        if (!fflag &&
314                            !check(p->fts_path, p->fts_accpath, p->fts_statp))
315                                continue;
316                }
317
318                rval = 0;
319#if RTEMS_REMOVED
320                if (!xuid &&
321                    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
322                    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
323                        rval = chflags(p->fts_accpath,
324                                       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
325#endif
326                if (rval == 0) {
327                        /*
328                         * If we can't read or search the directory, may still be
329                         * able to remove it.  Don't print out the un{read,search}able
330                         * message unless the remove fails.
331                         */
332                        switch (p->fts_info) {
333                        case FTS_DP:
334                        case FTS_DNR:
335                                rval = rmdir(p->fts_accpath);
336                                if (rval == 0 || (fflag && errno == ENOENT)) {
337                                        if (rval == 0 && vflag)
338                                                (void)printf("%s\n",
339                                                    p->fts_path);
340                                        continue;
341                                }
342                                break;
343
344#if RTEMS_REMOVED
345                        case FTS_W:
346                                rval = undelete(p->fts_accpath);
347                                if (rval == 0 && (fflag && errno == ENOENT)) {
348                                        if (vflag)
349                                                (void)printf("%s\n",
350                                                    p->fts_path);
351                                        continue;
352                                }
353                                break;
354#endif
355
356                        case FTS_NS:
357                                /*
358                                 * Assume that since fts_read() couldn't stat
359                                 * the file, it can't be unlinked.
360                                 */
361                                if (fflag)
362                                        continue;
363                                /* FALLTHROUGH */
364                        default:
365                                if (Pflag)
366                                        if (!rm_overwrite(p->fts_accpath, NULL))
367                                                continue;
368                                rval = unlink(p->fts_accpath);
369                                if (rval == 0 || (fflag && errno == ENOENT)) {
370                                        if (rval == 0 && vflag)
371                                                (void)printf("%s\n",
372                                                    p->fts_path);
373                                        continue;
374                                }
375                        }
376                }
377#if RTEMS_REMOVED
378err:
379#endif
380                warn("%s", p->fts_path);
381                eval = 1;
382        }
383        if (errno)
384                err(exit_jump, 1, "fts_read");
385        fts_close(fts);
386}
387
388#define S_ISWHT(m) (0)
389
390void
391rm_file_rm(rtems_shell_rm_globals* globals, char **argv)
392{
393        struct stat sb;
394        int rval;
395        char *f;
396
397        /*
398         * Remove a file.  POSIX 1003.2 states that, by default, attempting
399         * to remove a directory is an error, so must always stat the file.
400         */
401        while ((f = *argv++) != NULL) {
402                /* Assume if can't stat the file, can't unlink it. */
403                if (lstat(f, &sb)) {
404#if RTEMS_REMOVED
405                        if (Wflag) {
406                                sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
407                        } else {
408#endif
409                                if (!fflag || errno != ENOENT) {
410                                        warn("%s", f);
411                                        eval = 1;
412                                }
413                                continue;
414#if RTEMS_REMOVED
415                        }
416#endif
417                } else if (Wflag) {
418                        warnx("%s: %s", f, strerror(EEXIST));
419                        eval = 1;
420                        continue;
421                }
422
423                if (S_ISDIR(sb.st_mode) && !dflag) {
424                        warnx("%s: is a directory", f);
425                        eval = 1;
426                        continue;
427                }
428                if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
429                        continue;
430                rval = 0;
431#if RTEMS_REMOVED
432                if (!xuid && !S_ISWHT(sb.st_mode) &&
433                    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
434                    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
435                        rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
436#endif
437                if (rval == 0) {
438                        if (S_ISWHT(sb.st_mode))
439#if RTEMS_REMOVED
440                                rval = undelete(f);
441#endif
442      ;
443                        else if (S_ISDIR(sb.st_mode))
444                                rval = rmdir(f);
445                        else {
446                                if (Pflag)
447                                        if (!rm_overwrite(f, &sb))
448                                                continue;
449                                rval = unlink(f);
450                        }
451                }
452                if (rval && (!fflag || errno != ENOENT)) {
453                        warn("%s", f);
454                        eval = 1;
455                }
456                if (vflag && rval == 0)
457                        (void)printf("%s\n", f);
458        }
459}
460
461/*
462 * rm_overwrite --
463 *      Overwrite the file 3 times with varying bit patterns.
464 *
465 * XXX
466 * This is a cheap way to *really* delete files.  Note that only regular
467 * files are deleted, directories (and therefore names) will remain.
468 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
469 * System V file system).  In a logging file system, you'll have to have
470 * kernel support.
471 */
472int
473rm_overwrite_rm(rtems_shell_rm_globals* globals, char *file, struct stat *sbp)
474{
475        struct stat sb;
476#if RTEMS_REMOVED
477        struct statfs fsb;
478#endif
479        off_t len;
480        int bsize, fd, wlen;
481        char *buf = NULL;
482
483        fd = -1;
484        if (sbp == NULL) {
485                if (lstat(file, &sb))
486                        goto err;
487                sbp = &sb;
488        }
489        if (!S_ISREG(sbp->st_mode))
490                return (1);
491        if (sbp->st_nlink > 1 && !fflag) {
492                warnx("%s (inode %lu): not overwritten due to multiple links",
493                    file, sbp->st_ino);
494                return (0);
495        }
496        if ((fd = open(file, O_WRONLY, 0)) == -1)
497                goto err;
498#if RTEMS_REMOVED
499        if (fstatfs(fd, &fsb) == -1)
500                goto err;
501        bsize = MAX(fsb.f_iosize, 1024);
502#endif
503  bsize = 1024;
504        if ((buf = malloc(bsize)) == NULL)
505                err(exit_jump, 1, "%s: malloc", file);
506
507#define PASS(byte) {                                                    \
508        memset(buf, byte, bsize);                                       \
509        for (len = sbp->st_size; len > 0; len -= wlen) {                \
510                wlen = len < bsize ? len : bsize;                       \
511                if (write(fd, buf, wlen) != wlen)                       \
512                        goto err;                                       \
513        }                                                               \
514}
515        PASS(0xff);
516        if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
517                goto err;
518        PASS(0x00);
519        if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
520                goto err;
521        PASS(0xff);
522        if (!fsync(fd) && !close(fd)) {
523                free(buf);
524                return (1);
525        }
526
527err:    eval = 1;
528        if (buf)
529                free(buf);
530        if (fd != -1)
531                close(fd);
532        warn("%s", file);
533        return (0);
534}
535
536void strmode(mode_t mode, char *p);
537char *fflagstostr(u_long flags);
538const char *user_from_uid(uid_t uid, int nouser);
539
540int
541check_rm(rtems_shell_rm_globals* globals, char *path, char *name, struct stat *sp)
542{
543        int ch, first;
544        char modep[15], *flagsp;
545
546        /* Check -i first. */
547        if (iflag)
548                (void)fprintf(stderr, "remove %s? ", path);
549        else {
550                /*
551                 * If it's not a symbolic link and it's unwritable and we're
552                 * talking to a terminal, ask.  Symbolic links are excluded
553                 * because their permissions are meaningless.  Check stdin_ok
554                 * first because we may not have stat'ed the file.
555                 */
556#if RTEMS_REMOVED
557                if (!stdin_ok || S_ISLNK(sp->st_mode) ||
558                    (!access(name, W_OK) &&
559                    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
560                    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !xuid)))
561#endif
562                if (!stdin_ok || S_ISLNK(sp->st_mode) ||
563                    (!access(name, W_OK)))
564                        return (1);
565                strmode(sp->st_mode, modep);
566#if RTEMS_REMOVED
567                if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
568                        err(exit_jump, 1, "fflagstostr");
569#else
570    flagsp = "no supported";
571#endif
572                if (Pflag)
573                        errx(exit_jump, 1,
574                            "%s: -P was specified, but file is not writable",
575                            path);
576                (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
577                    modep + 1, modep[9] == ' ' ? "" : " ",
578                    user_from_uid(sp->st_uid, 0),
579                    group_from_gid(sp->st_gid, 0),
580                    *flagsp ? flagsp : "", *flagsp ? " " : "",
581                    path);
582                free(flagsp);
583        }
584        (void)fflush(stderr);
585
586        first = ch = getchar();
587        while (ch != '\n' && ch != EOF)
588                ch = getchar();
589        return (first == 'y' || first == 'Y');
590}
591
592#define ISSLASH(a)      ((a)[0] == '/' && (a)[1] == '\0')
593void
594checkslash_rm(rtems_shell_rm_globals* globals, char **argv)
595{
596        char **t, **u;
597        int complained;
598
599        complained = 0;
600        for (t = argv; *t;) {
601                if (ISSLASH(*t)) {
602                        if (!complained++)
603                                warnx("\"/\" may not be removed");
604                        eval = 1;
605                        for (u = t; u[0] != NULL; ++u)
606                                u[0] = u[1];
607                } else {
608                        ++t;
609                }
610        }
611}
612
613int
614check2_rm(rtems_shell_rm_globals* globals, char **argv)
615{
616        struct stat st;
617        int first;
618        int ch;
619        int fcount = 0;
620        int dcount = 0;
621        int i;
622        const char *dname = NULL;
623
624        for (i = 0; argv[i]; ++i) {
625                if (lstat(argv[i], &st) == 0) {
626                        if (S_ISDIR(st.st_mode)) {
627                                ++dcount;
628                                dname = argv[i];    /* only used if 1 dir */
629                        } else {
630                                ++fcount;
631                        }
632                }
633        }
634        first = 0;
635        while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
636                if (dcount && rflag) {
637                        fprintf(stderr, "recursively remove");
638                        if (dcount == 1)
639                                fprintf(stderr, " %s", dname);
640                        else
641                                fprintf(stderr, " %d dirs", dcount);
642                        if (fcount == 1)
643                                fprintf(stderr, " and 1 file");
644                        else if (fcount > 1)
645                                fprintf(stderr, " and %d files", fcount);
646                } else if (dcount + fcount > 3) {
647                        fprintf(stderr, "remove %d files", dcount + fcount);
648                } else {
649                        return(1);
650                }
651                fprintf(stderr, "? ");
652                fflush(stderr);
653
654                first = ch = getchar();
655                while (ch != '\n' && ch != EOF)
656                        ch = getchar();
657                if (ch == EOF)
658                        break;
659        }
660        return (first == 'y' || first == 'Y');
661}
662
663#define ISDOT(a)        ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
664void
665checkdot_rm(rtems_shell_rm_globals* globals, char **argv)
666{
667        char *p, **save, **t;
668        int complained;
669
670        complained = 0;
671        for (t = argv; *t;) {
672                if ((p = strrchr(*t, '/')) != NULL)
673                        ++p;
674                else
675                        p = *t;
676                if (ISDOT(p)) {
677                        if (!complained++)
678                                warnx("\".\" and \"..\" may not be removed");
679                        eval = 1;
680                        for (save = t; (t[0] = t[1]) != NULL; ++t)
681                                continue;
682                        t = save;
683                } else
684                        ++t;
685        }
686}
687
688void
689usage_rm(rtems_shell_rm_globals* globals)
690{
691
692        (void)fprintf(stderr, "%s\n%s\n",
693            "usage: rm [-f | -i] [-dIPRrvW] file ...",
694            "       unlink file");
695        exit(1);
696}
697
698rtems_shell_cmd_t rtems_shell_RM_Command = {
699  "rm",                                      /* name */
700  "[-f | -i] [-dIPRrvW] file ...",           /* usage */
701  "files",                                   /* topic */
702  rtems_shell_main_rm,                       /* command */
703  NULL,                                      /* alias */
704  NULL                                       /* next */
705};
Note: See TracBrowser for help on using the repository browser.