gdp / apps / gdp-writer.c @ master
History | View | Annotate | Download (9.78 KB)
1 | 4e425382 | Eric Allman | /* vim: set ai sw=4 sts=4 ts=4 : */
|
---|---|---|---|
2 | 47c6ea64 | Eric Allman | |
3 | 055d3009 | Eric Allman | /*
|
4 | eae1d3ec | Eric Allman | ** GDP-WRITER --- writes records to a log
|
5 | 055d3009 | Eric Allman | **
|
6 | ** This reads the records one line at a time from standard input
|
||
7 | ** and assumes they are text, but there is no text requirement
|
||
8 | ** implied by the GDP.
|
||
9 | **
|
||
10 | ** ----- BEGIN LICENSE BLOCK -----
|
||
11 | ** Applications for the Global Data Plane
|
||
12 | ** From the Ubiquitous Swarm Lab, 490 Cory Hall, U.C. Berkeley.
|
||
13 | **
|
||
14 | c87dd166 | Eric Allman | ** Copyright (c) 2015-2019, Regents of the University of California.
|
15 | 6bd5476b | Eric Allman | ** All rights reserved.
|
16 | 055d3009 | Eric Allman | **
|
17 | 6bd5476b | Eric Allman | ** Permission is hereby granted, without written agreement and without
|
18 | ** license or royalty fees, to use, copy, modify, and distribute this
|
||
19 | ** software and its documentation for any purpose, provided that the above
|
||
20 | ** copyright notice and the following two paragraphs appear in all copies
|
||
21 | ** of this software.
|
||
22 | 055d3009 | Eric Allman | **
|
23 | 6bd5476b | Eric Allman | ** IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
|
24 | ** SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST
|
||
25 | ** PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
|
||
26 | ** EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
27 | 055d3009 | Eric Allman | **
|
28 | 6bd5476b | Eric Allman | ** REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
|
29 | 055d3009 | Eric Allman | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
30 | 6bd5476b | Eric Allman | ** FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION,
|
31 | ** IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO
|
||
32 | ** OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
|
||
33 | ** OR MODIFICATIONS.
|
||
34 | 055d3009 | Eric Allman | ** ----- END LICENSE BLOCK -----
|
35 | */
|
||
36 | |||
37 | cd4cc511 | Eric Allman | #include <ep/ep.h> |
38 | f0313e1d | Eric Allman | #include <ep/ep_app.h> |
39 | 47c6ea64 | Eric Allman | #include <ep/ep_dbg.h> |
40 | 0cb4611f | Eric Allman | #include <ep/ep_hexdump.h> |
41 | b15403d1 | Eric Allman | #include <ep/ep_string.h> |
42 | cd4cc511 | Eric Allman | #include <gdp/gdp.h> |
43 | e2d81e0f | Eric Allman | |
44 | 47c6ea64 | Eric Allman | #include <unistd.h> |
45 | fd6e357c | Eric Allman | #include <errno.h> |
46 | e2d81e0f | Eric Allman | #include <fcntl.h> |
47 | 4e425382 | Eric Allman | #include <getopt.h> |
48 | 47c6ea64 | Eric Allman | #include <string.h> |
49 | ecbc8f12 | Eric Allman | #include <sysexits.h> |
50 | e2d81e0f | Eric Allman | #include <sys/stat.h> |
51 | 47c6ea64 | Eric Allman | |
52 | 0cb4611f | Eric Allman | bool AsyncIo = false; // use asynchronous I/O |
53 | bool Quiet = false; // be silent (no chatty messages) |
||
54 | bool Hexdump = false; // echo input in hex instead of ASCII |
||
55 | 1f3adfd0 | Eric Allman | bool KeepGoing = false; // keep going on append errors |
56 | 174e9f6d | Eric Allman | |
57 | a6e527fb | Eric Allman | static EP_DBG Dbg = EP_DBG_INIT("gdp-writer", "gdp-writer"); |
58 | |||
59 | fd6e357c | Eric Allman | /*
|
60 | ** DO_LOG --- log a timestamp (for performance checking).
|
||
61 | */
|
||
62 | |||
63 | FILE *LogFile; |
||
64 | |||
65 | void
|
||
66 | do_log(const char *tag) |
||
67 | { |
||
68 | struct timeval tv;
|
||
69 | |||
70 | if (LogFile == NULL) |
||
71 | return;
|
||
72 | gettimeofday(&tv, NULL);
|
||
73 | fprintf(LogFile, "%s %ld.%06ld\n", tag, tv.tv_sec, (long) tv.tv_usec); |
||
74 | } |
||
75 | |||
76 | #define LOG(tag) { if (LogFile != NULL) do_log(tag); } |
||
77 | |||
78 | |||
79 | 58292089 | Eric Allman | static const char *EventTypes[] = |
80 | e2682ba9 | Eric Allman | { |
81 | "Free (internal use)",
|
||
82 | "Data",
|
||
83 | "End of Subscription",
|
||
84 | "Shutdown",
|
||
85 | "Asynchronous Status",
|
||
86 | }; |
||
87 | |||
88 | void
|
||
89 | showstat(gdp_event_t *gev) |
||
90 | { |
||
91 | 328ab6a8 | Eric Allman | unsigned int evtype = gdp_event_gettype(gev); |
92 | e2682ba9 | Eric Allman | EP_STAT estat = gdp_event_getstat(gev); |
93 | gdp_datum_t *d = gdp_event_getdatum(gev); |
||
94 | char ebuf[100]; |
||
95 | char tbuf[20]; |
||
96 | 58292089 | Eric Allman | const char *evname; |
97 | e2682ba9 | Eric Allman | |
98 | 328ab6a8 | Eric Allman | if (evtype >= sizeof EventTypes / sizeof EventTypes[0]) |
99 | e2682ba9 | Eric Allman | { |
100 | 328ab6a8 | Eric Allman | snprintf(tbuf, sizeof tbuf, "%u", evtype); |
101 | e2682ba9 | Eric Allman | evname = tbuf; |
102 | } |
||
103 | else
|
||
104 | { |
||
105 | evname = EventTypes[evtype]; |
||
106 | } |
||
107 | |||
108 | printf("Asynchronous event type %s:\n"
|
||
109 | "\trecno %" PRIgdp_recno ", stat %s\n", |
||
110 | evname, |
||
111 | gdp_datum_getrecno(d), |
||
112 | ep_stat_tostr(estat, ebuf, sizeof ebuf));
|
||
113 | |||
114 | gdp_event_free(gev); |
||
115 | } |
||
116 | |||
117 | |||
118 | 0cb4611f | Eric Allman | EP_STAT |
119 | eae1d3ec | Eric Allman | write_record(gdp_datum_t *datum, gdp_gin_t *gin) |
120 | 0cb4611f | Eric Allman | { |
121 | EP_STAT estat; |
||
122 | |||
123 | // echo the input for that warm fuzzy feeling
|
||
124 | if (!Quiet)
|
||
125 | { |
||
126 | gdp_buf_t *dbuf = gdp_datum_getbuf(datum); |
||
127 | int l = gdp_buf_getlength(dbuf);
|
||
128 | unsigned char *buf = gdp_buf_getptr(dbuf, l); |
||
129 | |||
130 | if (!Hexdump)
|
||
131 | fprintf(stdout, "Got input %s%.*s%s\n",
|
||
132 | EpChar->lquote, l, buf, EpChar->rquote); |
||
133 | else
|
||
134 | ep_hexdump(buf, l, stdout, EP_HEXDUMP_ASCII, 0);
|
||
135 | } |
||
136 | |||
137 | a6e527fb | Eric Allman | if (ep_dbg_test(Dbg, 60)) |
138 | gdp_datum_print(datum, ep_dbg_getfile(), GDP_DATUM_PRDEBUG); |
||
139 | |||
140 | 0cb4611f | Eric Allman | // then send the buffer to the GDP
|
141 | LOG("W");
|
||
142 | if (AsyncIo)
|
||
143 | { |
||
144 | eae1d3ec | Eric Allman | estat = gdp_gin_append_async(gin, 1, &datum, NULL, showstat, NULL); |
145 | 0cb4611f | Eric Allman | EP_STAT_CHECK(estat, return estat);
|
146 | |||
147 | // return value will be printed asynchronously
|
||
148 | } |
||
149 | else
|
||
150 | { |
||
151 | eae1d3ec | Eric Allman | estat = gdp_gin_append(gin, datum, NULL);
|
152 | 0cb4611f | Eric Allman | |
153 | 1f3adfd0 | Eric Allman | if (EP_STAT_ISOK(estat))
|
154 | { |
||
155 | // print the return value (shows the record number assigned)
|
||
156 | if (!Quiet)
|
||
157 | 587a05de | Eric Allman | gdp_datum_print(datum, stdout, GDP_DATUM_PRMETAONLY); |
158 | 1f3adfd0 | Eric Allman | } |
159 | else if (!Quiet) |
||
160 | { |
||
161 | char ebuf[100]; |
||
162 | ep_app_error("Append error: %s",
|
||
163 | ep_stat_tostr(estat, ebuf, sizeof ebuf));
|
||
164 | } |
||
165 | 0cb4611f | Eric Allman | } |
166 | return estat;
|
||
167 | } |
||
168 | |||
169 | |||
170 | 284a8bac | Eric Allman | EP_STAT |
171 | signkey_cb( |
||
172 | gdp_name_t gname, |
||
173 | void *udata,
|
||
174 | EP_CRYPTO_KEY **skeyp) |
||
175 | { |
||
176 | FILE *fp; |
||
177 | EP_CRYPTO_KEY *skey; |
||
178 | fc0cbc23 | Eric Allman | const char *signing_key_file = (const char *) udata; |
179 | 284a8bac | Eric Allman | |
180 | ep_dbg_cprintf(Dbg, 1, "signkey_cb(%s)\n", signing_key_file); |
||
181 | |||
182 | fp = fopen(signing_key_file, "r");
|
||
183 | if (fp == NULL) |
||
184 | { |
||
185 | ep_app_error("cannot open signing key file %s", signing_key_file);
|
||
186 | return ep_stat_from_errno(errno);
|
||
187 | } |
||
188 | |||
189 | skey = ep_crypto_key_read_fp(fp, signing_key_file, |
||
190 | EP_CRYPTO_KEYFORM_PEM, EP_CRYPTO_F_SECRET); |
||
191 | if (skey == NULL) |
||
192 | { |
||
193 | ep_app_error("cannot read signing key file %s", signing_key_file);
|
||
194 | return ep_stat_from_errno(errno);
|
||
195 | } |
||
196 | |||
197 | *skeyp = skey; |
||
198 | return EP_STAT_OK;
|
||
199 | } |
||
200 | |||
201 | |||
202 | 3ddd2ed9 | Eric Allman | void
|
203 | usage(void)
|
||
204 | { |
||
205 | cbe2980a | Eric Allman | fprintf(stderr, |
206 | 0cb4611f | Eric Allman | "Usage: %s [-1] [-a] [-D dbgspec] [-G router_addr] [-K key_file]\n"
|
207 | 49d76189 | Eric Allman | "\t[-L log_file] [-q] [-S] log_name\n"
|
208 | 0cb4611f | Eric Allman | " -1 write all input as one record\n"
|
209 | e2682ba9 | Eric Allman | " -a use asynchronous I/O\n"
|
210 | cbe2980a | Eric Allman | " -D set debugging flags\n"
|
211 | " -G IP host to contact for gdp_router\n"
|
||
212 | 1f3adfd0 | Eric Allman | " -i ignore append errors\n"
|
213 | 37960b88 | Eric Allman | " -K signing key file\n"
|
214 | 0cb4611f | Eric Allman | " -L set logging file name (for debugging)\n"
|
215 | 49d76189 | Eric Allman | " -q run quietly (no non-error output)\n"
|
216 | " -S continue even if signing key cannot be found\n",
|
||
217 | 3ddd2ed9 | Eric Allman | ep_app_getprogname()); |
218 | exit(EX_USAGE); |
||
219 | } |
||
220 | |||
221 | |||
222 | 47c6ea64 | Eric Allman | int
|
223 | main(int argc, char **argv) |
||
224 | { |
||
225 | eae1d3ec | Eric Allman | gdp_gin_t *gin; |
226 | gdp_name_t gdpiname; |
||
227 | 4e425382 | Eric Allman | int opt;
|
228 | EP_STAT estat; |
||
229 | 5cd53c1a | Eric Allman | char *gdpd_addr = NULL; |
230 | 591ae6e9 | Eric Allman | bool show_usage = false; |
231 | 0cb4611f | Eric Allman | bool one_record = false; |
232 | 49d76189 | Eric Allman | bool allow_no_signing_key = false; |
233 | fd6e357c | Eric Allman | char *log_file_name = NULL; |
234 | 37960b88 | Eric Allman | char *signing_key_file = NULL; |
235 | eae1d3ec | Eric Allman | gdp_open_info_t *info; |
236 | 47c6ea64 | Eric Allman | |
237 | 97696c1c | Eric Allman | // collect command-line arguments
|
238 | 49d76189 | Eric Allman | while ((opt = getopt(argc, argv, "1aD:G:iK:L:qS")) > 0) |
239 | 47c6ea64 | Eric Allman | { |
240 | 4e425382 | Eric Allman | switch (opt)
|
241 | { |
||
242 | 0cb4611f | Eric Allman | case '1': |
243 | one_record = true;
|
||
244 | Hexdump = true;
|
||
245 | break;
|
||
246 | |||
247 | e2682ba9 | Eric Allman | case 'a': |
248 | 0cb4611f | Eric Allman | AsyncIo = true;
|
249 | break;
|
||
250 | e2682ba9 | Eric Allman | |
251 | 4e425382 | Eric Allman | case 'D': |
252 | ep_dbg_set(optarg); |
||
253 | break;
|
||
254 | 5cd53c1a | Eric Allman | |
255 | case 'G': |
||
256 | gdpd_addr = optarg; |
||
257 | break;
|
||
258 | 591ae6e9 | Eric Allman | |
259 | 1f3adfd0 | Eric Allman | case 'i': |
260 | KeepGoing = true;
|
||
261 | break;
|
||
262 | |||
263 | 37960b88 | Eric Allman | case 'K': |
264 | signing_key_file = optarg; |
||
265 | break;
|
||
266 | |||
267 | fd6e357c | Eric Allman | case 'L': |
268 | log_file_name = optarg; |
||
269 | break;
|
||
270 | |||
271 | 0cb4611f | Eric Allman | case 'q': |
272 | Quiet = true;
|
||
273 | break;
|
||
274 | |||
275 | 49d76189 | Eric Allman | case 'S': |
276 | allow_no_signing_key = true;
|
||
277 | break;
|
||
278 | |||
279 | 591ae6e9 | Eric Allman | default:
|
280 | show_usage = true;
|
||
281 | break;
|
||
282 | 4e425382 | Eric Allman | } |
283 | 47c6ea64 | Eric Allman | } |
284 | ecbc8f12 | Eric Allman | argc -= optind; |
285 | argv += optind; |
||
286 | |||
287 | c6293003 | Eric Allman | if (show_usage || argc != 1) |
288 | 3ddd2ed9 | Eric Allman | usage(); |
289 | |||
290 | fd6e357c | Eric Allman | if (log_file_name != NULL) |
291 | { |
||
292 | // open a log file (for timing measurements)
|
||
293 | LogFile = fopen(log_file_name, "a");
|
||
294 | if (LogFile == NULL) |
||
295 | 9829d4ae | Eric Allman | ep_app_error("Cannot open log file %s: %s",
|
296 | fd6e357c | Eric Allman | log_file_name, strerror(errno)); |
297 | else
|
||
298 | setlinebuf(LogFile); |
||
299 | } |
||
300 | |||
301 | 97696c1c | Eric Allman | // initialize the GDP library
|
302 | 5cd53c1a | Eric Allman | estat = gdp_init(gdpd_addr); |
303 | 4e425382 | Eric Allman | if (!EP_STAT_ISOK(estat))
|
304 | { |
||
305 | ep_app_error("GDP Initialization failed");
|
||
306 | goto fail0;
|
||
307 | } |
||
308 | 0c663d10 | Eric Allman | |
309 | 2e4a5d7d | Eric Allman | // allow thread to settle to avoid interspersed debug output
|
310 | c6293003 | Eric Allman | ep_time_nanosleep(INT64_C(100000000));
|
311 | 2e4a5d7d | Eric Allman | |
312 | 37960b88 | Eric Allman | // set up any open information
|
313 | eae1d3ec | Eric Allman | info = gdp_open_info_new(); |
314 | 37960b88 | Eric Allman | |
315 | if (signing_key_file != NULL) |
||
316 | { |
||
317 | eae1d3ec | Eric Allman | gdp_open_info_set_signkey_cb(info, signkey_cb, signing_key_file); |
318 | 284a8bac | Eric Allman | |
319 | eae1d3ec | Eric Allman | #if 0 // old code: keep as an example of gdp_open_info_set_signing_key
|
320 | 37960b88 | Eric Allman | FILE *fp;
|
321 | EP_CRYPTO_KEY *skey;
|
||
322 | |||
323 | fp = fopen(signing_key_file, "r");
|
||
324 | if (fp == NULL)
|
||
325 | {
|
||
326 | ep_app_error("cannot open signing key file %s", signing_key_file);
|
||
327 | goto fail1;
|
||
328 | }
|
||
329 | |||
330 | skey = ep_crypto_key_read_fp(fp, signing_key_file,
|
||
331 | 058dfca9 | Eric Allman | EP_CRYPTO_KEYFORM_PEM, EP_CRYPTO_F_SECRET);
|
332 | 37960b88 | Eric Allman | if (skey == NULL)
|
333 | {
|
||
334 | ep_app_error("cannot read signing key file %s", signing_key_file);
|
||
335 | goto fail1;
|
||
336 | }
|
||
337 | |||
338 | eae1d3ec | Eric Allman | estat = gdp_open_info_set_signing_key(info, skey);
|
339 | 37960b88 | Eric Allman | EP_STAT_CHECK(estat, goto fail1);
|
340 | 284a8bac | Eric Allman | #endif
|
341 | 37960b88 | Eric Allman | } |
342 | |||
343 | 49d76189 | Eric Allman | if (allow_no_signing_key)
|
344 | estat = gdp_open_info_set_no_skey_nonfatal(info, true);
|
||
345 | |||
346 | eae1d3ec | Eric Allman | // open a GDP object with the provided name
|
347 | 22f2276a | Eric Allman | estat = gdp_parse_name(argv[0], gdpiname);
|
348 | if (EP_STAT_ISFAIL(estat))
|
||
349 | goto fail1;
|
||
350 | else
|
||
351 | { |
||
352 | estat = gdp_gin_open(gdpiname, GDP_MODE_AO, info, &gin); |
||
353 | 49d76189 | Eric Allman | if (EP_STAT_ISFAIL(estat))
|
354 | goto fail1;
|
||
355 | 22f2276a | Eric Allman | } |
356 | ecbc8f12 | Eric Allman | |
357 | 0cb4611f | Eric Allman | if (!Quiet)
|
358 | 943e4d9a | Eric Allman | { |
359 | gdp_pname_t pname; |
||
360 | |||
361 | eae1d3ec | Eric Allman | // dump the internal version of the GDP object to facilitate testing
|
362 | 943e4d9a | Eric Allman | printf("GDPname: %s (%" PRIu64 " recs)\n", |
363 | eae1d3ec | Eric Allman | gdp_printable_name(*gdp_gin_getname(gin), pname), |
364 | gdp_gin_getnrecs(gin)); |
||
365 | 97696c1c | Eric Allman | |
366 | 0cb4611f | Eric Allman | // OK, ready to go!
|
367 | fprintf(stdout, "\nStarting to read input\n");
|
||
368 | } |
||
369 | 47c6ea64 | Eric Allman | |
370 | 0cb4611f | Eric Allman | { |
371 | d2422e31 | Eric Allman | // we need a place to buffer the input
|
372 | 2e65953f | Eric Allman | gdp_datum_t *datum = gdp_datum_new(); |
373 | 97696c1c | Eric Allman | |
374 | d2422e31 | Eric Allman | if (one_record)
|
375 | e2682ba9 | Eric Allman | { |
376 | d2422e31 | Eric Allman | // read the entire stdin into a single datum
|
377 | char buf[8 * 1024]; |
||
378 | int l;
|
||
379 | e2682ba9 | Eric Allman | |
380 | d2422e31 | Eric Allman | while ((l = fread(buf, 1, sizeof buf, stdin)) > 0) |
381 | gdp_buf_write(gdp_datum_getbuf(datum), buf, l); |
||
382 | 97696c1c | Eric Allman | |
383 | eae1d3ec | Eric Allman | estat = write_record(datum, gin); |
384 | e2682ba9 | Eric Allman | } |
385 | d2422e31 | Eric Allman | else
|
386 | { |
||
387 | // write lines into multiple datums
|
||
388 | char buf[200]; |
||
389 | |||
390 | while (fgets(buf, sizeof buf, stdin) != NULL) |
||
391 | { |
||
392 | // strip off newlines
|
||
393 | char *p = strchr(buf, '\n'); |
||
394 | if (p != NULL) |
||
395 | *p++ = '\0';
|
||
396 | |||
397 | // first copy the text buffer into the datum buffer
|
||
398 | 587a05de | Eric Allman | gdp_datum_reset(datum); |
399 | d2422e31 | Eric Allman | gdp_buf_write(gdp_datum_getbuf(datum), buf, strlen(buf)); |
400 | |||
401 | // write the record to the log
|
||
402 | eae1d3ec | Eric Allman | estat = write_record(datum, gin); |
403 | d2422e31 | Eric Allman | if (!EP_STAT_ISOK(estat) && !KeepGoing)
|
404 | break;
|
||
405 | } |
||
406 | } |
||
407 | 97696c1c | Eric Allman | |
408 | d2422e31 | Eric Allman | // OK, all done. Free our resources and exit
|
409 | gdp_datum_free(datum); |
||
410 | } |
||
411 | 47c6ea64 | Eric Allman | |
412 | e2682ba9 | Eric Allman | // give a chance to collect async results
|
413 | 0cb4611f | Eric Allman | if (AsyncIo)
|
414 | e2682ba9 | Eric Allman | sleep(1);
|
415 | |||
416 | 97696c1c | Eric Allman | // tell the GDP that we are done
|
417 | eae1d3ec | Eric Allman | gdp_gin_close(gin); |
418 | f0313e1d | Eric Allman | |
419 | 5bd0848f | Eric Allman | fail1:
|
420 | 37960b88 | Eric Allman | if (info != NULL) |
421 | eae1d3ec | Eric Allman | gdp_open_info_free(info); |
422 | 5bd0848f | Eric Allman | |
423 | 174e9f6d | Eric Allman | fail0:
|
424 | 2dba893f | Eric Allman | ; // avoid compiler error
|
425 | int exitstat;
|
||
426 | |||
427 | if (EP_STAT_ISOK(estat))
|
||
428 | exitstat = EX_OK; |
||
429 | a4ca7afc | Eric Allman | else if (EP_STAT_IS_SAME(estat, GDP_STAT_NAK_NOROUTE)) |
430 | 2dba893f | Eric Allman | exitstat = EX_CANTCREAT; |
431 | a4ca7afc | Eric Allman | else if (EP_STAT_ISABORT(estat)) |
432 | 2dba893f | Eric Allman | exitstat = EX_SOFTWARE; |
433 | else
|
||
434 | exitstat = EX_UNAVAILABLE; |
||
435 | |||
436 | 97696c1c | Eric Allman | // OK status can have values; hide that from the user
|
437 | 9509f13b | Eric Allman | if (EP_STAT_ISOK(estat))
|
438 | estat = EP_STAT_OK; |
||
439 | 2dba893f | Eric Allman | if (!EP_STAT_ISOK(estat))
|
440 | 9829d4ae | Eric Allman | ep_app_message(estat, "exiting with status");
|
441 | 2dba893f | Eric Allman | else if (!Quiet) |
442 | fprintf(stderr, "Exiting with status OK\n");
|
||
443 | return exitstat;
|
||
444 | 47c6ea64 | Eric Allman | } |