home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / bsd_srcs / usr.sbin / amd / amq / amq.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-05-12  |  14.4 KB  |  656 lines

  1. /*
  2.  * Copyright (c) 1990 Jan-Simon Pendry
  3.  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
  4.  * Copyright (c) 1990 The Regents of the University of California.
  5.  * All rights reserved.
  6.  *
  7.  * This code is derived from software contributed to Berkeley by
  8.  * Jan-Simon Pendry at Imperial College, London.
  9.  *
  10.  * Redistribution and use in source and binary forms, with or without
  11.  * modification, are permitted provided that the following conditions
  12.  * are met:
  13.  * 1. Redistributions of source code must retain the above copyright
  14.  *    notice, this list of conditions and the following disclaimer.
  15.  * 2. Redistributions in binary form must reproduce the above copyright
  16.  *    notice, this list of conditions and the following disclaimer in the
  17.  *    documentation and/or other materials provided with the distribution.
  18.  * 3. All advertising materials mentioning features or use of this software
  19.  *    must display the following acknowledgement:
  20.  *    This product includes software developed by the University of
  21.  *    California, Berkeley and its contributors.
  22.  * 4. Neither the name of the University nor the names of its contributors
  23.  *    may be used to endorse or promote products derived from this software
  24.  *    without specific prior written permission.
  25.  *
  26.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  27.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  28.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  29.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  30.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  31.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  32.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  33.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  34.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  35.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  36.  * SUCH DAMAGE.
  37.  *
  38.  *    @(#)amq.c    5.3 (Berkeley) 5/12/91
  39.  *
  40.  * $Id: amq.c,v 5.2.1.5 91/05/07 22:18:45 jsp Alpha $
  41.  *
  42.  */
  43.  
  44. /*
  45.  * Automounter query tool
  46.  */
  47.  
  48. #ifndef lint
  49. char copyright[] = "\
  50. @(#)Copyright (c) 1990 Jan-Simon Pendry\n\
  51. @(#)Copyright (c) 1990 Imperial College of Science, Technology & Medicine\n\
  52. @(#)Copyright (c) 1990 The Regents of the University of California.\n\
  53. @(#)All rights reserved.\n";
  54. #endif /* not lint */
  55.  
  56. #ifndef lint
  57. static char rcsid[] = "$Id: amq.c,v 5.2.1.5 91/05/07 22:18:45 jsp Alpha $";
  58. static char sccsid[] = "@(#)amq.c    5.3 (Berkeley) 5/12/91";
  59. #endif /* not lint */
  60.  
  61. #include "am.h"
  62. #include "amq.h"
  63. #include <stdio.h>
  64. #include <fcntl.h>
  65. #include <netdb.h>
  66.  
  67. static int privsock();
  68.  
  69. char *progname;
  70. static int flush_flag;
  71. static int minfo_flag;
  72. static int unmount_flag;
  73. static int stats_flag;
  74. static int getvers_flag;
  75. static char *debug_opts;
  76. static char *logfile;
  77. static char *mount_map;
  78. static char *xlog_optstr;
  79. static char localhost[] = "localhost";
  80. static char *def_server = localhost;
  81.  
  82. extern int optind;
  83. extern char *optarg;
  84.  
  85. static struct timeval tmo = { 10, 0 };
  86. #define    TIMEOUT tmo
  87.  
  88. enum show_opt { Full, Stats, Calc, Short, ShowDone };
  89.  
  90. /*
  91.  * If (e) is Calc then just calculate the sizes
  92.  * Otherwise display the mount node on stdout
  93.  */
  94. static void show_mti(mt, e, mwid, dwid, twid)
  95. amq_mount_tree *mt;
  96. enum show_opt e;
  97. int *mwid;
  98. int *dwid;
  99. int *twid;
  100. {
  101.     switch (e) {
  102.     case Calc: {
  103.         int mw = strlen(mt->mt_mountinfo);
  104.         int dw = strlen(mt->mt_directory);
  105.         int tw = strlen(mt->mt_type);
  106.         if (mw > *mwid) *mwid = mw;
  107.         if (dw > *dwid) *dwid = dw;
  108.         if (tw > *twid) *twid = tw;
  109.     } break;
  110.  
  111.     case Full: {
  112.         struct tm *tp = localtime((time_t *) &mt->mt_mounttime);
  113. printf("%-*.*s %-*.*s %-*.*s %s\n\t%-5d %-7d %-6d %-7d %-7d %-6d %02d/%02d/%02d %02d:%02d:%02d\n",
  114.             *dwid, *dwid,
  115.             *mt->mt_directory ? mt->mt_directory : "/",    /* XXX */
  116.             *twid, *twid,
  117.             mt->mt_type,
  118.             *mwid, *mwid, 
  119.             mt->mt_mountinfo,
  120.             mt->mt_mountpoint,
  121.  
  122.             mt->mt_mountuid,
  123.             mt->mt_getattr,
  124.             mt->mt_lookup,
  125.             mt->mt_readdir,
  126.             mt->mt_readlink,
  127.             mt->mt_statfs,
  128.  
  129.             tp->tm_year > 99 ? tp->tm_year - 100 : tp->tm_year,
  130.             tp->tm_mon+1, tp->tm_mday,
  131.             tp->tm_hour, tp->tm_min, tp->tm_sec);
  132.     } break;
  133.  
  134.     case Stats: {
  135.         struct tm *tp = localtime((time_t *) &mt->mt_mounttime);
  136. printf("%-*.*s %-5d %-7d %-6d %-7d %-7d %-6d %02d/%02d/%02d %02d:%02d:%02d\n",
  137.             *dwid, *dwid,
  138.             *mt->mt_directory ? mt->mt_directory : "/",    /* XXX */
  139.  
  140.             mt->mt_mountuid,
  141.             mt->mt_getattr,
  142.             mt->mt_lookup,
  143.             mt->mt_readdir,
  144.             mt->mt_readlink,
  145.             mt->mt_statfs,
  146.  
  147.             tp->tm_year > 99 ? tp->tm_year - 100 : tp->tm_year,
  148.             tp->tm_mon+1, tp->tm_mday,
  149.             tp->tm_hour, tp->tm_min, tp->tm_sec);
  150.     } break;
  151.  
  152.     case Short: {
  153.         printf("%-*.*s %-*.*s %-*.*s %s\n",
  154.             *dwid, *dwid,
  155.             *mt->mt_directory ? mt->mt_directory : "/",
  156.             *twid, *twid,
  157.             mt->mt_type,
  158.             *mwid, *mwid,
  159.             mt->mt_mountinfo,
  160.             mt->mt_mountpoint);
  161.     } break;
  162.     }
  163. }
  164.  
  165. /*
  166.  * Display a mount tree.
  167.  */
  168. static void show_mt(mt, e, mwid, dwid, pwid)
  169. amq_mount_tree *mt;
  170. enum show_opt e;
  171. int *mwid;
  172. int *dwid;
  173. int *pwid;
  174. {
  175.     while (mt) {
  176.         show_mti(mt, e, mwid, dwid, pwid);
  177.         show_mt(mt->mt_next, e, mwid, dwid, pwid);
  178.         mt = mt->mt_child;
  179.     }
  180. }
  181.  
  182. static void show_mi(ml, e, mwid, dwid, twid)
  183. amq_mount_info_list *ml;
  184. enum show_opt e;
  185. int *mwid;
  186. int *dwid;
  187. int *twid;
  188. {
  189.     int i;
  190.     switch (e) {
  191.     case Calc: {
  192.         for (i = 0; i < ml->amq_mount_info_list_len; i++) {
  193.             amq_mount_info *mi = &ml->amq_mount_info_list_val[i];
  194.             int mw = strlen(mi->mi_mountinfo);
  195.             int dw = strlen(mi->mi_mountpt);
  196.             int tw = strlen(mi->mi_type);
  197.             if (mw > *mwid) *mwid = mw;
  198.             if (dw > *dwid) *dwid = dw;
  199.             if (tw > *twid) *twid = tw;
  200.         }
  201.     } break;
  202.  
  203.     case Full: {
  204.         for (i = 0; i < ml->amq_mount_info_list_len; i++) {
  205.             amq_mount_info *mi = &ml->amq_mount_info_list_val[i];
  206.             printf("%-*.*s %-*.*s %-*.*s %-3d %s is %s",
  207.                         *mwid, *mwid, mi->mi_mountinfo,
  208.                         *dwid, *dwid, mi->mi_mountpt,
  209.                         *twid, *twid, mi->mi_type,
  210.                         mi->mi_refc, mi->mi_fserver,
  211.                         mi->mi_up > 0 ? "up" :
  212.                         mi->mi_up < 0 ? "starting" : "down");
  213.             if (mi->mi_error > 0) {
  214.                 extern char *sys_errlist[];
  215.                 extern int sys_nerr;
  216.                 if (mi->mi_error < sys_nerr)
  217.                     printf(" (%s)", sys_errlist[mi->mi_error]);
  218.                 else
  219.                     printf(" (Error %d)", mi->mi_error);
  220.             } else if (mi->mi_error < 0) {
  221.                 fputs(" (in progress)", stdout);
  222.             }
  223.             fputc('\n', stdout);
  224.         }
  225.     } break;
  226.     }
  227. }
  228.  
  229. /*
  230.  * Display general mount statistics
  231.  */
  232. static void show_ms(ms)
  233. amq_mount_stats *ms;
  234. {
  235.     printf("\
  236. requests  stale     mount     mount     unmount\n\
  237. deferred  fhandles  ok        failed    failed\n\
  238. %-9d %-9d %-9d %-9d %-9d\n",
  239.     ms->as_drops, ms->as_stale, ms->as_mok, ms->as_merr, ms->as_uerr);
  240. }
  241.  
  242. static bool_t
  243. xdr_pri_free(xdr_args, args_ptr)
  244. xdrproc_t xdr_args;
  245. caddr_t args_ptr;
  246. {
  247.     XDR xdr;
  248.     xdr.x_op = XDR_FREE;
  249.     return ((*xdr_args)(&xdr, args_ptr));
  250. }
  251.  
  252. #ifdef hpux
  253. #include <cluster.h>
  254. static char *cluster_server()
  255. {
  256.     struct cct_entry *cp;
  257.  
  258.     if (cnodeid() == 0) {
  259.         /*
  260.          * Not clustered
  261.          */
  262.         return def_server;
  263.     }
  264.  
  265.     while (cp = getccent())
  266.         if (cp->cnode_type == 'r')
  267.             return cp->cnode_name;
  268.  
  269.  
  270.     return def_server;
  271. }
  272. #endif /* hpux */
  273.  
  274. /*
  275.  * MAIN
  276.  */
  277. main(argc, argv)
  278. int argc;
  279. char *argv[];
  280. {
  281.     int opt_ch;
  282.     int errs = 0;
  283.     char *server;
  284.     struct sockaddr_in server_addr;
  285.  
  286.     /* In order to pass the Amd security check, we must use a priv port. */
  287.     int s;
  288.  
  289.     CLIENT *clnt;
  290.     struct hostent *hp;
  291.     int nodefault = 0;
  292.  
  293.     /*
  294.      * Compute program name
  295.      */
  296.     if (argv[0]) {
  297.         progname = strrchr(argv[0], '/');
  298.         if (progname && progname[1])
  299.             progname++;
  300.         else
  301.             progname = argv[0];
  302.     }
  303.     if (!progname)
  304.         progname = "amq";
  305.  
  306.     /*
  307.      * Parse arguments
  308.      */
  309.     while ((opt_ch = getopt(argc, argv, "fh:l:msuvx:D:M:")) != EOF)
  310.     switch (opt_ch) {
  311.     case 'f':
  312.         flush_flag = 1;
  313.         nodefault = 1;
  314.         break;
  315.  
  316.     case 'h':
  317.         def_server = optarg;
  318.         break;
  319.  
  320.     case 'l':
  321.         logfile = optarg;
  322.         nodefault = 1;
  323.         break;
  324.  
  325.     case 'm':
  326.         minfo_flag = 1;
  327.         nodefault = 1;
  328.         break;
  329.  
  330.     case 's':
  331.         stats_flag = 1;
  332.         nodefault = 1;
  333.         break;
  334.  
  335.     case 'u':
  336.         unmount_flag = 1;
  337.         nodefault = 1;
  338.         break;
  339.  
  340.     case 'v':
  341.         getvers_flag = 1;
  342.         nodefault = 1;
  343.         break;
  344.  
  345.     case 'x':
  346.         xlog_optstr = optarg;
  347.         nodefault = 1;
  348.         break;
  349.  
  350.     case 'D':
  351.         debug_opts = optarg;
  352.         nodefault = 1;
  353.         break;
  354.  
  355.     case 'M':
  356.         mount_map = optarg;
  357.         nodefault = 1;
  358.         break;
  359.  
  360.     default:
  361.         errs = 1;
  362.         break;
  363.     }
  364.  
  365.     if (optind == argc) {
  366.         if (unmount_flag)
  367.             errs = 1;
  368.     }
  369.     
  370.     if (errs) {
  371. show_usage:
  372.         fprintf(stderr, "\
  373. Usage: %s [-h host] [[-f] [-m] [-v] [-s]] | [[-u] directory ...]] |\n\
  374. \t[-l logfile|\"syslog\"] [-x log_flags] [-D dbg_opts] [-M mapent]\n", progname);
  375.         exit(1);
  376.     }
  377.  
  378. #ifdef hpux
  379.     /*
  380.      * Figure out root server of cluster
  381.      */
  382.     if (def_server == localhost)
  383.         server = cluster_server();
  384.     else
  385. #endif /* hpux */
  386.     server = def_server;
  387.  
  388.     /*
  389.      * Get address of server
  390.      */
  391.     if ((hp = gethostbyname(server)) == 0 && strcmp(server, localhost) != 0) {
  392.         fprintf(stderr, "%s: Can't get address of %s\n", progname, server);
  393.         exit(1);
  394.     }
  395.     bzero(&server_addr, sizeof server_addr);
  396.     server_addr.sin_family = AF_INET;
  397.     if (hp) {
  398.         bcopy((voidp) hp->h_addr, (voidp) &server_addr.sin_addr,
  399.             sizeof(server_addr.sin_addr));
  400.     } else {
  401.         /* fake "localhost" */
  402.         server_addr.sin_addr.s_addr = htonl(0x7f000001);
  403.     }
  404.  
  405.     /*
  406.      * Create RPC endpoint
  407.      */
  408.     s = privsock();
  409.     clnt = clntudp_create(&server_addr, AMQ_PROGRAM, AMQ_VERSION, TIMEOUT, &s);
  410.     if (clnt == 0) {
  411.         fprintf(stderr, "%s: ", progname);
  412.         clnt_pcreateerror(server);
  413.         exit(1);
  414.     }
  415.  
  416.     /*
  417.      * Control debugging
  418.      */
  419.     if (debug_opts) {
  420.         int *rc;
  421.         amq_setopt opt;
  422.         opt.as_opt = AMOPT_DEBUG;
  423.         opt.as_str = debug_opts;
  424.         rc = amqproc_setopt_1(&opt, clnt);
  425.         if (rc && *rc < 0) {
  426.             fprintf(stderr, "%s: daemon not compiled for debug", progname);
  427.             errs = 1;
  428.         } else if (!rc || *rc > 0) {
  429.             fprintf(stderr, "%s: debug setting for \"%s\" failed\n", progname, debug_opts);
  430.             errs = 1;
  431.         }
  432.     }
  433.  
  434.     /*
  435.      * Control logging
  436.      */
  437.     if (xlog_optstr) {
  438.         int *rc;
  439.         amq_setopt opt;
  440.         opt.as_opt = AMOPT_XLOG;
  441.         opt.as_str = xlog_optstr;
  442.         rc = amqproc_setopt_1(&opt, clnt);
  443.         if (!rc || *rc) {
  444.             fprintf(stderr, "%s: setting log level to \"%s\" failed\n", progname, xlog_optstr);
  445.             errs = 1;
  446.         }
  447.     }
  448.  
  449.     /*
  450.      * Control log file
  451.      */
  452.     if (logfile) {
  453.         int *rc;
  454.         amq_setopt opt;
  455.         opt.as_opt = AMOPT_LOGFILE;
  456.         opt.as_str = logfile;
  457.         rc = amqproc_setopt_1(&opt, clnt);
  458.         if (!rc || *rc) {
  459.             fprintf(stderr, "%s: setting logfile to \"%s\" failed\n", progname, logfile);
  460.             errs = 1;
  461.         }
  462.     }
  463.  
  464.     /*
  465.      * Flush map cache
  466.      */
  467.     if (flush_flag) {
  468.         int *rc;
  469.         amq_setopt opt;
  470.         opt.as_opt = AMOPT_FLUSHMAPC;
  471.         opt.as_str = "";
  472.         rc = amqproc_setopt_1(&opt, clnt);
  473.         if (!rc || *rc) {
  474.             fprintf(stderr, "%s: amd on %s cannot flush the map cache\n", progname, server);
  475.             errs = 1;
  476.         }
  477.     }
  478.  
  479.     /*
  480.      * Mount info
  481.      */
  482.     if (minfo_flag) {
  483.         int dummy;
  484.         amq_mount_info_list *ml = amqproc_getmntfs_1(&dummy, clnt);
  485.         if (ml) {
  486.             int mwid = 0, dwid = 0, twid = 0;
  487.             show_mi(ml, Calc, &mwid, &dwid, &twid);
  488.             mwid++; dwid++; twid++;
  489.             show_mi(ml, Full, &mwid, &dwid, &twid);
  490.  
  491.         } else {
  492.             fprintf(stderr, "%s: amd on %s cannot provide mount info\n", progname, server);
  493.         }
  494.     }
  495.  
  496.     /*
  497.      * Mount map
  498.      */
  499.     if (mount_map) {
  500.         int *rc;
  501.         do {
  502.             rc = amqproc_mount_1(&mount_map, clnt);
  503.         } while (rc && *rc < 0);
  504.         if (!rc || *rc > 0) {
  505.             if (rc)
  506.                 errno = *rc;
  507.             else
  508.                 errno = ETIMEDOUT;
  509.             fprintf(stderr, "%s: could not start new ", progname);
  510.             perror("autmount point");
  511.         }
  512.     }
  513.  
  514.     /*
  515.      * Get Version
  516.      */
  517.     if (getvers_flag) {
  518.         amq_string *spp = amqproc_getvers_1((voidp) 0, clnt);
  519.         if (spp && *spp) {
  520.             printf("%s.\n", *spp);
  521.             free(*spp);
  522.         } else {
  523.             fprintf(stderr, "%s: failed to get version information\n", progname);
  524.             errs = 1;
  525.         }
  526.     }
  527.  
  528.     /*
  529.      * Apply required operation to all remaining arguments
  530.      */
  531.     if (optind < argc) {
  532.         do {
  533.             char *fs = argv[optind++];
  534.             if (unmount_flag) {
  535.                 /*
  536.                  * Unmount request
  537.                  */
  538.                 amqproc_umnt_1(&fs, clnt);
  539.             } else {
  540.                 /*
  541.                  * Stats request
  542.                  */
  543.                 amq_mount_tree_p *mtp = amqproc_mnttree_1(&fs, clnt);
  544.                 if (mtp) {
  545.                     amq_mount_tree *mt = *mtp;
  546.                     if (mt) {
  547.                         int mwid = 0, dwid = 0, twid = 0;
  548.                         show_mt(mt, Calc, &mwid, &dwid, &twid);
  549.                         mwid++; dwid++, twid++;
  550.         printf("%-*.*s Uid   Getattr Lookup RdDir   RdLnk   Statfs Mounted@\n",
  551.             dwid, dwid, "What");
  552.                         show_mt(mt, Stats, &mwid, &dwid, &twid);
  553.                     } else {
  554.                         fprintf(stderr, "%s: %s not automounted\n", progname, fs);
  555.                     }
  556.                     xdr_pri_free(xdr_amq_mount_tree_p, (caddr_t) mtp);
  557.                 } else {
  558.                     fprintf(stderr, "%s: ", progname);
  559.                     clnt_perror(clnt, server);
  560.                     errs = 1;
  561.                 }
  562.             }
  563.         } while (optind < argc);
  564.     } else if (unmount_flag) {
  565.         goto show_usage;
  566.     } else if (stats_flag) {
  567.         amq_mount_stats *ms = amqproc_stats_1((voidp) 0, clnt);
  568.         if (ms) {
  569.             show_ms(ms);
  570.         } else {
  571.             fprintf(stderr, "%s: ", progname);
  572.             clnt_perror(clnt, server);
  573.             errs = 1;
  574.         }
  575.     } else if (!nodefault) {
  576.         amq_mount_tree_list *mlp = amqproc_export_1((voidp) 0, clnt);
  577.         if (mlp) {
  578.             enum show_opt e = Calc;
  579.             int mwid = 0, dwid = 0, pwid = 0;
  580.             while (e != ShowDone) {
  581.                 int i;
  582.                 for (i = 0; i < mlp->amq_mount_tree_list_len; i++) {
  583.                     show_mt(mlp->amq_mount_tree_list_val[i],
  584.                          e, &mwid, &dwid, &pwid);
  585.                 }
  586.                 mwid++; dwid++, pwid++;
  587.                 if (e == Calc) e = Short;
  588.                 else if (e == Short) e = ShowDone;
  589.             }
  590.         } else {
  591.             fprintf(stderr, "%s: ", progname);
  592.             clnt_perror(clnt, server);
  593.             errs = 1;
  594.         }
  595.     }
  596.  
  597.     exit(errs);
  598. }
  599.  
  600. /*
  601.  * udpresport creates a datagram socket and attempts to bind it to a 
  602.  * secure port.
  603.  * returns: The bound socket, or -1 to indicate an error.
  604.  */
  605. static int udpresport()
  606. {
  607.     int alport;
  608.     struct sockaddr_in addr;
  609.     int sock;
  610.  
  611.     /* Use internet address family */
  612.     addr.sin_family = AF_INET;
  613.     addr.sin_addr.s_addr = INADDR_ANY;
  614.     if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
  615.         return -1;
  616.     for (alport = IPPORT_RESERVED-1; alport > IPPORT_RESERVED/2 + 1; alport--) {
  617.         addr.sin_port = htons((u_short)alport);
  618.         if (bind(sock, (struct sockaddr *)&addr, sizeof (addr)) >= 0)
  619.             return sock;
  620.         if (errno != EADDRINUSE) {
  621.             close(sock);
  622.             return -1;
  623.         }
  624.     }
  625.     close(sock);
  626.     errno = EAGAIN;
  627.     return -1;
  628. }
  629.  
  630. /*
  631.  * Privsock() calls udpresport() to attempt to bind a socket to a secure
  632.  * port.  If udpresport() fails, privsock returns a magic socket number which
  633.  * indicates to RPC that it should make its own socket.
  634.  * returns: A privileged socket # or RPC_ANYSOCK.
  635.  */
  636. static int privsock()
  637. {
  638.     int sock = udpresport();
  639.  
  640.     if (sock < 0) {
  641.         errno = 0;
  642.         /* Couldn't get a secure port, let RPC make an insecure one */
  643.         sock = RPC_ANYSOCK;
  644.     }
  645.     return sock;
  646. }
  647.  
  648. #ifdef DEBUG
  649. xfree(f, l, p)
  650. char *f, *l;
  651. voidp p;
  652. {
  653.     free(p);
  654. }
  655. #endif /* DEBUG */
  656.