]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - contrib/openpam/misc/gendoc.pl
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / contrib / openpam / misc / gendoc.pl
1 #!/usr/bin/perl -w
2 #-
3 # Copyright (c) 2002-2003 Networks Associates Technology, Inc.
4 # Copyright (c) 2004-2007 Dag-Erling Smørgrav
5 # All rights reserved.
6 #
7 # This software was developed for the FreeBSD Project by ThinkSec AS and
8 # Network Associates Laboratories, the Security Research Division of
9 # Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10 # ("CBOSS"), as part of the DARPA CHATS research program.
11 #
12 # Redistribution and use in source and binary forms, with or without
13 # modification, are permitted provided that the following conditions
14 # are met:
15 # 1. Redistributions of source code must retain the above copyright
16 #    notice, this list of conditions and the following disclaimer.
17 # 2. Redistributions in binary form must reproduce the above copyright
18 #    notice, this list of conditions and the following disclaimer in the
19 #    documentation and/or other materials provided with the distribution.
20 # 3. The name of the author may not be used to endorse or promote
21 #    products derived from this software without specific prior written
22 #    permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 # SUCH DAMAGE.
35 #
36 # $Id: gendoc.pl 408 2007-12-21 11:36:24Z des $
37 #
38
39 use strict;
40 use locale;
41 use Fcntl;
42 use Getopt::Std;
43 use POSIX qw(locale_h strftime);
44 use vars qw($COPYRIGHT $TODAY %FUNCTIONS %PAMERR);
45
46 $COPYRIGHT = ".\\\"-
47 .\\\" Copyright (c) 2001-2003 Networks Associates Technology, Inc.
48 .\\\" Copyright (c) 2004-2007 Dag-Erling Smørgrav
49 .\\\" All rights reserved.
50 .\\\"
51 .\\\" This software was developed for the FreeBSD Project by ThinkSec AS and
52 .\\\" Network Associates Laboratories, the Security Research Division of
53 .\\\" Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
54 .\\\" (\"CBOSS\"), as part of the DARPA CHATS research program.
55 .\\\"
56 .\\\" Redistribution and use in source and binary forms, with or without
57 .\\\" modification, are permitted provided that the following conditions
58 .\\\" are met:
59 .\\\" 1. Redistributions of source code must retain the above copyright
60 .\\\"    notice, this list of conditions and the following disclaimer.
61 .\\\" 2. Redistributions in binary form must reproduce the above copyright
62 .\\\"    notice, this list of conditions and the following disclaimer in the
63 .\\\"    documentation and/or other materials provided with the distribution.
64 .\\\" 3. The name of the author may not be used to endorse or promote
65 .\\\"    products derived from this software without specific prior written
66 .\\\"    permission.
67 .\\\"
68 .\\\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
69 .\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
70 .\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
71 .\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
72 .\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
73 .\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
74 .\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
75 .\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
76 .\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
77 .\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
78 .\\\" SUCH DAMAGE.
79 .\\\"
80 .\\\" \$" . "P4" . "\$
81 .\\\"";
82
83 %PAMERR = (
84     PAM_SUCCESS                 => "Success",
85     PAM_OPEN_ERR                => "Failed to load module",
86     PAM_SYMBOL_ERR              => "Invalid symbol",
87     PAM_SERVICE_ERR             => "Error in service module",
88     PAM_SYSTEM_ERR              => "System error",
89     PAM_BUF_ERR                 => "Memory buffer error",
90     PAM_CONV_ERR                => "Conversation failure",
91     PAM_PERM_DENIED             => "Permission denied",
92     PAM_MAXTRIES                => "Maximum number of tries exceeded",
93     PAM_AUTH_ERR                => "Authentication error",
94     PAM_NEW_AUTHTOK_REQD        => "New authentication token required",
95     PAM_CRED_INSUFFICIENT       => "Insufficient credentials",
96     PAM_AUTHINFO_UNAVAIL        => "Authentication information is unavailable",
97     PAM_USER_UNKNOWN            => "Unknown user",
98     PAM_CRED_UNAVAIL            => "Failed to retrieve user credentials",
99     PAM_CRED_EXPIRED            => "User credentials have expired",
100     PAM_CRED_ERR                => "Failed to set user credentials",
101     PAM_ACCT_EXPIRED            => "User account has expired",
102     PAM_AUTHTOK_EXPIRED         => "Password has expired",
103     PAM_SESSION_ERR             => "Session failure",
104     PAM_AUTHTOK_ERR             => "Authentication token failure",
105     PAM_AUTHTOK_RECOVERY_ERR    => "Failed to recover old authentication token",
106     PAM_AUTHTOK_LOCK_BUSY       => "Authentication token lock busy",
107     PAM_AUTHTOK_DISABLE_AGING   => "Authentication token aging disabled",
108     PAM_NO_MODULE_DATA          => "Module data not found",
109     PAM_IGNORE                  => "Ignore this module",
110     PAM_ABORT                   => "General failure",
111     PAM_TRY_AGAIN               => "Try again",
112     PAM_MODULE_UNKNOWN          => "Unknown module type",
113     PAM_DOMAIN_UNKNOWN          => "Unknown authentication domain",
114 );
115
116 sub parse_source($) {
117     my $fn = shift;
118
119     local *FILE;
120     my $source;
121     my $func;
122     my $descr;
123     my $type;
124     my $args;
125     my $argnames;
126     my $man;
127     my $inlist;
128     my $inliteral;
129     my %xref;
130     my @errors;
131
132     if ($fn !~ m,\.c$,) {
133         warn("$fn: not C source, ignoring\n");
134         return undef;
135     }
136
137     sysopen(FILE, $fn, O_RDONLY)
138         or die("$fn: open(): $!\n");
139     $source = join('', <FILE>);
140     close(FILE);
141
142     return undef
143         if ($source =~ m/^ \* NOPARSE\s*$/m);
144
145     $func = $fn;
146     $func =~ s,^(?:.*/)?([^/]+)\.c$,$1,;
147     if ($source !~ m,\n \* ([\S ]+)\n \*/\n\n([\S ]+)\n$func\((.*?)\)\n\{,s) {
148         warn("$fn: can't find $func\n");
149         return undef;
150     }
151     ($descr, $type, $args) = ($1, $2, $3);
152     $descr =~ s,^([A-Z][a-z]),lc($1),e;
153     $descr =~ s,[\.\s]*$,,;
154     while ($args =~ s/^((?:[^\(]|\([^\)]*\))*),\s*/$1\" \"/g) {
155         # nothing
156     }
157     $args =~ s/,\s+/, /gs;
158     $args = "\"$args\"";
159
160     %xref = (
161         3 => { 'pam' => 1 },
162     );
163
164     if ($type eq "int") {
165         foreach (split("\n", $source)) {
166             next unless (m/^ \*\s+(!?PAM_[A-Z_]+|=[a-z_]+)\s*$/);
167             push(@errors, $1);
168         }
169         ++$xref{3}->{'pam_strerror'};
170     }
171
172     $argnames = $args;
173     # extract names of regular arguments
174     $argnames =~ s/\"[^\"]+\*?\b(\w+)\"/\"$1\"/g;
175     # extract names of function pointer arguments
176     $argnames =~ s/\"([\w\s\*]+)\(\*?(\w+)\)\([^\)]+\)\"/\"$2\"/g;
177     # escape metacharacters (there shouldn't be any, but...)
178     $argnames =~ s/([\|\[\]\(\)\.\*\+\?])/\\$1/g;
179     # separate argument names with |
180     $argnames =~ s/\" \"/|/g;
181     # and surround with ()
182     $argnames =~ s/^\"(.*)\"$/($1)/;
183     # $argnames is now a regexp that matches argument names
184     $inliteral = $inlist = 0;
185     foreach (split("\n", $source)) {
186         s/\s*$//;
187         if (!defined($man)) {
188             if (m/^\/\*\*$/) {
189                 $man = "";
190             }
191             next;
192         }
193         last if (m/^ \*\/$/);
194         s/^ \* ?//;
195         s/\\(.)/$1/gs;
196         if (m/^$/) {
197             if ($man ne "" && $man !~ m/\.Pp\n$/s) {
198                 if ($inliteral) {
199                     $man .= "\0\n";
200                 } elsif ($inlist) {
201                     $man .= ".El\n.Pp\n";
202                     $inlist = 0;
203                 } else {
204                     $man .= ".Pp\n";
205                 }
206             }
207             next;
208         }
209         if (m/^>(\w+)(\s+\d)?$/) {
210             my ($page, $sect) = ($1, $2 ? int($2) : 3);
211             ++$xref{$sect}->{$page};
212             next;
213         }
214         if (s/^\s+(=?\w+):\s*/.It $1/) {
215             if ($inliteral) {
216                 $man .= ".Ed\n";
217                 $inliteral = 0;
218             }
219             if (!$inlist) {
220                 $man =~ s/\.Pp\n$//s;
221                 $man .= ".Bl -tag -width 18n\n";
222                 $inlist = 1;
223             }
224             s/^\.It =([A-Z][A-Z_]+)$/.It Dv $1/gs;
225             $man .= "$_\n";
226             next;
227         } elsif ($inlist && m/^\S/) {
228             $man .= ".El\n.Pp\n";
229             $inlist = 0;
230         } elsif ($inliteral && m/^\S/) {
231             $man .= ".Ed\n";
232             $inliteral = 0;
233         } elsif ($inliteral) {
234             $man .= "$_\n";
235             next;
236         } elsif ($inlist) {
237             s/^\s+//;
238         } elsif (m/^\s+/) {
239             $man .= ".Bd -literal\n";
240             $inliteral = 1;
241             $man .= "$_\n";
242             next;
243         }
244         s/\s*=$func\b\s*/\n.Nm\n/gs;
245         s/\s*=$argnames\b\s*/\n.Fa $1\n/gs;
246         s/\s*=(struct \w+(?: \*)?)\b\s*/\n.Vt $1\n/gs;
247         s/\s*:([a-z_]+)\b\s*/\n.Va $1\n/gs;
248         s/\s*;([a-z_]+)\b\s*/\n.Dv $1\n/gs;
249         while (s/\s*=([a-z_]+)\b\s*/\n.Xr $1 3\n/s) {
250             ++$xref{3}->{$1};
251         }
252         s/\s*\"(?=\w)/\n.Do\n/gs;
253         s/\"(?!\w)\s*/\n.Dc\n/gs;
254         s/\s*=([A-Z][A-Z_]+)\b\s*(?![\.,:;])/\n.Dv $1\n/gs;
255         s/\s*=([A-Z][A-Z_]+)\b([\.,:;]+)\s*/\n.Dv $1 $2\n/gs;
256         s/\s*{([A-Z][a-z] .*?)}\s*/\n.$1\n/gs;
257         $man .= "$_\n";
258     }
259     if (defined($man)) {
260         if ($inlist) {
261             $man .= ".El\n";
262         }
263         if ($inliteral) {
264             $man .= ".Ed\n";
265         }
266         $man =~ s/(\n\.[A-Z][a-z] [\w ]+)\n([\.,:;-]\S*)\s*/$1 $2\n/gs;
267         $man =~ s/\s*$/\n/gm;
268         $man =~ s/\n+/\n/gs;
269         $man =~ s/\0//gs;
270         $man =~ s/\n\n\./\n\./gs;
271         chomp($man);
272     } else {
273         $man = "No description available.";
274     }
275
276     $FUNCTIONS{$func} = {
277         'source'        => $fn,
278         'name'          => $func,
279         'descr'         => $descr,
280         'type'          => $type,
281         'args'          => $args,
282         'man'           => $man,
283         'xref'          => \%xref,
284         'errors'        => \@errors,
285     };
286     if ($source =~ m/^ \* NODOC\s*$/m) {
287         $FUNCTIONS{$func}->{'nodoc'} = 1;
288     }
289     if ($source !~ m/^ \* XSSO \d/m) {
290         $FUNCTIONS{$func}->{'openpam'} = 1;
291     }
292     expand_errors($FUNCTIONS{$func});
293     return $FUNCTIONS{$func};
294 }
295
296 sub expand_errors($);
297 sub expand_errors($) {
298     my $func = shift;           # Ref to function hash
299
300     my %errors;
301     my $ref;
302     my $fn;
303
304     if (defined($func->{'recursed'})) {
305         warn("$func->{'name'}(): loop in error spec\n");
306         return qw();
307     }
308     $func->{'recursed'} = 1;
309
310     foreach (@{$func->{'errors'}}) {
311         if (m/^(PAM_[A-Z_]+)$/) {
312             if (!defined($PAMERR{$1})) {
313                 warn("$func->{'name'}(): unrecognized error: $1\n");
314                 next;
315             }
316             $errors{$1} = 1;
317         } elsif (m/^!(PAM_[A-Z_]+)$/) {
318             # treat negations separately
319         } elsif (m/^=([a-z_]+)$/) {
320             $ref = $1;
321             if (!defined($FUNCTIONS{$ref})) {
322                 $fn = $func->{'source'};
323                 $fn =~ s/$func->{'name'}/$ref/;
324                 parse_source($fn);
325             }
326             if (!defined($FUNCTIONS{$ref})) {
327                 warn("$func->{'name'}(): reference to unknown $ref()\n");
328                 next;
329             }
330             foreach (@{$FUNCTIONS{$ref}->{'errors'}}) {
331                 $errors{$_} = 1;
332             }
333         } else {
334             warn("$func->{'name'}(): invalid error specification: $_\n");
335         }
336     }
337     foreach (@{$func->{'errors'}}) {
338         if (m/^!(PAM_[A-Z_]+)$/) {
339             delete($errors{$1});
340         }
341     }
342     delete($func->{'recursed'});
343     $func->{'errors'} = [ sort(keys(%errors)) ];
344 }
345
346 sub dictionary_order($$) {
347     my ($a, $b) = @_;
348
349     $a =~ s/[^[:alpha:]]//g;
350     $b =~ s/[^[:alpha:]]//g;
351     $a cmp $b;
352 }
353
354 sub genxref($) {
355     my $xref = shift;           # References
356
357     my $mdoc = '';
358     my @refs = ();
359     foreach my $sect (sort(keys(%{$xref}))) {
360         foreach my $page (sort(dictionary_order keys(%{$xref->{$sect}}))) {
361             push(@refs, "$page $sect");
362         }
363     }
364     while ($_ = shift(@refs)) {
365         $mdoc .= ".Xr $_" .
366             (@refs ? " ,\n" : "\n");
367     }
368     return $mdoc;
369 }
370
371 sub gendoc($) {
372     my $func = shift;           # Ref to function hash
373
374     local *FILE;
375     my $mdoc;
376     my $fn;
377
378     return if defined($func->{'nodoc'});
379
380     $mdoc = "$COPYRIGHT
381 .Dd $TODAY
382 .Dt " . uc($func->{'name'}) . " 3
383 .Os
384 .Sh NAME
385 .Nm $func->{'name'}
386 .Nd $func->{'descr'}
387 .Sh LIBRARY
388 .Lb libpam
389 .Sh SYNOPSIS
390 .In sys/types.h
391 .In security/pam_appl.h
392 ";
393     if ($func->{'name'} =~ m/_sm_/) {
394         $mdoc .= ".In security/pam_modules.h\n"
395     }
396     if ($func->{'name'} =~ m/openpam/) {
397         $mdoc .= ".In security/openpam.h\n"
398     }
399     $mdoc .= ".Ft \"$func->{'type'}\"
400 .Fn $func->{'name'} $func->{'args'}
401 .Sh DESCRIPTION
402 $func->{'man'}
403 ";
404     if ($func->{'type'} eq "int") {
405         $mdoc .= ".Sh RETURN VALUES
406 The
407 .Nm
408 function returns one of the following values:
409 .Bl -tag -width 18n
410 ";
411         my @errors = @{$func->{'errors'}};
412         warn("$func->{'name'}(): no error specification\n")
413             unless(@errors);
414         foreach (@errors) {
415             $mdoc .= ".It Bq Er $_\n$PAMERR{$_}.\n";
416         }
417         $mdoc .= ".El\n";
418     } else {
419         if ($func->{'type'} =~ m/\*$/) {
420             $mdoc .= ".Sh RETURN VALUES
421 The
422 .Nm
423 function returns
424 .Dv NULL
425 on failure.
426 ";
427         }
428     }
429     $mdoc .= ".Sh SEE ALSO\n" . genxref($func->{'xref'});
430     $mdoc .= ".Sh STANDARDS\n";
431     if ($func->{'openpam'}) {
432         $mdoc .= "The
433 .Nm
434 function is an OpenPAM extension.
435 ";
436     } else {
437         $mdoc .= ".Rs
438 .%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
439 .%D \"June 1997\"
440 .Re
441 ";
442     }
443     $mdoc .= ".Sh AUTHORS
444 The
445 .Nm
446 function and this manual page were developed for the
447 .Fx
448 Project by ThinkSec AS and Network Associates Laboratories, the
449 Security Research Division of Network Associates, Inc.\\& under
450 DARPA/SPAWAR contract N66001-01-C-8035
451 .Pq Dq CBOSS ,
452 as part of the DARPA CHATS research program.
453 ";
454
455     $fn = "$func->{'name'}.3";
456     if (sysopen(FILE, $fn, O_RDWR|O_CREAT|O_TRUNC)) {
457         print(FILE $mdoc);
458         close(FILE);
459     } else {
460         warn("$fn: open(): $!\n");
461     }
462 }
463
464 sub readproto($) {
465     my $fn = shift;             # File name
466
467     local *FILE;
468     my %func;
469
470     sysopen(FILE, $fn, O_RDONLY)
471         or die("$fn: open(): $!\n");
472     while (<FILE>) {
473         if (m/^\.Nm ((?:open)?pam_.*?)\s*$/) {
474             $func{'Nm'} = $func{'Nm'} || $1;
475         } elsif (m/^\.Ft (\S.*?)\s*$/) {
476             $func{'Ft'} = $func{'Ft'} || $1;
477         } elsif (m/^\.Fn (\S.*?)\s*$/) {
478             $func{'Fn'} = $func{'Fn'} || $1;
479         }
480     }
481     close(FILE);
482     if ($func{'Nm'}) {
483         $FUNCTIONS{$func{'Nm'}} = \%func;
484     } else {
485         warn("No function found\n");
486     }
487 }
488
489 sub gensummary($) {
490     my $page = shift;           # Which page to produce
491
492     local *FILE;
493     my $upage;
494     my $func;
495     my %xref;
496
497     sysopen(FILE, "$page.3", O_RDWR|O_CREAT|O_TRUNC)
498         or die("$page.3: $!\n");
499
500     $upage = uc($page);
501     print FILE "$COPYRIGHT
502 .Dd $TODAY
503 .Dt $upage 3
504 .Os
505 .Sh NAME
506 ";
507     my @funcs = sort(keys(%FUNCTIONS));
508     while ($func = shift(@funcs)) {
509         print FILE ".Nm $FUNCTIONS{$func}->{'Nm'}";
510         print FILE " ,"
511                 if (@funcs);
512         print FILE "\n";
513     }
514     print FILE ".Nd Pluggable Authentication Modules Library
515 .Sh LIBRARY
516 .Lb libpam
517 .Sh SYNOPSIS\n";
518     if ($page eq 'pam') {
519         print FILE ".In security/pam_appl.h\n";
520     } else {
521         print FILE ".In security/openpam.h\n";
522     }
523     foreach $func (sort(keys(%FUNCTIONS))) {
524         print FILE ".Ft $FUNCTIONS{$func}->{'Ft'}\n";
525         print FILE ".Fn $FUNCTIONS{$func}->{'Fn'}\n";
526     }
527     while (<STDIN>) {
528         if (m/^\.Xr (\S+)\s*(\d)\s*$/) {
529             ++$xref{int($2)}->{$1};
530         }
531         print FILE $_;
532     }
533
534     if ($page eq 'pam') {
535         print FILE ".Sh RETURN VALUES
536 The following return codes are defined by
537 .In security/pam_constants.h :
538 .Bl -tag -width 18n
539 ";
540         foreach (sort(keys(%PAMERR))) {
541             print FILE ".It Bq Er $_\n$PAMERR{$_}.\n";
542         }
543         print FILE ".El\n";
544     }
545     print FILE ".Sh SEE ALSO
546 ";
547     if ($page eq 'pam') {
548         ++$xref{3}->{'openpam'};
549     }
550     foreach $func (keys(%FUNCTIONS)) {
551         ++$xref{3}->{$func};
552     }
553     print FILE genxref(\%xref);
554     print FILE ".Sh STANDARDS
555 .Rs
556 .%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
557 .%D \"June 1997\"
558 .Re
559 .Sh AUTHORS
560 The OpenPAM library and this manual page were developed for the
561 .Fx
562 Project by ThinkSec AS and Network Associates Laboratories, the
563 Security Research Division of Network Associates, Inc.\\& under
564 DARPA/SPAWAR contract N66001-01-C-8035
565 .Pq Dq CBOSS ,
566 as part of the DARPA CHATS research program.
567 ";
568     close(FILE);
569 }
570
571 sub usage() {
572
573     print(STDERR "usage: gendoc [-s] source [...]\n");
574     exit(1);
575 }
576
577 MAIN:{
578     my %opts;
579
580     usage()
581         unless (@ARGV && getopts("op", \%opts));
582     setlocale(LC_ALL, "en_US.ISO8859-1");
583     $TODAY = strftime("%B %e, %Y", localtime(time()));
584     $TODAY =~ s,\s+, ,g;
585     if ($opts{'o'} || $opts{'p'}) {
586         foreach my $fn (@ARGV) {
587             readproto($fn);
588         }
589         gensummary('openpam')
590             if ($opts{'o'});
591         gensummary('pam')
592             if ($opts{'p'});
593     } else {
594         foreach my $fn (@ARGV) {
595             my $func = parse_source($fn);
596             gendoc($func)
597                 if (defined($func));
598         }
599     }
600     exit(0);
601 }