summaryrefslogtreecommitdiff
path: root/src/lib/libc/net/res_debug.c
diff options
context:
space:
mode:
authorderaadt <>1995-10-18 08:42:23 +0000
committerderaadt <>1995-10-18 08:42:23 +0000
commit0527d29da443886d92e9a418180c5b25a5f8d270 (patch)
tree86b3a64928451a669cefa27900e5884036b4e349 /src/lib/libc/net/res_debug.c
downloadopenbsd-0527d29da443886d92e9a418180c5b25a5f8d270.tar.gz
openbsd-0527d29da443886d92e9a418180c5b25a5f8d270.tar.bz2
openbsd-0527d29da443886d92e9a418180c5b25a5f8d270.zip
initial import of NetBSD tree
Diffstat (limited to 'src/lib/libc/net/res_debug.c')
-rw-r--r--src/lib/libc/net/res_debug.c749
1 files changed, 749 insertions, 0 deletions
diff --git a/src/lib/libc/net/res_debug.c b/src/lib/libc/net/res_debug.c
new file mode 100644
index 0000000000..d841293f18
--- /dev/null
+++ b/src/lib/libc/net/res_debug.c
@@ -0,0 +1,749 @@
1/* $NetBSD: res_debug.c,v 1.7 1995/02/25 06:20:56 cgd Exp $ */
2
3/*-
4 * Copyright (c) 1985, 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 * -
35 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
36 *
37 * Permission to use, copy, modify, and distribute this software for any
38 * purpose with or without fee is hereby granted, provided that the above
39 * copyright notice and this permission notice appear in all copies, and that
40 * the name of Digital Equipment Corporation not be used in advertising or
41 * publicity pertaining to distribution of the document or software without
42 * specific, written prior permission.
43 *
44 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
45 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
46 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
47 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
48 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
49 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
50 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
51 * SOFTWARE.
52 * -
53 * --Copyright--
54 */
55
56#if defined(LIBC_SCCS) && !defined(lint)
57#if 0
58static char sccsid[] = "@(#)res_debug.c 8.1 (Berkeley) 6/4/93";
59#else
60static char rcsid[] = "$NetBSD: res_debug.c,v 1.7 1995/02/25 06:20:56 cgd Exp $";
61#endif
62#endif /* LIBC_SCCS and not lint */
63
64#include <sys/param.h>
65#include <netinet/in.h>
66#include <arpa/inet.h>
67#include <arpa/nameser.h>
68#include <resolv.h>
69#include <stdio.h>
70#include <string.h>
71
72void __fp_query();
73char *__p_class(), *__p_time(), *__p_type();
74char *p_cdname(), *p_fqname(), *p_rr();
75static char *p_option __P((u_int32_t));
76
77char *_res_opcodes[] = {
78 "QUERY",
79 "IQUERY",
80 "CQUERYM",
81 "CQUERYU",
82 "4",
83 "5",
84 "6",
85 "7",
86 "8",
87 "UPDATEA",
88 "UPDATED",
89 "UPDATEDA",
90 "UPDATEM",
91 "UPDATEMA",
92 "ZONEINIT",
93 "ZONEREF",
94};
95
96char *_res_resultcodes[] = {
97 "NOERROR",
98 "FORMERR",
99 "SERVFAIL",
100 "NXDOMAIN",
101 "NOTIMP",
102 "REFUSED",
103 "6",
104 "7",
105 "8",
106 "9",
107 "10",
108 "11",
109 "12",
110 "13",
111 "14",
112 "NOCHANGE",
113};
114
115static char retbuf[16];
116
117static char *
118dewks(wks)
119 int wks;
120{
121 switch (wks) {
122 case 5: return("rje");
123 case 7: return("echo");
124 case 9: return("discard");
125 case 11: return("systat");
126 case 13: return("daytime");
127 case 15: return("netstat");
128 case 17: return("qotd");
129 case 19: return("chargen");
130 case 20: return("ftp-data");
131 case 21: return("ftp");
132 case 23: return("telnet");
133 case 25: return("smtp");
134 case 37: return("time");
135 case 39: return("rlp");
136 case 42: return("name");
137 case 43: return("whois");
138 case 53: return("domain");
139 case 57: return("apts");
140 case 59: return("apfs");
141 case 67: return("bootps");
142 case 68: return("bootpc");
143 case 69: return("tftp");
144 case 77: return("rje");
145 case 79: return("finger");
146 case 87: return("link");
147 case 95: return("supdup");
148 case 100: return("newacct");
149 case 101: return("hostnames");
150 case 102: return("iso-tsap");
151 case 103: return("x400");
152 case 104: return("x400-snd");
153 case 105: return("csnet-ns");
154 case 109: return("pop-2");
155 case 111: return("sunrpc");
156 case 113: return("auth");
157 case 115: return("sftp");
158 case 117: return("uucp-path");
159 case 119: return("nntp");
160 case 121: return("erpc");
161 case 123: return("ntp");
162 case 133: return("statsrv");
163 case 136: return("profile");
164 case 144: return("NeWS");
165 case 161: return("snmp");
166 case 162: return("snmp-trap");
167 case 170: return("print-srv");
168 default: (void) sprintf(retbuf, "%d", wks); return(retbuf);
169 }
170}
171
172static char *
173deproto(protonum)
174 int protonum;
175{
176 switch (protonum) {
177 case 1: return("icmp");
178 case 2: return("igmp");
179 case 3: return("ggp");
180 case 5: return("st");
181 case 6: return("tcp");
182 case 7: return("ucl");
183 case 8: return("egp");
184 case 9: return("igp");
185 case 11: return("nvp-II");
186 case 12: return("pup");
187 case 16: return("chaos");
188 case 17: return("udp");
189 default: (void) sprintf(retbuf, "%d", protonum); return(retbuf);
190 }
191}
192
193static char *
194do_rrset(msg, cp, cnt, pflag, file, hs)
195 int cnt, pflag;
196 char *cp,*msg, *hs;
197 FILE *file;
198{
199 int n;
200 int sflag;
201 /*
202 * Print answer records
203 */
204 sflag = (_res.pfcode & pflag);
205 if (n = ntohs(cnt)) {
206 if ((!_res.pfcode) || ((sflag) && (_res.pfcode & RES_PRF_HEAD1)))
207 fprintf(file, hs);
208 while (--n >= 0) {
209 cp = p_rr(cp, msg, file);
210 if ((cp-msg) > PACKETSZ)
211 return (NULL);
212 }
213 if ((!_res.pfcode) || ((sflag) && (_res.pfcode & RES_PRF_HEAD1)))
214 putc('\n', file);
215 }
216 return(cp);
217}
218
219__p_query(msg)
220 char *msg;
221{
222 __fp_query(msg, stdout);
223}
224
225/*
226 * Print the current options.
227 * This is intended to be primarily a debugging routine.
228 */
229void
230__fp_resstat(statp, file)
231 struct __res_state *statp;
232 FILE *file;
233{
234 int bit;
235
236 fprintf(file, ";; res options:");
237 if (!statp)
238 statp = &_res;
239 for (bit = 0; bit < 32; bit++) { /* XXX 32 - bad assumption! */
240 if (statp->options & (1<<bit))
241 fprintf(file, " %s", p_option(1<<bit));
242 }
243 putc('\n', file);
244}
245
246/*
247 * Print the contents of a query.
248 * This is intended to be primarily a debugging routine.
249 */
250void
251__fp_query(msg,file)
252 char *msg;
253 FILE *file;
254{
255 register char *cp;
256 register HEADER *hp;
257 register int n;
258
259 /*
260 * Print header fields.
261 */
262 hp = (HEADER *)msg;
263 cp = msg + sizeof(HEADER);
264 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEADX) || hp->rcode) {
265 fprintf(file,";; ->>HEADER<<- opcode: %s, status: %s, id: %d",
266 _res_opcodes[hp->opcode],
267 _res_resultcodes[hp->rcode],
268 ntohs(hp->id));
269 putc('\n', file);
270 }
271 putc(';', file);
272 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEAD2)) {
273 fprintf(file,"; flags:");
274 if (hp->qr)
275 fprintf(file," qr");
276 if (hp->aa)
277 fprintf(file," aa");
278 if (hp->tc)
279 fprintf(file," tc");
280 if (hp->rd)
281 fprintf(file," rd");
282 if (hp->ra)
283 fprintf(file," ra");
284 if (hp->pr)
285 fprintf(file," pr");
286 }
287 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEAD1)) {
288 fprintf(file,"; Ques: %d", ntohs(hp->qdcount));
289 fprintf(file,", Ans: %d", ntohs(hp->ancount));
290 fprintf(file,", Auth: %d", ntohs(hp->nscount));
291 fprintf(file,", Addit: %d\n", ntohs(hp->arcount));
292 }
293#if 0
294 if (_res.pfcode & (RES_PRF_HEADX | RES_PRF_HEAD2 | RES_PRF_HEAD1)) {
295 putc('\n',file);
296 }
297#endif
298 /*
299 * Print question records.
300 */
301 if (n = ntohs(hp->qdcount)) {
302 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
303 fprintf(file,";; QUESTIONS:\n");
304 while (--n >= 0) {
305 fprintf(file,";;\t");
306 cp = p_cdname(cp, msg, file);
307 if (cp == NULL)
308 return;
309 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
310 fprintf(file, ", type = %s",
311 __p_type(_getshort(cp)));
312 cp += sizeof(u_int16_t);
313 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
314 fprintf(file, ", class = %s\n\n",
315 __p_class(_getshort(cp)));
316 cp += sizeof(u_int16_t);
317 }
318 }
319 /*
320 * Print authoritative answer records
321 */
322 cp = do_rrset(msg, cp, hp->ancount, RES_PRF_ANS, file,
323 ";; ANSWERS:\n");
324 if (cp == NULL)
325 return;
326
327 /*
328 * print name server records
329 */
330 cp = do_rrset(msg, cp, hp->nscount, RES_PRF_AUTH, file,
331 ";; AUTHORITY RECORDS:\n");
332 if (!cp)
333 return;
334
335 /*
336 * print additional records
337 */
338 cp = do_rrset(msg, cp, hp->arcount, RES_PRF_ADD, file,
339 ";; ADDITIONAL RECORDS:\n");
340 if (!cp)
341 return;
342}
343
344char *
345p_cdname(cp, msg, file)
346 char *cp, *msg;
347 FILE *file;
348{
349 char name[MAXDNAME];
350 int n;
351
352 if ((n = dn_expand((u_char *)msg, (u_char *)cp + MAXCDNAME,
353 (u_char *)cp, (u_char *)name, sizeof(name))) < 0)
354 return (NULL);
355 if (name[0] == '\0')
356 putc('.', file);
357 else
358 fputs(name, file);
359 return (cp + n);
360}
361
362char *
363p_fqname(cp, msg, file)
364 char *cp, *msg;
365 FILE *file;
366{
367 char name[MAXDNAME];
368 int n, len;
369
370 if ((n = dn_expand((u_char *)msg, (u_char *)cp + MAXCDNAME,
371 (u_char *)cp, (u_char *)name, sizeof(name))) < 0)
372 return (NULL);
373 if (name[0] == '\0') {
374 putc('.', file);
375 } else {
376 fputs(name, file);
377 if (name[strlen(name) - 1] != '.')
378 putc('.', file);
379 }
380 return (cp + n);
381}
382
383/*
384 * Print resource record fields in human readable form.
385 */
386char *
387p_rr(cp, msg, file)
388 char *cp, *msg;
389 FILE *file;
390{
391 int type, class, dlen, n, c;
392 struct in_addr inaddr;
393 char *cp1, *cp2;
394 u_int32_t tmpttl, t;
395 int lcnt;
396
397 if ((cp = p_fqname(cp, msg, file)) == NULL)
398 return (NULL); /* compression error */
399 type = _getshort(cp);
400 cp += sizeof(u_int16_t);
401 class = _getshort(cp);
402 cp += sizeof(u_int16_t);
403 tmpttl = _getlong(cp);
404 cp += sizeof(u_int32_t);
405 dlen = _getshort(cp);
406 cp += sizeof(u_int16_t);
407 cp1 = cp;
408 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_TTLID))
409 fprintf(file, "\t%lu", tmpttl);
410 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_CLASS))
411 fprintf(file, "\t%s", __p_class(class));
412 fprintf(file, "\t%s", __p_type(type));
413 /*
414 * Print type specific data, if appropriate
415 */
416 switch (type) {
417 case T_A:
418 switch (class) {
419 case C_IN:
420 case C_HS:
421 bcopy(cp, (char *)&inaddr, sizeof(inaddr));
422 if (dlen == 4) {
423 fprintf(file,"\t%s", inet_ntoa(inaddr));
424 cp += dlen;
425 } else if (dlen == 7) {
426 char *address;
427 u_char protocol;
428 u_short port;
429
430 address = inet_ntoa(inaddr);
431 cp += sizeof(inaddr);
432 protocol = *(u_char*)cp;
433 cp += sizeof(u_char);
434 port = _getshort(cp);
435 cp += sizeof(u_int16_t);
436 fprintf(file, "\t%s\t; proto %d, port %d",
437 address, protocol, port);
438 }
439 break;
440 default:
441 cp += dlen;
442 }
443 break;
444 case T_CNAME:
445 case T_MB:
446 case T_MG:
447 case T_MR:
448 case T_NS:
449 case T_PTR:
450 putc('\t', file);
451 cp = p_fqname(cp, msg, file);
452 break;
453
454 case T_HINFO:
455 if (n = *cp++) {
456 fprintf(file,"\t%.*s", n, cp);
457 cp += n;
458 }
459 if (n = *cp++) {
460 fprintf(file,"\t%.*s", n, cp);
461 cp += n;
462 }
463 break;
464
465 case T_SOA:
466 putc('\t', file);
467 cp = p_fqname(cp, msg, file); /* origin */
468 putc(' ', file);
469 cp = p_fqname(cp, msg, file); /* mail addr */
470 fputs(" (\n", file);
471 t = _getlong(cp); cp += sizeof(u_int32_t);
472 fprintf(file,"\t\t\t%lu\t; serial\n", t);
473 t = _getlong(cp); cp += sizeof(u_int32_t);
474 fprintf(file,"\t\t\t%lu\t; refresh (%s)\n", t, __p_time(t));
475 t = _getlong(cp); cp += sizeof(u_int32_t);
476 fprintf(file,"\t\t\t%lu\t; retry (%s)\n", t, __p_time(t));
477 t = _getlong(cp); cp += sizeof(u_int32_t);
478 fprintf(file,"\t\t\t%lu\t; expire (%s)\n", t, __p_time(t));
479 t = _getlong(cp); cp += sizeof(u_int32_t);
480 fprintf(file,"\t\t\t%lu )\t; minimum (%s)", t, __p_time(t));
481 break;
482
483 case T_MX:
484 case T_AFSDB:
485 fprintf(file,"\t%d ", _getshort(cp));
486 cp += sizeof(u_int16_t);
487 cp = p_fqname(cp, msg, file);
488 break;
489
490 case T_TXT:
491 (void) fputs("\t\"", file);
492 cp2 = cp1 + dlen;
493 while (cp < cp2) {
494 if (n = (unsigned char) *cp++) {
495 for (c = n; c > 0 && cp < cp2; c--)
496 if (*cp == '\n') {
497 (void) putc('\\', file);
498 (void) putc(*cp++, file);
499 } else
500 (void) putc(*cp++, file);
501 }
502 }
503 putc('"', file);
504 break;
505
506 case T_MINFO:
507 case T_RP:
508 putc('\t', file);
509 cp = p_fqname(cp, msg, file);
510 putc(' ', file);
511 cp = p_fqname(cp, msg, file);
512 break;
513
514 case T_UINFO:
515 putc('\t', file);
516 fputs(cp, file);
517 cp += dlen;
518 break;
519
520 case T_UID:
521 case T_GID:
522 if (dlen == 4) {
523 fprintf(file,"\t%u", _getlong(cp));
524 cp += sizeof(int32_t);
525 }
526 break;
527
528 case T_WKS:
529 if (dlen < sizeof(u_int32_t) + 1)
530 break;
531 bcopy(cp, (char *)&inaddr, sizeof(inaddr));
532 cp += sizeof(u_int32_t);
533 fprintf(file, "\t%s %s ( ",
534 inet_ntoa(inaddr),
535 deproto((int) *cp));
536 cp += sizeof(u_char);
537 n = 0;
538 lcnt = 0;
539 while (cp < cp1 + dlen) {
540 c = *cp++;
541 do {
542 if (c & 0200) {
543 if (lcnt == 0) {
544 fputs("\n\t\t\t", file);
545 lcnt = 5;
546 }
547 fputs(dewks(n), file);
548 putc(' ', file);
549 lcnt--;
550 }
551 c <<= 1;
552 } while (++n & 07);
553 }
554 putc(')', file);
555 break;
556
557#ifdef ALLOW_T_UNSPEC
558 case T_UNSPEC:
559 {
560 int NumBytes = 8;
561 char *DataPtr;
562 int i;
563
564 if (dlen < NumBytes) NumBytes = dlen;
565 fprintf(file, "\tFirst %d bytes of hex data:",
566 NumBytes);
567 for (i = 0, DataPtr = cp; i < NumBytes; i++, DataPtr++)
568 fprintf(file, " %x", *DataPtr);
569 cp += dlen;
570 }
571 break;
572#endif /* ALLOW_T_UNSPEC */
573
574 default:
575 fprintf(file,"\t?%d?", type);
576 cp += dlen;
577 }
578#if 0
579 fprintf(file, "\t; dlen=%d, ttl %s\n", dlen, __p_time(tmpttl));
580#else
581 putc('\n', file);
582#endif
583 if (cp - cp1 != dlen) {
584 fprintf(file,";; packet size error (found %d, dlen was %d)\n",
585 cp - cp1, dlen);
586 cp = NULL;
587 }
588 return (cp);
589}
590
591static char nbuf[40];
592
593/*
594 * Return a string for the type
595 */
596char *
597__p_type(type)
598 int type;
599{
600 switch (type) {
601 case T_A:
602 return("A");
603 case T_NS: /* authoritative server */
604 return("NS");
605 case T_CNAME: /* canonical name */
606 return("CNAME");
607 case T_SOA: /* start of authority zone */
608 return("SOA");
609 case T_MB: /* mailbox domain name */
610 return("MB");
611 case T_MG: /* mail group member */
612 return("MG");
613 case T_MR: /* mail rename name */
614 return("MR");
615 case T_NULL: /* null resource record */
616 return("NULL");
617 case T_WKS: /* well known service */
618 return("WKS");
619 case T_PTR: /* domain name pointer */
620 return("PTR");
621 case T_HINFO: /* host information */
622 return("HINFO");
623 case T_MINFO: /* mailbox information */
624 return("MINFO");
625 case T_MX: /* mail routing info */
626 return("MX");
627 case T_TXT: /* text */
628 return("TXT");
629 case T_RP: /* responsible person */
630 return("RP");
631 case T_AFSDB: /* AFS cell database */
632 return("AFSDB");
633 case T_AXFR: /* zone transfer */
634 return("AXFR");
635 case T_MAILB: /* mail box */
636 return("MAILB");
637 case T_MAILA: /* mail address */
638 return("MAILA");
639 case T_ANY: /* matches any type */
640 return("ANY");
641 case T_UINFO:
642 return("UINFO");
643 case T_UID:
644 return("UID");
645 case T_GID:
646 return("GID");
647#ifdef ALLOW_T_UNSPEC
648 case T_UNSPEC:
649 return("UNSPEC");
650#endif /* ALLOW_T_UNSPEC */
651
652 default:
653 (void)sprintf(nbuf, "%d", type);
654 return(nbuf);
655 }
656}
657
658/*
659 * Return a mnemonic for class
660 */
661char *
662__p_class(class)
663 int class;
664{
665
666 switch (class) {
667 case C_IN: /* internet class */
668 return("IN");
669 case C_HS: /* hesiod class */
670 return("HS");
671 case C_ANY: /* matches any class */
672 return("ANY");
673 default:
674 (void)sprintf(nbuf, "%d", class);
675 return(nbuf);
676 }
677}
678
679/*
680 * Return a mnemonic for an option
681 */
682static char *
683p_option(option)
684 u_int32_t option;
685{
686 switch (option) {
687 case RES_INIT: return "init";
688 case RES_DEBUG: return "debug";
689 case RES_AAONLY: return "aaonly";
690 case RES_USEVC: return "usevc";
691 case RES_PRIMARY: return "primry";
692 case RES_IGNTC: return "igntc";
693 case RES_RECURSE: return "recurs";
694 case RES_DEFNAMES: return "defnam";
695 case RES_STAYOPEN: return "styopn";
696 case RES_DNSRCH: return "dnsrch";
697 default: sprintf(nbuf, "?0x%x?", option); return nbuf;
698 }
699}
700
701/*
702 * Return a mnemonic for a time to live
703 */
704char *
705__p_time(value)
706 u_int32_t value;
707{
708 int secs, mins, hours, days;
709 register char *p;
710
711 if (value == 0) {
712 strcpy(nbuf, "0 secs");
713 return(nbuf);
714 }
715
716 secs = value % 60;
717 value /= 60;
718 mins = value % 60;
719 value /= 60;
720 hours = value % 24;
721 value /= 24;
722 days = value;
723 value = 0;
724
725#define PLURALIZE(x) x, (x == 1) ? "" : "s"
726 p = nbuf;
727 if (days) {
728 (void)sprintf(p, "%d day%s", PLURALIZE(days));
729 while (*++p);
730 }
731 if (hours) {
732 if (days)
733 *p++ = ' ';
734 (void)sprintf(p, "%d hour%s", PLURALIZE(hours));
735 while (*++p);
736 }
737 if (mins) {
738 if (days || hours)
739 *p++ = ' ';
740 (void)sprintf(p, "%d min%s", PLURALIZE(mins));
741 while (*++p);
742 }
743 if (secs || ! (days || hours || mins)) {
744 if (days || hours || mins)
745 *p++ = ' ';
746 (void)sprintf(p, "%d sec%s", PLURALIZE(secs));
747 }
748 return(nbuf);
749}