Formerly ../hurd/hurdpid.c.~9~
[kopensolaris-gnu/glibc.git] / resolv / res_debug.c
1 /*
2  * ++Copyright++ 1985, 1990
3  * -
4  * Copyright (c) 1985, 1990 Regents of the University of California.
5  * 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 static char sccsid[] = "@(#)res_debug.c 5.36 (Berkeley) 3/6/91";
58 static char rcsid[] = "$Id$";
59 #endif /* LIBC_SCCS and not lint */
60
61 #include <sys/param.h>
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
64 #include <arpa/nameser.h>
65 #include <resolv.h>
66 #include <stdio.h>
67 #include "../conf/portability.h"
68
69 void __fp_query();
70 char *__p_class(), *__p_time(), *__p_type();
71 char *p_cdname(), *p_fqname(), *p_rr();
72 static char *p_option __P((u_int32_t));
73
74 char *_res_opcodes[] = {
75         "QUERY",
76         "IQUERY",
77         "CQUERYM",
78         "CQUERYU",
79         "4",
80         "5",
81         "6",
82         "7",
83         "8",
84         "UPDATEA",
85         "UPDATED",
86         "UPDATEDA",
87         "UPDATEM",
88         "UPDATEMA",
89         "ZONEINIT",
90         "ZONEREF",
91 };
92
93 char *_res_resultcodes[] = {
94         "NOERROR",
95         "FORMERR",
96         "SERVFAIL",
97         "NXDOMAIN",
98         "NOTIMP",
99         "REFUSED",
100         "6",
101         "7",
102         "8",
103         "9",
104         "10",
105         "11",
106         "12",
107         "13",
108         "14",
109         "NOCHANGE",
110 };
111
112 static char retbuf[16];
113
114 static char *
115 dewks(wks)
116         int wks;
117 {
118         switch (wks) {
119         case 5: return("rje");
120         case 7: return("echo");
121         case 9: return("discard");
122         case 11: return("systat");
123         case 13: return("daytime");
124         case 15: return("netstat");
125         case 17: return("qotd");
126         case 19: return("chargen");
127         case 20: return("ftp-data");
128         case 21: return("ftp");
129         case 23: return("telnet");
130         case 25: return("smtp");
131         case 37: return("time");
132         case 39: return("rlp");
133         case 42: return("name");
134         case 43: return("whois");
135         case 53: return("domain");
136         case 57: return("apts");
137         case 59: return("apfs");
138         case 67: return("bootps");
139         case 68: return("bootpc");
140         case 69: return("tftp");
141         case 77: return("rje");
142         case 79: return("finger");
143         case 87: return("link");
144         case 95: return("supdup");
145         case 100: return("newacct");
146         case 101: return("hostnames");
147         case 102: return("iso-tsap");
148         case 103: return("x400");
149         case 104: return("x400-snd");
150         case 105: return("csnet-ns");
151         case 109: return("pop-2");
152         case 111: return("sunrpc");
153         case 113: return("auth");
154         case 115: return("sftp");
155         case 117: return("uucp-path");
156         case 119: return("nntp");
157         case 121: return("erpc");
158         case 123: return("ntp");
159         case 133: return("statsrv");
160         case 136: return("profile");
161         case 144: return("NeWS");
162         case 161: return("snmp");
163         case 162: return("snmp-trap");
164         case 170: return("print-srv");
165         default: (void) sprintf(retbuf, "%d", wks); return(retbuf);
166         }
167 }
168
169 static char *
170 deproto(protonum)
171         int protonum;
172 {
173         switch (protonum) {
174         case 1: return("icmp");
175         case 2: return("igmp");
176         case 3: return("ggp");
177         case 5: return("st");
178         case 6: return("tcp");
179         case 7: return("ucl");
180         case 8: return("egp");
181         case 9: return("igp");
182         case 11: return("nvp-II");
183         case 12: return("pup");
184         case 16: return("chaos");
185         case 17: return("udp");
186         default: (void) sprintf(retbuf, "%d", protonum); return(retbuf);
187         }
188 }
189
190 static char *
191 do_rrset(msg, cp, cnt, pflag, file, hs)
192         int cnt, pflag;
193         char *cp,*msg, *hs;
194         FILE *file;
195 {
196         int n;
197         char *t1, *t2, *list[100],**tt;
198         int sflag;
199         /*
200          * Print  answer records
201          */
202         sflag = (_res.pfcode & pflag);
203         if (n = ntohs(cnt)) {
204                 *list=NULL;
205                 if ((!_res.pfcode) || ((sflag) && (_res.pfcode & RES_PRF_HEAD1)))
206                         fprintf(file, hs);
207                 while (--n >= 0) {
208                         cp = p_rr(cp, msg, file);
209                         if ((cp-msg) > PACKETSZ)
210                                 return (NULL);
211                 }
212                 if ((!_res.pfcode) || ((sflag) && (_res.pfcode & RES_PRF_HEAD1)))
213                         putc('\n', file);
214         }
215         return(cp);
216 }
217
218 __p_query(msg)
219         char *msg;
220 {
221         __fp_query(msg, stdout);
222 }
223
224 /*
225  * Print the current options.
226  * This is intended to be primarily a debugging routine.
227  */
228 void
229 __fp_resstat(statp, file)
230         struct __res_state *statp;
231         FILE *file;
232 {
233         int bit;
234
235         fprintf(file, ";; res options:");
236         if (!statp)
237                 statp = &_res;
238         for (bit = 0;  bit < 32;  bit++) {      /* XXX 32 - bad assumption! */
239                 if (statp->options & (1<<bit))
240                         fprintf(file, " %s", p_option(1<<bit));
241         }
242         putc('\n', file);
243 }
244
245 /*
246  * Print the contents of a query.
247  * This is intended to be primarily a debugging routine.
248  */
249 void
250 __fp_query(msg,file)
251         char *msg;
252         FILE *file;
253 {
254         register char *cp;
255         register HEADER *hp;
256         register int n;
257
258         /*
259          * Print header fields.
260          */
261         hp = (HEADER *)msg;
262         cp = msg + sizeof(HEADER);
263         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEADX) || hp->rcode) {
264                 fprintf(file,";; ->>HEADER<<- opcode: %s, status: %s, id: %d",
265                         _res_opcodes[hp->opcode],
266                         _res_resultcodes[hp->rcode],
267                         ntohs(hp->id));
268                 putc('\n', file);
269         }
270         putc(';', file);
271         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEAD2)) {
272                 fprintf(file,"; flags:");
273                 if (hp->qr)
274                         fprintf(file," qr");
275                 if (hp->aa)
276                         fprintf(file," aa");
277                 if (hp->tc)
278                         fprintf(file," tc");
279                 if (hp->rd)
280                         fprintf(file," rd");
281                 if (hp->ra)
282                         fprintf(file," ra");
283                 if (hp->pr)
284                         fprintf(file," pr");
285         }
286         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_HEAD1)) {
287                 fprintf(file,"; Ques: %d", ntohs(hp->qdcount));
288                 fprintf(file,", Ans: %d", ntohs(hp->ancount));
289                 fprintf(file,", Auth: %d", ntohs(hp->nscount));
290                 fprintf(file,", Addit: %d\n", ntohs(hp->arcount));
291         }
292 #if 0
293         if (_res.pfcode & (RES_PRF_HEADX | RES_PRF_HEAD2 | RES_PRF_HEAD1)) {
294                 putc('\n',file);
295         }
296 #endif
297         /*
298          * Print question records.
299          */
300         if (n = ntohs(hp->qdcount)) {
301                 if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
302                         fprintf(file,";; QUESTIONS:\n");
303                 while (--n >= 0) {
304                         fprintf(file,";;\t");
305                         cp = p_cdname(cp, msg, file);
306                         if (cp == NULL)
307                                 return;
308                         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
309                                 fprintf(file, ", type = %s",
310                                         __p_type(_getshort(cp)));
311                         cp += sizeof(u_short);
312                         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_QUES))
313                                 fprintf(file, ", class = %s\n\n",
314                                         __p_class(_getshort(cp)));
315                         cp += sizeof(u_short);
316                 }
317         }
318         /*
319          * Print authoritative answer records
320          */
321         cp = do_rrset(msg, cp, hp->ancount, RES_PRF_ANS, file,
322                       ";; ANSWERS:\n");
323         if (cp == NULL)
324                 return;
325
326         /*
327          * print name server records
328          */
329         cp = do_rrset(msg, cp, hp->nscount, RES_PRF_AUTH, file,
330                       ";; AUTHORITY RECORDS:\n");
331         if (!cp)
332                 return;
333
334         /*
335          * print additional records
336          */
337         cp = do_rrset(msg, cp, hp->arcount, RES_PRF_ADD, file,
338                       ";; ADDITIONAL RECORDS:\n");
339         if (!cp)
340                 return;
341 }
342
343 char *
344 p_cdname(cp, msg, file)
345         char *cp, *msg;
346         FILE *file;
347 {
348         char name[MAXDNAME];
349         int n;
350
351         if ((n = dn_expand((u_char *)msg, (u_char *)msg + 512, (u_char *)cp,
352             (u_char *)name, sizeof(name))) < 0)
353                 return (NULL);
354         if (name[0] == '\0') {
355                 putc('.', file);
356         } else {
357                 fputs(name, file);
358         }
359         return (cp + n);
360 }
361
362 char *
363 p_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 *)msg + 512, (u_char *)cp,
371             (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
385 /*
386  * Print resource record fields in human readable form.
387  */
388 char *
389 p_rr(cp, msg, file)
390         char *cp, *msg;
391         FILE *file;
392 {
393         int type, class, dlen, n, c;
394         struct in_addr inaddr;
395         char *cp1, *cp2;
396         u_int32_t tmpttl, t;
397         int lcnt;
398
399         if ((cp = p_fqname(cp, msg, file)) == NULL)
400                 return (NULL);                  /* compression error */
401         type = _getshort(cp);
402         cp += sizeof(u_short);
403         class = _getshort(cp);
404         cp += sizeof(u_short);
405         tmpttl = _getlong(cp);
406         cp += sizeof(u_int32_t);
407         dlen = _getshort(cp);
408         cp += sizeof(u_short);
409         cp1 = cp;
410         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_TTLID))
411                 fprintf(file, "\t%lu", tmpttl);
412         if ((!_res.pfcode) || (_res.pfcode & RES_PRF_CLASS))
413                 fprintf(file, "\t%s", __p_class(class));
414         fprintf(file, "\t%s", __p_type(type));
415         /*
416          * Print type specific data, if appropriate
417          */
418         switch (type) {
419         case T_A:
420                 switch (class) {
421                 case C_IN:
422                 case C_HS:
423                         bcopy(cp, (char *)&inaddr, sizeof(inaddr));
424                         if (dlen == 4) {
425                                 fprintf(file,"\t%s", inet_ntoa(inaddr));
426                                 cp += dlen;
427                         } else if (dlen == 7) {
428                                 char *address;
429                                 u_char protocol;
430                                 u_short port;
431
432                                 address = inet_ntoa(inaddr);
433                                 cp += sizeof(inaddr);
434                                 protocol = *(u_char*)cp;
435                                 cp += sizeof(u_char);
436                                 port = _getshort(cp);
437                                 cp += sizeof(u_short);
438                                 fprintf(file, "\t%s\t; proto %d, port %d",
439                                         address, protocol, port);
440                         }
441                         break;
442                 default:
443                         cp += dlen;
444                 }
445                 break;
446         case T_CNAME:
447         case T_MB:
448         case T_MG:
449         case T_MR:
450         case T_NS:
451         case T_PTR:
452                 putc('\t', file);
453                 cp = p_fqname(cp, msg, file);
454                 break;
455
456         case T_HINFO:
457                 if (n = *cp++) {
458                         fprintf(file,"\t%.*s", n, cp);
459                         cp += n;
460                 }
461                 if (n = *cp++) {
462                         fprintf(file,"\t%.*s", n, cp);
463                         cp += n;
464                 }
465                 break;
466
467         case T_SOA:
468                 putc('\t', file);
469                 cp = p_fqname(cp, msg, file);   /* origin */
470                 putc(' ', file);
471                 cp = p_fqname(cp, msg, file);   /* mail addr */
472                 fputs(" (\n", file);
473                 t = _getlong(cp);  cp += sizeof(u_int32_t);
474                 fprintf(file,"\t\t\t%lu\t; serial\n", t);
475                 t = _getlong(cp);  cp += sizeof(u_int32_t);
476                 fprintf(file,"\t\t\t%lu\t; refresh (%s)\n", t, __p_time(t));
477                 t = _getlong(cp);  cp += sizeof(u_int32_t);
478                 fprintf(file,"\t\t\t%lu\t; retry (%s)\n", t, __p_time(t));
479                 t = _getlong(cp);  cp += sizeof(u_int32_t);
480                 fprintf(file,"\t\t\t%lu\t; expire (%s)\n", t, __p_time(t));
481                 t = _getlong(cp);  cp += sizeof(u_int32_t);
482                 fprintf(file,"\t\t\t%lu )\t; minimum (%s)", t, __p_time(t));
483                 break;
484
485         case T_MX:
486                 fprintf(file,"\t%d ", _getshort(cp));
487                 cp += sizeof(u_short);
488                 cp = p_fqname(cp, msg, file);
489                 break;
490
491         case T_TXT:
492                 (void) fputs("\t\"", file);
493                 cp2 = cp1 + dlen;
494                 while (cp < cp2) {
495                         if (n = (unsigned char) *cp++) {
496                                 for (c = n; c > 0 && cp < cp2; c--)
497                                         if (*cp == '\n') {
498                                             (void) putc('\\', file);
499                                             (void) putc(*cp++, file);
500                                         } else
501                                             (void) putc(*cp++, file);
502                         }
503                 }
504                 putc('"', file);
505                 break;
506
507         case T_MINFO:
508         case T_RP:
509                 putc('\t', file);
510                 cp = p_fqname(cp, msg, file);
511                 putc(' ', file);
512                 cp = p_fqname(cp, msg, file);
513                 break;
514
515         case T_UINFO:
516                 putc('\t', file);
517                 fputs(cp, file);
518                 cp += dlen;
519                 break;
520
521         case T_UID:
522         case T_GID:
523                 if (dlen == 4) {
524                         fprintf(file,"\t%u", _getlong(cp));
525                         cp += sizeof(int32_t);
526                 }
527                 break;
528
529         case T_WKS:
530                 if (dlen < sizeof(u_int32_t) + 1)
531                         break;
532                 bcopy(cp, (char *)&inaddr, sizeof(inaddr));
533                 cp += sizeof(u_int32_t);
534                 fprintf(file, "\t%s %s ( ",
535                         inet_ntoa(inaddr),
536                         deproto((int) *cp));
537                 cp += sizeof(u_char);
538                 n = 0;
539                 lcnt = 0;
540                 while (cp < cp1 + dlen) {
541                         c = *cp++;
542                         do {
543                                 if (c & 0200) {
544                                         if (lcnt == 0) {
545                                                 fputs("\n\t\t\t", file);
546                                                 lcnt = 5;
547                                         }
548                                         fputs(dewks(n), file);
549                                         putc(' ', file);
550                                         lcnt--;
551                                 }
552                                 c <<= 1;
553                         } while (++n & 07);
554                 }
555                 putc(')', file);
556                 break;
557
558 #ifdef ALLOW_T_UNSPEC
559         case T_UNSPEC:
560                 {
561                         int NumBytes = 8;
562                         char *DataPtr;
563                         int i;
564
565                         if (dlen < NumBytes) NumBytes = dlen;
566                         fprintf(file, "\tFirst %d bytes of hex data:",
567                                 NumBytes);
568                         for (i = 0, DataPtr = cp; i < NumBytes; i++, DataPtr++)
569                                 fprintf(file, " %x", *DataPtr);
570                         cp += dlen;
571                 }
572                 break;
573 #endif /* ALLOW_T_UNSPEC */
574
575         default:
576                 fprintf(file,"\t?%d?", type);
577                 cp += dlen;
578         }
579 #if 0
580         fprintf(file, "\t; dlen=%d, ttl %s\n", dlen, __p_time(tmpttl));
581 #else
582         putc('\n', file);
583 #endif
584         if (cp - cp1 != dlen) {
585                 fprintf(file,";; packet size error (found %d, dlen was %d)\n",
586                         cp - cp1, dlen);
587                 cp = NULL;
588         }
589         return (cp);
590 }
591
592 static  char nbuf[40];
593
594 /*
595  * Return a string for the type
596  */
597 char *
598 __p_type(type)
599         int type;
600 {
601         switch (type) {
602         case T_A:
603                 return("A");
604         case T_NS:              /* authoritative server */
605                 return("NS");
606         case T_CNAME:           /* canonical name */
607                 return("CNAME");
608         case T_SOA:             /* start of authority zone */
609                 return("SOA");
610         case T_MB:              /* mailbox domain name */
611                 return("MB");
612         case T_MG:              /* mail group member */
613                 return("MG");
614         case T_MR:              /* mail rename name */
615                 return("MR");
616         case T_NULL:            /* null resource record */
617                 return("NULL");
618         case T_WKS:             /* well known service */
619                 return("WKS");
620         case T_PTR:             /* domain name pointer */
621                 return("PTR");
622         case T_HINFO:           /* host information */
623                 return("HINFO");
624         case T_MINFO:           /* mailbox information */
625                 return("MINFO");
626         case T_MX:              /* mail routing info */
627                 return("MX");
628         case T_TXT:             /* text */
629                 return("TXT");
630         case T_RP:              /* responsible person */
631                 return("RP");
632         case T_AXFR:            /* zone transfer */
633                 return("AXFR");
634         case T_MAILB:           /* mail box */
635                 return("MAILB");
636         case T_MAILA:           /* mail address */
637                 return("MAILA");
638         case T_ANY:             /* matches any type */
639                 return("ANY");
640         case T_UINFO:
641                 return("UINFO");
642         case T_UID:
643                 return("UID");
644         case T_GID:
645                 return("GID");
646 #ifdef ALLOW_T_UNSPEC
647         case T_UNSPEC:
648                 return("UNSPEC");
649 #endif /* ALLOW_T_UNSPEC */
650         default:
651                 (void)sprintf(nbuf, "%d", type);
652                 return(nbuf);
653         }
654 }
655
656 /*
657  * Return a mnemonic for class
658  */
659 char *
660 __p_class(class)
661         int class;
662 {
663
664         switch (class) {
665         case C_IN:              /* internet class */
666                 return("IN");
667         case C_HS:              /* hesiod class */
668                 return("HS");
669         case C_ANY:             /* matches any class */
670                 return("ANY");
671         default:
672                 (void)sprintf(nbuf, "%d", class);
673                 return(nbuf);
674         }
675 }
676
677 /*
678  * Return a mnemonic for an option
679  */
680 static char *
681 p_option(option)
682         u_int32_t option;
683 {
684         switch (option) {
685         case RES_INIT:          return "init";
686         case RES_DEBUG:         return "debug";
687         case RES_AAONLY:        return "aaonly";
688         case RES_USEVC:         return "usevc";
689         case RES_PRIMARY:       return "primry";
690         case RES_IGNTC:         return "igntc";
691         case RES_RECURSE:       return "recurs";
692         case RES_DEFNAMES:      return "defnam";
693         case RES_STAYOPEN:      return "styopn";
694         case RES_DNSRCH:        return "dnsrch";
695         default:                sprintf(nbuf, "?0x%x?", option); return nbuf;
696         }
697 }
698
699 /*
700  * Return a mnemonic for a time to live
701  */
702 char *
703 __p_time(value)
704         u_int32_t value;
705 {
706         int secs, mins, hours, days;
707         register char *p;
708
709         if (value == 0) {
710                 strcpy(nbuf, "0 secs");
711                 return(nbuf);
712         }
713
714         secs = value % 60;
715         value /= 60;
716         mins = value % 60;
717         value /= 60;
718         hours = value % 24;
719         value /= 24;
720         days = value;
721         value = 0;
722
723 #define PLURALIZE(x)    x, (x == 1) ? "" : "s"
724         p = nbuf;
725         if (days) {
726                 (void)sprintf(p, "%d day%s", PLURALIZE(days));
727                 while (*++p);
728         }
729         if (hours) {
730                 if (days)
731                         *p++ = ' ';
732                 (void)sprintf(p, "%d hour%s", PLURALIZE(hours));
733                 while (*++p);
734         }
735         if (mins) {
736                 if (days || hours)
737                         *p++ = ' ';
738                 (void)sprintf(p, "%d min%s", PLURALIZE(mins));
739                 while (*++p);
740         }
741         if (secs || ! (days || hours || mins)) {
742                 if (days || hours || mins)
743                         *p++ = ' ';
744                 (void)sprintf(p, "%d sec%s", PLURALIZE(secs));
745         }
746         return(nbuf);
747 }