| /* | |
| * Copyright 2003 Digi International (www.digi.com) | |
| * Scott H Kilau <Scott_Kilau at digi dot com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the | |
| * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
| * PURPOSE. See the GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| * $Id: downld.c,v 1.6 2009/01/14 14:10:54 markh Exp $ | |
| */ | |
| /* | |
| ** downld.c | |
| ** | |
| ** This is the daemon that sends the fep, bios, and concentrator images | |
| ** from user space to the driver. | |
| ** BUGS: | |
| ** If the file changes in the middle of the download, you probably | |
| ** will get what you deserve. | |
| ** | |
| */ | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <fcntl.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <sys/errno.h> | |
| #include "dgap_types.h" | |
| #include "digi.h" | |
| #include "dgap_fep5.h" | |
| #include "dgap_downld.h" | |
| #include <string.h> | |
| #include <malloc.h> | |
| #include <stddef.h> | |
| #include <unistd.h> | |
| char *pgm; | |
| void myperror(); | |
| /* | |
| ** This structure is used to keep track of the diferent images available | |
| ** to give to the driver. It is arranged so that the things that are | |
| ** constants or that have defaults are first inthe strucutre to simplify | |
| ** the table of initializers. | |
| */ | |
| struct image_info { | |
| short type; /* bios, fep, conc */ | |
| short family; /* boards this applies to */ | |
| short subtype; /* subtype */ | |
| int len; /* size of image */ | |
| char *image; /* ioctl struct + image */ | |
| char *name; | |
| char *fname; /* filename of binary (i.e. "asfep.bin") */ | |
| char *pathname; /* pathname to this binary ("/etc/dgap/xrfep.bin"); */ | |
| time_t mtime; /* Last modification time */ | |
| }; | |
| #define IBIOS 0 | |
| #define IFEP 1 | |
| #define ICONC 2 | |
| #define ICONFIG 3 | |
| #define IBAD 4 | |
| #define DEFAULT_LOC "/lib/firmware/dgap/" | |
| struct image_info *image_list; | |
| int nimages, count; | |
| struct image_info images[] = { | |
| {IBIOS, T_EPC, SUBTYPE, 0, NULL, "EPC/X", "fxbios.bin", DEFAULT_LOC "fxbios.bin", 0 }, | |
| {IFEP, T_EPC, SUBTYPE, 0, NULL, "EPC/X", "fxfep.bin", DEFAULT_LOC "fxfep.bin", 0 }, | |
| {ICONC, T_EPC, SUBTYPE, 0, NULL, "EPC/X", "fxcon.bin", DEFAULT_LOC "fxcon.bin", 0 }, | |
| {IBIOS, T_CX, SUBTYPE, 0, NULL, "C/X", "cxbios.bin", DEFAULT_LOC "cxbios.bin", 0 }, | |
| {IFEP, T_CX, SUBTYPE, 0, NULL, "C/X", "cxhost.bin", DEFAULT_LOC "cxhost.bin", 0 }, | |
| {IBIOS, T_CX, T_PCIBUS, 0, NULL, "C/X PCI", "cxpbios.bin", DEFAULT_LOC "cxpbios.bin", 0 }, | |
| {IFEP, T_CX, T_PCIBUS, 0, NULL, "C/X PCI", "cxpfep.bin", DEFAULT_LOC "cxpfep.bin", 0 }, | |
| {ICONC, T_CX, SUBTYPE, 0, NULL, "C/X", "cxcon.bin", DEFAULT_LOC "cxcon.bin", 0 }, | |
| {ICONC, T_CX, SUBTYPE, 0, NULL, "C/X", "ibmcxcon.bin", DEFAULT_LOC "ibmcxcon.bin", 0 }, | |
| {ICONC, T_CX, SUBTYPE, 0, NULL, "C/X", "ibmencon.bin", DEFAULT_LOC "ibmencon.bin", 0 }, | |
| {IBIOS, FAMILY, T_PCXR, 0, NULL, "PCXR", "xrbios.bin", DEFAULT_LOC "xrbios.bin", 0 }, | |
| {IFEP, FAMILY, T_PCXR, 0, NULL, "PCXR", "xrfep.bin", DEFAULT_LOC "xrfep.bin", 0 }, | |
| {IBIOS, T_PCLITE, SUBTYPE, 0, NULL, "X/em", "sxbios.bin", DEFAULT_LOC "sxbios.bin", 0 }, | |
| {IFEP, T_PCLITE, SUBTYPE, 0, NULL, "X/em", "sxfep.bin", DEFAULT_LOC "sxfep.bin", 0 }, | |
| {IBIOS, T_EPC, T_PCIBUS, 0, NULL, "PCI", "pcibios.bin", DEFAULT_LOC "pcibios.bin", 0 }, | |
| {IFEP, T_EPC, T_PCIBUS, 0, NULL, "PCI", "pcifep.bin", DEFAULT_LOC "pcifep.bin", 0 }, | |
| {ICONFIG, 0, 0, 0, NULL, NULL, "dgap.conf", "/etc/dgap.conf", 0 }, | |
| /* IBAD/NULL entry indicating end-of-table */ | |
| {IBAD, 0, 0, 0, NULL, NULL, NULL, NULL, 0 } | |
| } ; | |
| int errorprint = 1; | |
| int nodldprint = 1; | |
| int debugflag; | |
| int fd; | |
| struct downld_t *ip; /* Image pointer in current image */ | |
| struct downld_t *dp; /* conc. download */ | |
| /* | |
| * The same for either the FEP or the BIOS. | |
| * Append the downldio header, issue the ioctl, then free | |
| * the buffer. Not horribly CPU efficient, but quite RAM efficient. | |
| */ | |
| void squirt(int req_type, int bdid, struct image_info *ii) | |
| { | |
| struct downldio *dliop; | |
| int size_buf; | |
| int sfd; | |
| struct stat sb; | |
| /* | |
| * If this binary comes from a file, stat it to see how | |
| * large it is. Yes, we intentionally do this each | |
| * time for the binary may change between loads. | |
| */ | |
| if (ii->pathname) { | |
| sfd = open(ii->pathname, O_RDONLY); | |
| if (sfd < 0 ) { | |
| myperror(ii->pathname); | |
| goto squirt_end; | |
| } | |
| if (fstat(sfd, &sb) == -1 ) { | |
| myperror(ii->pathname); | |
| goto squirt_end; | |
| } | |
| ii->len = sb.st_size ; | |
| } | |
| size_buf = ii->len + sizeof(struct downldio); | |
| /* | |
| * This buffer will be freed at the end of this function. It is | |
| * not resilient and should be around only long enough for the d/l | |
| * to happen. | |
| */ | |
| dliop = (struct downldio *) malloc(size_buf); | |
| if (dliop == NULL) { | |
| fprintf(stderr,"%s: can't get %d bytes of memory; aborting\n", | |
| pgm, size_buf); | |
| exit (1); | |
| } | |
| /* Now, stick the image in fepimage. This can come from either | |
| * the compiled-in image or from the filesystem. | |
| */ | |
| if (ii->pathname) | |
| read(sfd, dliop->image.fi.fepimage, ii->len); | |
| else | |
| memcpy(dliop ->image.fi.fepimage, ii->image, ii->len); | |
| dliop->req_type = req_type; | |
| dliop->bdid = bdid; | |
| dliop->image.fi.len = ii->len; | |
| if (debugflag) | |
| printf("sending %d bytes of %s %s from %s\n", | |
| ii->len, | |
| (ii->type == IFEP) ? "FEP" : (ii->type == IBIOS) ? "BIOS" : "CONFIG", | |
| ii->name ? ii->name : "", | |
| (ii->pathname) ? ii->pathname : "internal image" ); | |
| if (ioctl(fd, DIGI_DLREQ_SET, (char *) dliop) == -1) { | |
| if(errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n",pgm); | |
| errorprint = 0; | |
| } | |
| sleep(2); | |
| } | |
| squirt_end: | |
| if (ii->pathname) { | |
| close(sfd); | |
| } | |
| free(dliop); | |
| } | |
| /* | |
| * See if we need to reload the download image in core | |
| * | |
| */ | |
| void consider_file_rescan(struct image_info *ii) | |
| { | |
| int sfd ; | |
| int len ; | |
| struct stat sb; | |
| /* This operation only makes sense when we're working from a file */ | |
| if (ii->pathname) { | |
| sfd = open (ii->pathname, O_RDONLY) ; | |
| if (sfd < 0 ) { | |
| myperror(ii->pathname); | |
| exit(1) ; | |
| } | |
| if( fstat(sfd,&sb) == -1 ) { | |
| myperror(ii->pathname); | |
| exit(1); | |
| } | |
| /* If the file hasn't changed since we last did this, | |
| * and we have not done a free() on the image, bail | |
| */ | |
| if (ii->image && (sb.st_mtime == ii->mtime)) | |
| goto end_rescan; | |
| ii->len = len = sb.st_size ; | |
| /* Record the timestamp of the file */ | |
| ii->mtime = sb.st_mtime; | |
| /* image should be NULL unless there is an image malloced | |
| * in already. Before we malloc again, make sure we don't | |
| * have a memory leak. | |
| */ | |
| if ( ii->image ) { | |
| free( ii->image ); | |
| /* ii->image = NULL; */ /* not necessary */ | |
| } | |
| /* This image will be kept only long enough for the | |
| * download to happen. After sending the last block, | |
| * it will be freed | |
| */ | |
| ii->image = malloc(len) ; | |
| if (ii->image == NULL) { | |
| fprintf(stderr, | |
| "%s: can't get %d bytes of memory; aborting\n", | |
| pgm, len); | |
| exit (1); | |
| } | |
| if (read(sfd, ii->image, len) < len) { | |
| fprintf(stderr,"%s: read error on %s; aborting\n", | |
| pgm, ii->pathname); | |
| exit (1); | |
| } | |
| end_rescan: | |
| close(sfd); | |
| } | |
| } | |
| /* | |
| * Scan for images to match the driver requests | |
| */ | |
| struct image_info * find_conc_image() | |
| { | |
| int x ; | |
| struct image_info *i = NULL ; | |
| for ( x = 0; x < nimages; x++ ) { | |
| i=&image_list[x]; | |
| if(i->type != ICONC) | |
| continue; | |
| consider_file_rescan(i) ; | |
| ip = (struct downld_t *) image_list[x].image; | |
| if (ip == NULL) continue; | |
| /* | |
| * When I removed Clusterport, I kept only the code that I | |
| * was SURE wasn't ClusterPort. We may not need the next two | |
| * lines of code. | |
| */ | |
| if ((dp->dl_type != 'P' ) && ( ip->dl_srev == dp->dl_srev )) | |
| return i; | |
| } | |
| return NULL ; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| struct downldio dlio; | |
| int offset, bsize; | |
| int x; | |
| char *down, *image, *fname; | |
| struct image_info *ii; | |
| pgm = argv[0]; | |
| dp = &dlio.image.dl; /* conc. download */ | |
| while((argc > 2) && !strcmp(argv[1],"-d")) { | |
| debugflag++ ; | |
| argc-- ; | |
| argv++ ; | |
| } | |
| if(argc < 2) { | |
| fprintf(stderr, | |
| "usage: %s download-device [image-file] ...\n", | |
| pgm); | |
| exit(1); | |
| } | |
| /* | |
| * Daemonize, unless debugging is turned on. | |
| */ | |
| if (debugflag == 0) { | |
| switch (fork()) | |
| { | |
| case 0: | |
| break; | |
| case -1: | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| setsid(); | |
| /* | |
| * The child no longer needs "stdin", "stdout", or "stderr", | |
| * and should not block processes waiting for them to close. | |
| */ | |
| fclose(stdin); | |
| fclose(stdout); | |
| fclose(stderr); | |
| } | |
| while (1) { | |
| if( (fd = open(argv[1], O_RDWR)) == -1 ) { | |
| sleep(1); | |
| } | |
| else | |
| break; | |
| } | |
| /* | |
| ** create a list of images to search through when trying to match | |
| ** requests from the driver. Put images from the command line in | |
| ** the list before built in images so that the command line images | |
| ** can override the built in ones. | |
| */ | |
| /* allocate space for the list */ | |
| nimages = argc - 2; | |
| /* count the number of default list entries */ | |
| for (count = 0; images[count].type != IBAD; ++count) ; | |
| nimages += count; | |
| /* Really should just remove the variable "image_list".... robertl */ | |
| image_list = images ; | |
| /* get the images from the command line */ | |
| for(x = 2; x < argc; x++) { | |
| int xx; | |
| /* | |
| * strip off any leading path information for | |
| * determining file type | |
| */ | |
| if( (fname = strrchr(argv[x],'/')) == NULL) | |
| fname = argv[x]; | |
| else | |
| fname++; /* skip the slash */ | |
| for (xx = 0; xx < count; xx++) { | |
| if (strcmp(fname, images[xx].fname) == 0 ) { | |
| images[xx].pathname = argv[x]; | |
| /* image should be NULL until */ | |
| /* space is malloced */ | |
| images[xx].image = NULL ; | |
| } | |
| } | |
| } | |
| sleep(3); | |
| /* | |
| ** Endless loop: get a request from the fep, and service that request. | |
| */ | |
| for(;;) { | |
| /* get the request */ | |
| if (debugflag) | |
| printf("b4 get ioctl..."); | |
| if (ioctl(fd,DIGI_DLREQ_GET, &dlio) == -1 ) { | |
| if (errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n", | |
| pgm); | |
| errorprint = 0; | |
| } | |
| sleep(2); | |
| } else { | |
| if (debugflag) | |
| printf("dlio.req_type is %d bd %d\n", | |
| dlio.req_type,dlio.bdid); | |
| switch(dlio.req_type) { | |
| case DLREQ_BIOS: | |
| /* | |
| ** find the bios image for this type | |
| */ | |
| for ( x = 0; x < nimages; x++ ) { | |
| if(image_list[x].type != IBIOS) | |
| continue; | |
| if ((dlio.image.fi.type & FAMILY) == | |
| image_list[x].family) { | |
| if ( image_list[x].family == T_CX ) { | |
| if ((dlio.image.fi.type & BUSTYPE) | |
| == T_PCIBUS ) { | |
| if ( image_list[x].subtype | |
| == T_PCIBUS ) | |
| break; | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| else if ( image_list[x].family == T_EPC ) { | |
| /* If subtype of image is T_PCIBUS, it is */ | |
| /* a PCI EPC image, so the board must */ | |
| /* have bus type T_PCIBUS to match */ | |
| if ((dlio.image.fi.type & BUSTYPE) | |
| == T_PCIBUS ) { | |
| if ( image_list[x].subtype | |
| == T_PCIBUS ) | |
| break; | |
| } | |
| else { | |
| /* NON PCI EPC doesn't use PCI image */ | |
| if ( image_list[x].subtype | |
| != T_PCIBUS ) | |
| break; | |
| } | |
| } | |
| else | |
| break; | |
| } | |
| else if ((dlio.image.fi.type & SUBTYPE) == image_list[x].subtype) { | |
| /* PCXR board will break out of the loop here */ | |
| if ( image_list[x].subtype == T_PCXR ) { | |
| break; | |
| } | |
| } | |
| } | |
| if ( x >= nimages) { | |
| /* | |
| ** no valid images exist | |
| */ | |
| if(nodldprint) { | |
| fprintf(stderr, | |
| "%s: cannot find correct BIOS image\n", | |
| pgm); | |
| nodldprint = 0; | |
| } | |
| dlio.image.fi.type = -1; | |
| if (ioctl(fd, DIGI_DLREQ_SET, &dlio) == -1) { | |
| if (errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n", | |
| pgm); | |
| errorprint = 0; | |
| } | |
| sleep(2); | |
| } | |
| break; | |
| } | |
| squirt(dlio.req_type, dlio.bdid, &image_list[x]); | |
| break ; | |
| case DLREQ_FEP: | |
| /* | |
| ** find the fep image for this type | |
| */ | |
| for ( x = 0; x < nimages; x++ ) { | |
| if(image_list[x].type != IFEP) | |
| continue; | |
| if( (dlio.image.fi.type & FAMILY) == | |
| image_list[x].family ) { | |
| if ( image_list[x].family == T_CX ) { | |
| /* C/X PCI board */ | |
| if ((dlio.image.fi.type & BUSTYPE) | |
| == T_PCIBUS ) { | |
| if ( image_list[x].subtype | |
| == T_PCIBUS ) | |
| break; | |
| } | |
| else { | |
| /* Regular CX */ | |
| break; | |
| } | |
| } | |
| else if ( image_list[x].family == T_EPC ) { | |
| /* If subtype of image is T_PCIBUS, it is */ | |
| /* a PCI EPC image, so the board must */ | |
| /* have bus type T_PCIBUS to match */ | |
| if ((dlio.image.fi.type & BUSTYPE) | |
| == T_PCIBUS ) { | |
| if ( image_list[x].subtype | |
| == T_PCIBUS ) | |
| break; | |
| } | |
| else { | |
| /* NON PCI EPC doesn't use PCI image */ | |
| if ( image_list[x].subtype | |
| != T_PCIBUS ) | |
| break; | |
| } | |
| } | |
| else | |
| break; | |
| } | |
| else if ((dlio.image.fi.type & SUBTYPE) == image_list[x].subtype) { | |
| /* PCXR board will break out of the loop here */ | |
| if ( image_list[x].subtype == T_PCXR ) { | |
| break; | |
| } | |
| } | |
| } | |
| if ( x >= nimages) { | |
| /* | |
| ** no valid images exist | |
| */ | |
| if(nodldprint) { | |
| fprintf(stderr, | |
| "%s: cannot find correct FEP image\n", | |
| pgm); | |
| nodldprint = 0; | |
| } | |
| dlio.image.fi.type=-1; | |
| if( ioctl(fd,DIGI_DLREQ_SET,&dlio) == -1 ) { | |
| if(errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n", | |
| pgm); | |
| errorprint=0; | |
| } | |
| sleep(2); | |
| } | |
| break; | |
| } | |
| squirt(dlio.req_type, dlio.bdid, &image_list[x]); | |
| break; | |
| case DLREQ_DEVCREATE: | |
| { | |
| char string[1024]; | |
| #if 0 | |
| sprintf(string, "%s /proc/dgap/%d/mknod", DEFSHELL, dlio.bdid); | |
| #endif | |
| sprintf(string, "%s /usr/sbin/dgap_updatedevs %d", DEFSHELL, dlio.bdid); | |
| system(string); | |
| if (debugflag) | |
| printf("Created Devices.\n"); | |
| if (ioctl(fd, DIGI_DLREQ_SET, &dlio) == -1 ) { | |
| if(errorprint) { | |
| fprintf(stderr, "%s: warning - DEVCREATE ioctl failed\n",pgm); | |
| errorprint = 0; | |
| } | |
| sleep(2); | |
| } | |
| if (debugflag) | |
| printf("After ioctl set - Created Device.\n"); | |
| } | |
| break; | |
| case DLREQ_CONFIG: | |
| for ( x = 0; x < nimages; x++ ) { | |
| if(image_list[x].type != ICONFIG) | |
| continue; | |
| else | |
| break; | |
| } | |
| if ( x >= nimages) { | |
| /* | |
| ** no valid images exist | |
| */ | |
| if(nodldprint) { | |
| fprintf(stderr, | |
| "%s: cannot find correct CONFIG image\n", | |
| pgm); | |
| nodldprint = 0; | |
| } | |
| dlio.image.fi.type=-1; | |
| if (ioctl(fd, DIGI_DLREQ_SET, &dlio) == -1 ) { | |
| if(errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n", | |
| pgm); | |
| errorprint=0; | |
| } | |
| sleep(2); | |
| } | |
| break; | |
| } | |
| squirt(dlio.req_type, dlio.bdid, &image_list[x]); | |
| break; | |
| case DLREQ_CONC: | |
| /* | |
| ** find the image needed for this download | |
| */ | |
| if ( dp->dl_seq == 0 ) { | |
| /* | |
| ** find image for hardware rev range | |
| */ | |
| for ( x = 0; x < nimages; x++ ) { | |
| ii=&image_list[x]; | |
| if(image_list[x].type != ICONC) | |
| continue; | |
| consider_file_rescan(ii) ; | |
| ip = (struct downld_t *) image_list[x].image; | |
| if (ip == NULL) continue; | |
| /* | |
| * When I removed Clusterport, I kept only the | |
| * code that I was SURE wasn't ClusterPort. | |
| * We may not need the next four lines of code. | |
| */ | |
| if ((dp->dl_type != 'P' ) && | |
| (ip->dl_lrev <= dp->dl_lrev ) && | |
| ( dp->dl_lrev <= ip->dl_hrev)) | |
| break; | |
| } | |
| if ( x >= nimages ) { | |
| /* | |
| ** No valid images exist | |
| */ | |
| if(nodldprint) { | |
| fprintf(stderr, | |
| "%s: cannot find correct download image %d\n", | |
| pgm, dp->dl_lrev); | |
| nodldprint=0; | |
| } | |
| continue; | |
| } | |
| } else { | |
| /* | |
| ** find image version required | |
| */ | |
| if ((ii = find_conc_image()) == NULL ) { | |
| /* | |
| ** No valid images exist | |
| */ | |
| fprintf(stderr, | |
| "%s: can't find rest of download image??\n", | |
| pgm); | |
| continue; | |
| } | |
| } | |
| /* | |
| ** download block of image | |
| */ | |
| offset = 1024 * dp->dl_seq; | |
| /* | |
| ** test if block requested within image | |
| */ | |
| if ( offset < ii->len ) { | |
| /* | |
| ** if it is, determine block size, set segment, | |
| ** set size, set pointers, and copy block | |
| */ | |
| if (( bsize = ii->len - offset ) > 1024 ) | |
| bsize = 1024; | |
| /* | |
| ** copy image version info to download area | |
| */ | |
| dp->dl_srev = ip->dl_srev; | |
| dp->dl_lrev = ip->dl_lrev; | |
| dp->dl_hrev = ip->dl_hrev; | |
| dp->dl_seg = (64 * dp->dl_seq) + ip->dl_seg; | |
| dp->dl_size = bsize; | |
| down = (char *)&dp->dl_data[0]; | |
| image = (char *)((char *)ip + offset); | |
| memcpy(down, image, bsize); | |
| } | |
| else { | |
| /* | |
| ** Image has been downloaded, set segment and | |
| ** size to indicate no more blocks | |
| */ | |
| dp->dl_seg = ip->dl_seg; | |
| dp->dl_size = 0; | |
| /* Now, we can release the concentrator */ | |
| /* image from memory if we're running */ | |
| /* from filesystem images */ | |
| if (ii->pathname) | |
| if (ii->image) { | |
| free(ii->image); | |
| ii->image = NULL ; | |
| } | |
| } | |
| if (debugflag) | |
| printf( | |
| "sending conc dl section %d to %s from %s\n", | |
| dp->dl_seq, ii->name, | |
| ii->pathname ? ii->pathname : "Internal Image"); | |
| if (ioctl(fd, DIGI_DLREQ_SET, &dlio) == -1 ) { | |
| if (errorprint) { | |
| fprintf(stderr, | |
| "%s: warning - download ioctl failed\n", | |
| pgm); | |
| errorprint=0; | |
| } | |
| sleep(2); | |
| } | |
| break; | |
| } /* switch */ | |
| } | |
| if (debugflag > 1) { | |
| printf("pausing: "); fflush(stdout); | |
| fflush(stdin); | |
| while(getchar() != '\n'); | |
| printf("continuing\n"); | |
| } | |
| } | |
| } | |
| /* | |
| ** myperror() | |
| ** | |
| ** Same as normal perror(), but places the program name at the begining | |
| ** of the message. | |
| */ | |
| void myperror(char *s) | |
| { | |
| fprintf(stderr,"%s: %s: %s.\n",pgm, s, strerror(errno)); | |
| } |