]> git.ozlabs.org Git - ppp.git/blob - pppd/sys-ultrix.c
Declare option variables here; define uint32
[ppp.git] / pppd / sys-ultrix.c
1 /*
2  * sys-ultrix.c - System-dependent procedures for setting up
3  * PPP interfaces on Ultrix systems.
4  *
5  * Copyright (c) 1989 Carnegie Mellon University.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms are permitted
9  * provided that the above copyright notice and this paragraph are
10  * duplicated in all such forms and that any documentation,
11  * advertising materials, and other materials related to such
12  * distribution and use acknowledge that the software was developed
13  * by Carnegie Mellon University.  The name of the
14  * University may not be used to endorse or promote products derived
15  * from this software without specific prior written permission.
16  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
18  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19  */
20
21 #ifndef lint
22 static char rcsid[] = "$Id: sys-ultrix.c,v 1.4 1994/05/25 06:30:49 paulus Exp $";
23 #endif
24
25 /*
26  * TODO:
27  */
28
29 #include <syslog.h>
30 #include <utmp.h>
31 #include <sys/types.h>
32 #include <sys/file.h>
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/time.h>
36 #include <sys/errno.h>
37 #include <sys/stat.h>
38
39 #include <net/if.h>
40
41 #include <net/if_ppp.h>
42 #include <net/route.h>
43 #include <netinet/in.h>
44
45 #include "pppd.h"
46 #include "ppp.h"
47
48 static int initdisc = -1;               /* Initial TTY discipline */
49 extern int kdebugflag;
50
51 /*
52  * establish_ppp - Turn the serial port into a ppp interface.
53  */
54 void
55 establish_ppp()
56 {
57     int pppdisc = PPPDISC;
58     int x;
59
60     if (ioctl(fd, TIOCGETD, &initdisc) < 0) {
61         syslog(LOG_ERR, "ioctl(TIOCGETD): %m");
62         die(1);
63     }
64     if (ioctl(fd, TIOCSETD, &pppdisc) < 0) {
65         syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
66         die(1);
67     }
68
69     /*
70      * Find out which interface we were given.
71      */
72     if (ioctl(fd, PPPIOCGUNIT, &ifunit) < 0) {  
73         syslog(LOG_ERR, "ioctl(PPPIOCGUNIT): %m");
74         die(1);
75     }
76
77     /*
78      * Enable debug in the driver if requested.
79      */
80     if (kdebugflag) {
81         if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
82             syslog(LOG_WARNING, "ioctl (PPPIOCGFLAGS): %m");
83         } else {
84             x |= (kdebugflag & 0xFF) * SC_DEBUG;
85             if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0)
86                 syslog(LOG_WARNING, "ioctl(PPPIOCSFLAGS): %m");
87         }
88     }
89 }
90
91
92 /*
93  * disestablish_ppp - Restore the serial port to normal operation.
94  * This shouldn't call die() because it's called from die().
95  */
96 void
97 disestablish_ppp()
98 {
99     int x;
100     char *s;
101
102     if (initdisc >= 0) {
103         /*
104          * Check whether the link seems not to be 8-bit clean.
105          */
106         if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) == 0) {
107             s = NULL;
108             switch (~x & (SC_RCV_B7_0|SC_RCV_B7_1|SC_RCV_EVNP|SC_RCV_ODDP)) {
109             case SC_RCV_B7_0:
110                 s = "bit 7 set to 1";
111                 break;
112             case SC_RCV_B7_1:
113                 s = "bit 7 set to 0";
114                 break;
115             case SC_RCV_EVNP:
116                 s = "odd parity";
117                 break;
118             case SC_RCV_ODDP:
119                 s = "even parity";
120                 break;
121             }
122             if (s != NULL) {
123                 syslog(LOG_WARNING, "Serial link is not 8-bit clean:");
124                 syslog(LOG_WARNING, "All received characters had %s", s);
125             }
126         }
127         if (ioctl(fd, TIOCSETD, &initdisc) < 0)
128             syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
129     }
130 }
131
132
133 /*
134  * output - Output PPP packet.
135  */
136 void
137 output(unit, p, len)
138     int unit;
139     u_char *p;
140     int len;
141 {
142     if (unit != 0)
143         MAINDEBUG((LOG_WARNING, "output: unit != 0!"));
144     if (debug)
145         log_packet(p, len, "sent ");
146
147     if (write(fd, p, len) < 0) {
148         syslog(LOG_ERR, "write: %m");
149         die(1);
150     }
151 }
152
153
154 /*
155  * read_packet - get a PPP packet from the serial device.
156  */
157 int
158 read_packet(buf)
159     u_char *buf;
160 {
161     int len;
162
163     if ((len = read(fd, buf, MTU + DLLHEADERLEN)) < 0) {
164         if (errno == EWOULDBLOCK) {
165             MAINDEBUG((LOG_DEBUG, "read(fd): EWOULDBLOCK"));
166             return -1;
167         }
168         syslog(LOG_ERR, "read(fd): %m");
169         die(1);
170     }
171     return len;
172 }
173
174
175 /*
176  * ppp_send_config - configure the transmit characteristics of
177  * the ppp interface.
178  */
179 void
180 ppp_send_config(unit, mtu, asyncmap, pcomp, accomp)
181     int unit, mtu;
182     u_long asyncmap;
183     int pcomp, accomp;
184 {
185     u_int x;
186     struct ifreq ifr;
187
188     strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
189     ifr.ifr_mtu = mtu;
190     if (ioctl(s, SIOCSIFMTU, (caddr_t) &ifr) < 0) {
191         syslog(LOG_ERR, "ioctl(SIOCSIFMTU): %m");
192         quit();
193     }
194
195     if (ioctl(fd, PPPIOCSASYNCMAP, (caddr_t) &asyncmap) < 0) {
196         syslog(LOG_ERR, "ioctl(PPPIOCSASYNCMAP): %m");
197         quit();
198     }
199
200     if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
201         syslog(LOG_ERR, "ioctl (PPPIOCGFLAGS): %m");
202         quit();
203     }
204     x = pcomp? x | SC_COMP_PROT: x &~ SC_COMP_PROT;
205     x = accomp? x | SC_COMP_AC: x &~ SC_COMP_AC;
206     if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0) {
207         syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
208         quit();
209     }
210 }
211
212
213 /*
214  * ppp_set_xaccm - set the extended transmit ACCM for the interface.
215  */
216 void
217 ppp_set_xaccm(unit, accm)
218     int unit;
219     ext_accm accm;
220 {
221     if (ioctl(fd, PPPIOCSXASYNCMAP, accm) < 0 && errno != ENOTTY)
222         syslog(LOG_WARNING, "ioctl(set extended ACCM): %m");
223 }
224
225
226 /*
227  * ppp_recv_config - configure the receive-side characteristics of
228  * the ppp interface.
229  */
230 void
231 ppp_recv_config(unit, mru, asyncmap, pcomp, accomp)
232     int unit, mru;
233     u_long asyncmap;
234     int pcomp, accomp;
235 {
236     int x;
237
238     if (ioctl(fd, PPPIOCSMRU, (caddr_t) &mru) < 0) {
239         syslog(LOG_ERR, "ioctl(PPPIOCSMRU): %m");
240         quit();
241     }
242     if (ioctl(fd, PPPIOCSRASYNCMAP, (caddr_t) &asyncmap) < 0) {
243         syslog(LOG_ERR, "ioctl(PPPIOCSRASYNCMAP): %m");
244         quit();
245     }
246     if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
247         syslog(LOG_ERR, "ioctl (PPPIOCGFLAGS): %m");
248         quit();
249     }
250     x = !accomp? x | SC_REJ_COMP_AC: x &~ SC_REJ_COMP_AC;
251     if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0) {
252         syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
253         quit();
254     }
255 }
256
257 /*
258  * sifvjcomp - config tcp header compression
259  */
260 int
261 sifvjcomp(u, vjcomp, cidcomp, maxcid)
262     int u, vjcomp, cidcomp, maxcid;
263 {
264     u_int x;
265
266     if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
267         syslog(LOG_ERR, "ioctl (PPPIOCGFLAGS): %m");
268         return 0;
269     }
270     x = vjcomp ? x | SC_COMP_TCP: x &~ SC_COMP_TCP;
271     x = cidcomp? x & ~SC_NO_TCP_CCID: x | SC_NO_TCP_CCID;
272     if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0) {
273         syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
274         return 0;
275     }
276     if (ioctl(fd, PPPIOCSMAXCID, (caddr_t) &maxcid) < 0) {
277         syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
278         return 0;
279     }
280     return 1;
281 }
282
283 /*
284  * sifup - Config the interface up and enable IP packets to pass.
285  */
286 int
287 sifup(u)
288 {
289     struct ifreq ifr;
290     u_int x;
291
292     strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
293     if (ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) < 0) {
294         syslog(LOG_ERR, "ioctl (SIOCGIFFLAGS): %m");
295         return 0;
296     }
297     ifr.ifr_flags |= IFF_UP;
298     if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) < 0) {
299         syslog(LOG_ERR, "ioctl(SIOCSIFFLAGS): %m");
300         return 0;
301     }
302     if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
303         syslog(LOG_ERR, "ioctl (PPPIOCGFLAGS): %m");
304         return 0;
305     }
306     x |= SC_ENABLE_IP;
307     if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0) {
308         syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
309         return 0;
310     }
311     return 1;
312 }
313
314 /*
315  * sifdown - Config the interface down and disable IP.
316  */
317 int
318 sifdown(u)
319 {
320     struct ifreq ifr;
321     u_int x;
322     int rv;
323
324     rv = 1;
325     if (ioctl(fd, PPPIOCGFLAGS, (caddr_t) &x) < 0) {
326         syslog(LOG_ERR, "ioctl (PPPIOCGFLAGS): %m");
327         rv = 0;
328     } else {
329         x &= ~SC_ENABLE_IP;
330         if (ioctl(fd, PPPIOCSFLAGS, (caddr_t) &x) < 0) {
331             syslog(LOG_ERR, "ioctl(PPPIOCSFLAGS): %m");
332             rv = 0;
333         }
334     }
335     strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
336     if (ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) < 0) {
337         syslog(LOG_ERR, "ioctl (SIOCGIFFLAGS): %m");
338         rv = 0;
339     } else {
340         ifr.ifr_flags &= ~IFF_UP;
341         if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) < 0) {
342             syslog(LOG_ERR, "ioctl(SIOCSIFFLAGS): %m");
343             rv = 0;
344         }
345     }
346     return rv;
347 }
348
349 /*
350  * SET_SA_FAMILY - set the sa_family field of a struct sockaddr,
351  * if it exists.
352  */
353 #define SET_SA_FAMILY(addr, family)             \
354     BZERO((char *) &(addr), sizeof(addr));      \
355     addr.sa_family = (family); 
356
357 /*
358  * sifaddr - Config the interface IP addresses and netmask.
359  */
360 int
361 sifaddr(u, o, h, m)
362 {
363     int ret;
364     struct ifreq ifr;
365
366     ret = 1;
367     strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
368     SET_SA_FAMILY(ifr.ifr_addr, AF_INET);
369     ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr = o;
370     if (ioctl(s, SIOCSIFADDR, (caddr_t) &ifr) < 0) {
371         syslog(LOG_ERR, "ioctl(SIOCSIFADDR): %m");
372         ret = 0;
373     }
374     ((struct sockaddr_in *) &ifr.ifr_dstaddr)->sin_addr.s_addr = h;
375     if (ioctl(s, SIOCSIFDSTADDR, (caddr_t) &ifr) < 0) {
376         syslog(LOG_ERR, "ioctl(SIOCSIFDSTADDR): %m");
377         ret = 0;
378     }
379     if (m != 0) {
380         ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr = m;
381         syslog(LOG_INFO, "Setting interface mask to %s\n", ip_ntoa(m));
382         if (ioctl(s, SIOCSIFNETMASK, (caddr_t) &ifr) < 0) {
383             syslog(LOG_ERR, "ioctl(SIOCSIFNETMASK): %m");
384             ret = 0;
385         }
386     }
387     return ret;
388 }
389
390 /*
391  * cifaddr - Clear the interface IP addresses, and delete routes
392  * through the interface if possible.
393  */
394 int
395 cifaddr(u, o, h)
396 {
397     struct rtentry rt;
398
399     SET_SA_FAMILY(rt.rt_dst, AF_INET);
400     ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr = h;
401     SET_SA_FAMILY(rt.rt_gateway, AF_INET);
402     ((struct sockaddr_in *) &rt.rt_gateway)->sin_addr.s_addr = o;
403     rt.rt_flags = RTF_HOST;
404     if (ioctl(s, SIOCDELRT, (caddr_t) &rt) < 0) {
405         syslog(LOG_ERR, "ioctl(SIOCDELRT): %m");
406         return 0;
407     }
408     return 1;
409 }
410
411 /*
412  * sifdefaultroute - assign a default route through the address given.
413  */
414 int
415 sifdefaultroute(u, g)
416 {
417     struct rtentry rt;
418
419     SET_SA_FAMILY(rt.rt_dst, AF_INET);
420     SET_SA_FAMILY(rt.rt_gateway, AF_INET);
421     ((struct sockaddr_in *) &rt.rt_gateway)->sin_addr.s_addr = g;
422     rt.rt_flags = RTF_GATEWAY;
423     if (ioctl(s, SIOCADDRT, &rt) < 0) {
424         syslog(LOG_ERR, "default route ioctl(SIOCADDRT): %m");
425         return 0;
426     }
427     return 1;
428 }
429
430 /*
431  * cifdefaultroute - delete a default route through the address given.
432  */
433 int
434 cifdefaultroute(u, g)
435 {
436     struct rtentry rt;
437
438     SET_SA_FAMILY(rt.rt_dst, AF_INET);
439     SET_SA_FAMILY(rt.rt_gateway, AF_INET);
440     ((struct sockaddr_in *) &rt.rt_gateway)->sin_addr.s_addr = g;
441     rt.rt_flags = RTF_GATEWAY;
442     if (ioctl(s, SIOCDELRT, &rt) < 0)
443         syslog(LOG_WARNING, "default route ioctl(SIOCDELRT): %m");
444 }
445
446 /*
447  * sifproxyarp - Make a proxy ARP entry for the peer.
448  */
449 int
450 sifproxyarp(unit, hisaddr)
451     int unit;
452     u_long hisaddr;
453 {
454     struct arpreq arpreq;
455
456     BZERO(&arpreq, sizeof(arpreq));
457
458     /*
459      * Get the hardware address of an interface on the same subnet
460      * as our local address.
461      */
462     if (!get_ether_addr(hisaddr, &arpreq.arp_ha)) {
463         syslog(LOG_ERR, "Cannot determine ethernet address for proxy ARP");
464         return 0;
465     }
466
467     SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
468     ((struct sockaddr_in *) &arpreq.arp_pa)->sin_addr.s_addr = hisaddr;
469     arpreq.arp_flags = ATF_PERM | ATF_PUBL;
470     if (ioctl(s, SIOCSARP, (caddr_t)&arpreq) < 0) {
471         syslog(LOG_ERR, "ioctl(SIOCSARP): %m");
472         return 0;
473     }
474
475     return 1;
476 }
477
478 /*
479  * cifproxyarp - Delete the proxy ARP entry for the peer.
480  */
481 int
482 cifproxyarp(unit, hisaddr)
483     int unit;
484     u_long hisaddr;
485 {
486     struct arpreq arpreq;
487
488     BZERO(&arpreq, sizeof(arpreq));
489     SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
490     ((struct sockaddr_in *) &arpreq.arp_pa)->sin_addr.s_addr = hisaddr;
491     if (ioctl(s, SIOCDARP, (caddr_t)&arpreq) < 0) {
492         syslog(LOG_WARNING, "ioctl(SIOCDARP): %m");
493         return 0;
494     }
495     return 1;
496 }
497
498 /*
499  * get_ether_addr - get the hardware address of an interface on the
500  * the same subnet as ipaddr.
501  */
502 #define MAX_IFS         32
503
504 int
505 get_ether_addr(ipaddr, hwaddr)
506     u_long ipaddr;
507     struct sockaddr *hwaddr;
508 {
509     struct ifreq *ifr, *ifend, *ifp;
510     u_long ina, mask;
511     struct sockaddr_dl *dla;
512     struct ifreq ifreq;
513     struct ifconf ifc;
514     struct ifreq ifs[MAX_IFS];
515
516     ifc.ifc_len = sizeof(ifs);
517     ifc.ifc_req = ifs;
518     if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
519         syslog(LOG_ERR, "ioctl(SIOCGIFCONF): %m");
520         return 0;
521     }
522
523     /*
524      * Scan through looking for an interface with an Internet
525      * address on the same subnet as `ipaddr'.
526      */
527     ifend = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
528     for (ifr = ifc.ifc_req; ifr < ifend; ) {
529         if (ifr->ifr_addr.sa_family == AF_INET) {
530             ina = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr.s_addr;
531             strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));
532             /*
533              * Check that the interface is up, and not point-to-point
534              * or loopback.
535              */
536             if (ioctl(s, SIOCGIFFLAGS, &ifreq) < 0)
537                 continue;
538             if ((ifreq.ifr_flags &
539                  (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|IFF_LOOPBACK|IFF_NOARP))
540                  != (IFF_UP|IFF_BROADCAST))
541                 continue;
542             /*
543              * Get its netmask and check that it's on the right subnet.
544              */
545             if (ioctl(s, SIOCGIFNETMASK, &ifreq) < 0)
546                 continue;
547             mask = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr.s_addr;
548             if ((ipaddr & mask) != (ina & mask))
549                 continue;
550
551             break;
552         }
553         ifr = (struct ifreq *) ((char *)&ifr->ifr_addr + sizeof(struct sockaddr)
554 );
555     }
556
557     if (ifr >= ifend)
558         return 0;
559     syslog(LOG_DEBUG, "found interface %s for proxy arp", ifr->ifr_name);
560
561     /*
562      * Now scan through again looking for a link-level address
563      * for this interface.
564      */
565     ifp = ifr;
566     for (ifr = ifc.ifc_req; ifr < ifend; ) {
567         if (strcmp(ifp->ifr_name, ifr->ifr_name) == 0
568             && ifr->ifr_addr.sa_family == AF_DLI) {
569 /*          && ifr->ifr_addr.sa_family == AF_LINK) { Per! Kolla !!! ROHACK */
570             /*
571              * Found the link-level address - copy it out
572              */
573             dla = (struct sockaddr_dl *)&ifr->ifr_addr;
574             hwaddr->sa_family = AF_UNSPEC;
575             BCOPY(dla, hwaddr->sa_data, sizeof(hwaddr->sa_data));
576             return 1;
577         }
578         ifr = (struct ifreq *) ((char *)&ifr->ifr_addr + sizeof(struct sockaddr)
579 );
580     }
581
582     return 0;
583 }
584
585
586 /*
587  * ppp_available - check whether the system has any ppp interfaces
588  * (in fact we check whether we can do an ioctl on ppp0).
589  */
590
591 int
592 ppp_available()
593 {
594     int s, ok;
595     struct ifreq ifr;
596
597     if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
598         return 1;               /* can't tell - maybe we're not root */
599
600     strncpy(ifr.ifr_name, "ppp0", sizeof (ifr.ifr_name));
601     ok = ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) >= 0;
602     close(s);
603
604     return ok;
605 }
606
607
608 /*
609   Seems like strdup() is not part of string package in Ultrix.
610   If I understood the man-page on the sun this should work.
611
612   Robert Olsson
613 */
614
615 char *strdup( in ) char *in;
616 {
617   char* dup;
618   if(! (dup = (char *) malloc( strlen( in ) +1 ))) return NULL;
619   (void) strcpy( dup, in );
620   return dup;
621 }
622
623 /*
624  * This logwtmp() implementation is subject to the following copyright:
625  *
626  * Copyright (c) 1988 The Regents of the University of California.
627  * All rights reserved.
628  *
629  * Redistribution and use in source and binary forms are permitted
630  * provided that the above copyright notice and this paragraph are
631  * duplicated in all such forms and that any documentation,
632  * advertising materials, and other materials related to such
633  * distribution and use acknowledge that the software was developed
634  * by the University of California, Berkeley.  The name of the
635  * University may not be used to endorse or promote products derived
636  * from this software without specific prior written permission.
637  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
638  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
639  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
640  */
641
642 #define WTMPFILE        "/usr/adm/wtmp"
643
644 int
645 logwtmp(line, name, host)
646     char *line, *name, *host;
647 {
648     int fd;
649     struct stat buf;
650     struct utmp ut;
651
652     if ((fd = open(WTMPFILE, O_WRONLY|O_APPEND, 0)) < 0)
653         return;
654     if (!fstat(fd, &buf)) {
655         (void)strncpy(ut.ut_line, line, sizeof(ut.ut_line));
656         (void)strncpy(ut.ut_name, name, sizeof(ut.ut_name));
657         (void)strncpy(ut.ut_host, host, sizeof(ut.ut_host));
658         (void)time(&ut.ut_time);
659         if (write(fd, (char *)&ut, sizeof(struct utmp)) != sizeof(struct utmp))
660             (void)ftruncate(fd, buf.st_size);
661     }
662     close(fd);
663 }