#include #include #include #include #include #include #include #include #include #undef getopt //#define DEBUG #define RENAME_ERR 1 #define MKDIR_ERR 2 #define FILE_ERR 4 #define MAX_DIRS 1 #define OURBUFSIZ 1024 #define OURLISTSIZ 4096 #define SPACE ' ' #define EOF (-1) #define MAILPROG "/usr/bin/Mail" #define ADMIN "johna" #ifdef WIN32 #include #include #include #define S_ISDIR(x) ((x) & _S_IFDIR) #define S_ISREG(x) ((x) & _S_IFREG) #define S_ISLNK(x) 0 #define STAT(a, b) stat(a, b) #define MKDIR(a, b) mkdir(a) #define STRNCMP(a,b,c) strnicmp(a,b,c) #define F_OK 0 struct dirent { char d_name[256]; }; struct DIR { int first; long handle; char dirname[OURBUFSIZ]; struct _finddata_t fft; }; #define SLASH '\\' // this is the destination filesystem name char *bkp = "X:\\"; // this is the intermediate file name char *btmp = "C:\\TEMP\\bkp_list.txt"; // this is the default exclusion file name char *efile = "C:\\BKP_EXCLUSIONS.txt"; #else #include #include #include #define MKDIR(a, b) mkdir(a, b) #define STAT(a, b) lstat(a, b) #define COPY_CMD "/bin/cp -dp" #define STRNCMP(a,b,c) strncmp(a,b,c) #define SLASH '/' // this is the destination filesystem name char *bkp = "/system.bkp"; // this is the intermediate file name char *btmp = "/tmp/bkp_list"; // this is the default exclusion file name char *efile = "/usr/local/lib/bkp_exclusions"; #endif typedef DIR * DIRP; struct NAMELIST { char *name; int namlen; struct NAMELIST *link; }; struct FSYSLIST { char *fsysname; int fnamelen; struct NAMELIST *dir_list; struct NAMELIST *fil_list; struct FSYSLIST *link; }; ///////////////////////////////////////////////////////////////////////////// int opterr = 1; int optind = 1; int optopt; char *optarg; int verbose = 0; int outfd = -1; int fcount = 0; int skipcount = 0; int copycount = 0; int maxlevel = 4; int bailout = 0; char *progname = NULL; dev_t filesys_dev = 0; DIRP filesys = NULL; struct FSYSLIST *fsysptr = NULL; struct NAMELIST *dir_excl_listp = NULL; struct NAMELIST *fil_excl_listp = NULL; FILE *exclusion_file = NULL; time_t last_bkp_time = 0; char filesystem_list[OURLISTSIZ]; #ifdef WIN32 extern int errno; #endif ///////////////////////////////////////////////////////////////////////////// void Msg(char *fmt, ...); void MailMsg(char *fmt, ...); void BuildNameLists(int argc, char *argv[]); char *GetFileSysName(); int Update(); int Rename(char *oldname, char *newname); void Unlink(char *path, struct stat *s); int ShuffleFiles(char level, char *newname, char *fname, char *ndp, char *digitp, int ret); int ShuffleLinks(char level, char *newname, char *fname, char *ndp, char *digitp, int ret); int Mkdir(char *path); void Permissions(char *dest, struct stat *s, int islink); void Copy(char *src, char *dest); int Excluded(char *path, struct NAMELIST *exclusion_list); int FileTreeWalk(char *path, DIRP d); int Getopt(int argc, char * argv[], char *opts); void *Malloc(unsigned size); int banned_file_extension(char *p); #ifdef WIN32 DIRP opendir(char *path); struct dirent *readdir(DIRP dir); int closedir(DIRP dir); #endif ///////////////////////////////////////////////////////////////////////////// void Usage() { Msg("USAGE: %s: [-v] [-d file] [-e file] [-g num] [-t file] filesys [filesys ...]", progname); Msg("\t-v log actions to stdout"); Msg("\t-d destination filesystem to use for backup [%s]",bkp); Msg("\t-e exclusion list file absolute path [%s]", efile); Msg("\t-g generations [%d]", maxlevel+1); Msg("\t-t temporary file absolute path [%s]", btmp); exit(-1); } /////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { int c; time_t now; char *fsysname; char bkp_date_name[OURBUFSIZ]; struct stat s; struct utimbuf u; if (progname = strrchr(argv[0], SLASH)) progname++; else progname = argv[0]; while ((c = Getopt(argc, argv, "d:e:g:t:v")) != EOF) { switch (c) { case 'd': bkp = optarg; if (STAT(bkp, &s) < 0) { MailMsg("Can't find destination %s! (%s)",bkp,strerror(errno)); } break; case 'e': exclusion_file = fopen(optarg, "r"); if (exclusion_file == NULL) { MailMsg("Can't open exclusion file %s! (%s)", exclusion_file, strerror(errno)); } break; case 'g': maxlevel = atoi(optarg) - 1; if ((maxlevel < 2) || (maxlevel > 9)) { MailMsg("Max generations (%d) must be between 2 and 10", atoi(optarg)); } break; case 't': btmp = optarg; break; case 'v': verbose += 1; break; default: Usage(); exit(1); } } if (exclusion_file == NULL) exclusion_file = fopen(efile, "r"); if (argc - optind) BuildNameLists(argc, argv); else Usage(); #ifndef WIN32 if (geteuid() != 0) { MailMsg("You must be 'root' to run this program!"); } #endif now = time(0); sprintf(bkp_date_name, "%s%cBACKUP_DATE", bkp, SLASH); if (STAT(bkp_date_name, &s) < 0) { MailMsg("Can't stat %s! (%s)", bkp_date_name, strerror(errno)); } last_bkp_time = s.st_mtime; outfd = open(btmp, O_RDWR|O_TRUNC|O_CREAT, 0644); if (outfd < 0) { MailMsg("Can't create intermediate file %s (%s)",btmp,strerror(errno)); } while (fsysname = GetFileSysName()) { if (STAT(fsysname, &s) < 0) { Msg("Can't stat %s! (%d)", fsysname, errno); continue; } filesys_dev = s.st_dev; if ((filesys = opendir(fsysname)) == NULL) { Msg("Can't open directory %s! (%d)", fsysname, errno); continue; } if (FileTreeWalk(fsysname, filesys) != 0) { MailMsg("FileTreeWalk failed"); } closedir(filesys); } lseek(outfd, 0, SEEK_SET); // rewind intermediate file if (Update() == 0) { // add files to backup volume u.actime = now; u.modtime = now; if (utime(bkp_date_name, &u) < 0) // set new bkp timestamp Msg("Can't update timestamp on %s (%d)", bkp_date_name, errno); } close(outfd); // finished with intermed file #ifndef DEBUG unlink(btmp); // destroy it #endif // if (verbose) Msg("%d files selected, %d files copied, %d files skipped.", fcount, copycount, skipcount); return 0; } ///////////////////////////////////////////////////////////////////////////// void MailMsg(char *fmt, ...) { va_list ap; char errmsg[OURBUFSIZ]; char mail[OURBUFSIZ]; va_start(ap, fmt); vsprintf(errmsg, fmt, ap); va_end(ap); Msg(errmsg); #ifndef WIN32 sprintf(mail, "echo '%s'| %s %s -s 'sysbkp failed!'\n", errmsg, MAILPROG, ADMIN); system(mail); #endif exit(1); } ///////////////////////////////////////////////////////////////////////////// void Msg(char *fmt, ...) { va_list ap; char errmsg[OURBUFSIZ]; va_start(ap, fmt); vsprintf(errmsg, fmt, ap); va_end(ap); fprintf(stderr, "%s\n", errmsg); } ///////////////////////////////////////////////////////////////////////////// void BuildNameLists(int argc, char *argv[]) { int i; char *p; char nambuf[OURBUFSIZ]; struct stat s; struct FSYSLIST *x, **y; struct NAMELIST *z, *l; // process backup filesystem/dir list y = &fsysptr; for (i=optind; ifsysname = argv[i]; x->fnamelen = strlen(argv[i]); x->dir_list = NULL; x->fil_list = NULL; x->link = NULL; *y = x; y = &x->link; } // process exclusion file/dir lists while (fgets(nambuf, OURBUFSIZ, exclusion_file)) { p = nambuf; while (*p && (*p != '\n')) // find newline; p++; *p = 0; // remove newline and terminate string p = nambuf; if (STAT(p, &s) < 0) { if (verbose > 1) Msg("can't stat %s -- ignoring.", p); continue; } // file filesys list x = fsysptr; while (x) { if (strncmp(x->fsysname, nambuf, x->fnamelen) == 0) { z = (struct NAMELIST *)Malloc(sizeof(struct NAMELIST)); z->link = NULL; z->namlen = strlen(nambuf) + 1; z->name = (char *)Malloc(z->namlen); memcpy(z->name, nambuf, z->namlen); if (S_ISDIR(s.st_mode)) { // add to directory list if (x->dir_list == NULL) x->dir_list = z; else { l = x->dir_list; while (l->link) l = l->link; l->link = z; } } else { // add to file list if (x->fil_list == NULL) x->fil_list = z; else { l = x->fil_list; while (l->link) l = l->link; l->link = z; } } } x = x->link; } } fclose(exclusion_file); } ///////////////////////////////////////////////////////////////////////////// char *GetFileSysName() { char *p; if (fsysptr) { dir_excl_listp = fsysptr->dir_list; fil_excl_listp = fsysptr->fil_list; p = fsysptr->fsysname; fsysptr = fsysptr->link; return p; } else return NULL; // end of list } ///////////////////////////////////////////////////////////////////////////// int ShuffleFiles(char level, char *newname, char *fname, char *ndp, char *digitp, int ret) { if (level == maxlevel) { // maximum generations? -- unlink file *ndp = '0' + level; if (unlink(newname) != 0) Unlink(newname, NULL); return 0; } *digitp = '0' + level; if (access(fname, F_OK) == 0) { // file exists for this level ret = ShuffleFiles(level+1, newname, fname, ndp, digitp, ret); if (ret) // error condition? -- bail out return ret; *digitp = '0' + level; *ndp = '0' + level + 1; if (Rename(fname, newname) < 0) ret |= RENAME_ERR; } return ret; } ///////////////////////////////////////////////////////////////////////////// int Update() { int len; int ret; FILE *f; char *p, *start_fname, *digitp, *ndp; char fname[OURBUFSIZ], newname[OURBUFSIZ]; ret = 0; if ((f = fdopen(outfd, "r")) == NULL) { Msg("Can't fdopen intermediate file. (%d)", errno); return FILE_ERR; } len = strlen(bkp); memcpy(fname, bkp, len + 1); start_fname = &fname[len]; while (fgets(start_fname, OURBUFSIZ-4, f) != NULL) { p = start_fname; #ifdef WIN32 if (p[1] == ':') p[1] = '$'; #endif while (*p && (*p != '\n')) // find newline; p++; *p = 0; // remove newline and terminate string if (access(fname, F_OK) == 0) { // file exists len = strlen(fname); digitp = &fname[len]; *digitp++ = ';'; *digitp++ = '1'; *digitp-- = 0; // leave digitp pointing at digit memcpy(newname, fname, len + 3); ndp = &newname[digitp - fname]; ret = ShuffleFiles(1, newname, fname, ndp, digitp, 0); if (ret) // bail out on error continue; *(--digitp) = 0; *ndp = '1'; if (Rename(fname, newname) < 0) { ret |= RENAME_ERR; continue; } } else { p = strrchr(fname, SLASH); *p = 0; if (Mkdir(fname)) // (mkdir -p) ret |= MKDIR_ERR; if (bailout) MailMsg("mkdir failure (%s)", strerror(bailout)); *p = SLASH; } Copy(start_fname, fname); } fclose(f); return ret; } ///////////////////////////////////////////////////////////////////////////// int ShuffleLinks(char level, char *newname, char *fname, char *ndp, char *digitp, int ret) { struct stat d; if (level == maxlevel) { // maximum generations? -- remove link *ndp = '0' + level; if (unlink(newname) != 0) Unlink(newname, NULL); if (verbose > 1) Msg("removing symlink %s", newname); return 0; } *digitp = '0' + level; if (STAT(fname, &d) == 0) { // link exists for this level ret = ShuffleLinks(level+1, newname, fname, ndp, digitp, ret); if (ret) // error condition? -- bail out return ret; *digitp = '0' + level; *ndp = '0' + level + 1; if (Rename(fname, newname) < 0) ret |= RENAME_ERR; } return ret; } ///////////////////////////////////////////////////////////////////////////// int SymUpdate(char *dest) { int len, ret; char *p, *start_fname, *digitp, *ndp; char fname[OURBUFSIZ], newname[OURBUFSIZ]; struct stat d; strcpy(fname, dest); len = strlen(fname); digitp = &fname[len]; *digitp++ = ';'; *digitp++ = '1'; *digitp-- = 0; // leave digitp pointing at digit memcpy(newname, fname, len + 3); ndp = &newname[digitp - fname]; ret = ShuffleLinks(1, newname, fname, ndp, digitp, 0); if (ret) // bail out on error return ret; *(--digitp) = 0; *ndp = '1'; if (Rename(fname, newname) < 0) return RENAME_ERR; return 0; } ///////////////////////////////////////////////////////////////////////////// int Rename(char *oldname, char *newname) { int ret = 0; #ifdef DEBUG Msg("rename %s to %s", oldname, newname); #else if ((ret = rename(oldname, newname)) < 0) { Msg("Can't rename %s to %s (%d)", oldname, newname, errno); } else { if (verbose > 1) Msg("%s renamed to %s", oldname, newname); } #endif return (ret != 0); } ///////////////////////////////////////////////////////////////////////////// void Unlink(char *path, struct stat *s) { struct stat d, x; struct dirent *de; DIRP dir; int pathlen; char name[OURBUFSIZ], *p; if (s == NULL) { s = &d; if (STAT(path, s) != 0) { if (errno != ENOENT) Msg("Can't stat %s (%d)", path, errno); return; } } if (S_ISDIR(s->st_mode)) { // remove entire directory tree if ((dir = opendir(path)) == NULL) { Msg("Can't open directory %s (%d)", name, errno); return; } pathlen = strlen(path); memcpy(name, path, pathlen); p = &name[pathlen]; *p++ = SLASH; // position for new names while ((de = readdir(dir)) != NULL) { if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..") == 0)) continue; // skip dot and dotdot dirs strcpy(p, de->d_name); // append new name to base path if (STAT(name, &x) != 0) { Msg("Can't stat %s (%d)", name, errno); return; } if (S_ISDIR(x.st_mode)) { Unlink(name, &x); } else { chmod(name, x.st_mode | 0777); unlink(name); } } closedir(dir); chmod(path, s->st_mode | 0777); rmdir(path); } else { chmod(path, s->st_mode | 0777); unlink(path); } if (verbose > 1) Msg("%s unlinked", path); } ///////////////////////////////////////////////////////////////////////////// int Mkdir(char *path) { char *p; struct stat d; if (access(path, F_OK) != 0) { // does this directory exist? p = strrchr(path, SLASH); if (p != NULL) *p = 0; else return 0; if (Mkdir(path)) // make all parent directories return 1; *p = SLASH; // make the next component visible if (MKDIR(path, 0644) < 0) { // make this directory if ((errno == ENOSPC) || (errno == EIO)) bailout = errno; Msg("Can't create path (%s) (%d)", path, errno); return 1; } if (verbose > 1) Msg("created path %s",path); return 0; } // is this path a directory? #ifndef WIN32 STAT(path, &d); if ( ! S_ISDIR(d.st_mode)) { if (SymUpdate(path) != 0) { // shuffle any existing files if ((errno == ENOSPC) || (errno == EIO)) bailout = errno; Msg("Can't create path (%s) (%d)", path, errno); return 1; } if (MKDIR(path, 0644) < 0) { // make this directory if ((errno == ENOSPC) || (errno == EIO)) bailout = errno; Msg("Can't create path (%s) (%d)", path, errno); return 1; } } #endif return 0; } ///////////////////////////////////////////////////////////////////////////// void Permissions(char *dest, struct stat *s, int islink) { struct utimbuf u; #ifndef WIN32 if (islink) { if (lchown(dest, s->st_uid, s->st_gid) < 0) Msg("Can't set owner/group on %s (%d)", dest, errno); if (islink == 1) return; } else { if (chown(dest, s->st_uid, s->st_gid) < 0) Msg("Can't set owner/group on %s (%d)", dest, errno); } #endif u.actime = s->st_atime; u.modtime = s->st_mtime; if (utime(dest, &u) < 0) // set destination timestamp Msg("Can't set timestamp on %s (%d)", dest, errno); if (chmod(dest, s->st_mode) < 0) Msg("Can't set mode on %s (%d)", dest, errno); } ///////////////////////////////////////////////////////////////////////////// int banned_file_extension(char *p) { char *q = p; while (*q) // find terminating null q++; q--; // point at last char in name; if (memcmp(&q[-1], ".o", 2) == 0) { skipcount++; if (verbose > 1) Msg("skipping %s", p); return 1; } if (memcmp(&q[-3], ".bak", 4) == 0) { skipcount++; if (verbose > 1) Msg("skipping %s", p); return 1; } if (memcmp(&q[-3], ".BAK", 4) == 0) { skipcount++; if (verbose > 1) Msg("skipping %s", p); return 1; } if (memcmp(&q[-3], ".log", 4) == 0) { skipcount++; if (verbose > 1) Msg("skipping %s", p); return 1; } return 0; // not a banned suffix } ///////////////////////////////////////////////////////////////////////////// void Copy(char *src, char *dest) { unsigned size, total, amount; FILE *in, *out; char buffer[OURBUFSIZ], source[OURBUFSIZ]; struct stat s, d; strcpy(source, src); #ifdef WIN32 source[1] = ':'; #endif STAT(source, &s); #ifndef WIN32 if (S_ISLNK(s.st_mode)) { if ((size = readlink(source, buffer, OURBUFSIZ)) < 0) Msg("Can't read symlink %s (%d)", src, errno); else { if (lstat(dest, &d) == 0) { // (access(dest, 0) doesnt work here!) if (SymUpdate(dest) != 0) { Msg("Can't rotate symlink %s (%d)", dest, errno); return; } } buffer[size] = 0; // terminate the link contents! if (symlink(buffer, dest) < 0) Msg("Can't create symlink %s (%d)", dest, errno); else { if (verbose) { if (verbose > 1) Msg("created symlink %s from %s", dest, source); else Msg("created symlink %s", dest); } if (stat(dest, &d) == 0) Permissions(dest, &s, 2); else Permissions(dest, &s, 1); } } return; } #endif in = fopen(source, "rb"); if (in == NULL) { Msg("Can't open %s for reading", source); return; } total = s.st_size; STAT(dest, &d); out = fopen(dest, "wb"); if (out == NULL) { #ifndef WIN32 if (S_ISLNK(d.st_mode)) { if (SymUpdate(dest) != 0) { Msg("Can't rotate symlink %s (%d)", dest, errno); return; } out = fopen(dest, "wb"); } } if (out == NULL) { #endif if ((errno == ENOSPC) || (errno == EIO)) bailout = errno; Msg("Can't open %s for writing", dest); fclose(in); if (bailout) MailMsg("Can't open %s for writing (%s)", dest, strerror(bailout)); return; } copycount++; while (total > 0) { amount = (total < OURBUFSIZ) ? total : OURBUFSIZ; size = fread(buffer, 1, amount, in); if (size != amount) { Msg("Read of %d bytes failed from %s", amount, source); fclose(in); fclose(out); unlink(dest); return; } if (fwrite(buffer, 1, size, out) != size) { if ((errno == ENOSPC) || (errno == EIO)) { bailout = errno; } Msg("Write of %d bytes failed from %s (%d)", amount, source, errno); fclose(in); fclose(out); unlink(dest); if (bailout) MailMsg("Write failure (%s)", strerror(bailout)); return; } total -= size; } fclose(in); fclose(out); Permissions(dest, &s, 0); if (verbose) { if (verbose > 1) Msg("%s copied to %s", source, dest); else Msg("%s copied", source); } } ///////////////////////////////////////////////////////////////////////////// void *Malloc(unsigned size) { void *x; if ((x = malloc(size)) == NULL) { Msg("Can't get %d memory. Ending", size); exit(1); } return x; } ///////////////////////////////////////////////////////////////////////////// int Write(char *s) { int len = strlen(s); if (write(outfd, s, len) != len) { Msg("Can't write intermediate file (%d)", errno); return errno; } return 0; } ///////////////////////////////////////////////////////////////////////////// int Excluded(char *path, struct NAMELIST *exclusion_list) { int ret; struct NAMELIST *n; n = exclusion_list; while (n) { // Msg("COMPARING [%s] to [%s]", path, n->name); ret = STRNCMP(path, n->name, n->namlen); if (ret == 0) { if (verbose) Msg("excluding %s", path); return 1; // this path or file is excluded! } n = n->link; } return 0; } ///////////////////////////////////////////////////////////////////////////// int FileTreeWalk(char *path, DIRP dir) { int ret = 0; int pathlen; int need_file; char name[OURBUFSIZ], *p; char testname[OURBUFSIZ]; DIRP newdir; struct stat s; struct dirent *de; pathlen = strlen(path); memcpy(name, path, pathlen); p = &name[pathlen - 1]; if (*p++ != SLASH) // don't double slash for root *p++ = SLASH; // position for new names while ((de = readdir(dir)) != NULL) { if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..") == 0)) continue; // skip dot and dotdot dirs strcpy(p, de->d_name); // append new name to base path if (STAT(name, &s) != 0) { Msg("Can't stat %s (%d)", name, errno); continue; // ADDED: johna 12-14-2004 // return 1; // REMOVED: johna 12-14-2004 } if (S_ISDIR(s.st_mode)) { if (s.st_dev != filesys_dev) continue; // crossed mount point if ((newdir = opendir(name)) == NULL) { Msg("Can't open directory %s (%d)", name, errno); closedir(newdir); return 1; } if (! Excluded(name, dir_excl_listp)) { if (FileTreeWalk(name, newdir) != 0) return 1; } closedir(newdir); continue; } if (S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) { if (s.st_mtime >= last_bkp_time) need_file = 1; if (s.st_mtime < last_bkp_time) { sprintf(testname, "%s%s", bkp, name); if (access(testname, F_OK) == 0) // file exists need_file = 0; else { if (S_ISLNK(s.st_mode)) // broken symlink? need_file = 0; // don't replicate endlessly else need_file = 1; } } if (banned_file_extension(name)) need_file = 0; if (need_file && (! Excluded(name, fil_excl_listp))) { // add filename to list if (verbose) Msg("Selecting %s", name); ret |= Write(name); ret |= Write("\n"); fcount++; } if (verbose > 2) Msg("INCLUDING %s", name); } else { if (verbose > 2) Msg("REJECTING %s", name); } } return ret; // end of directory } ///////////////////////////////////////////////////////////////////////////// #ifdef WIN32 DIRP opendir(char *path) { DIRP dirp; dirp = (DIRP)Malloc(sizeof(struct DIR)); if (dirp == NULL) { fprintf(stderr, "Out of memory!\n"); exit(1); } dirp->first = 1; getcwd(dirp->dirname, OURBUFSIZ); // save our current directory chdir(path); // switch to new directory return dirp; } ///////////////////////////////////////////////////////////////////////////// struct dirent *readdir(DIRP dirp) { int ret; if (dirp->first == 1) { ret = _findfirst("*", &dirp->fft); dirp->handle = ret; dirp->first = 0; } else ret = _findnext(dirp->handle, &dirp->fft); if (ret == -1) return NULL; return (struct dirent *)dirp->fft.name; } ///////////////////////////////////////////////////////////////////////////// int closedir(DIRP dirp) { chdir(dirp->dirname); // restore our original directory free(dirp); return 0; } #endif ///////////////////////////////////////////////////////////////////////////// int Getopt(int argc, char * argv[], char *opts) { static int sp = 1; register int c; register char *cp; if (sp == 1) { if ((optind >= argc) || (argv[optind][0] != '-') || (argv[optind][1] == '\0')) return(EOF); else if (strcmp(argv[optind], "--") == 0) { optind++; return(EOF); } } optopt = c = argv[optind][sp]; if (c == ':' || (cp = strchr(opts, c)) == NULL) { Msg("%s: illegal option -- %c", progname, c); if (argv[optind][++sp] == '\0') { optind++; sp = 1; } return('?'); } if (*++cp == ':') { if (argv[optind][sp+1] != '\0') optarg = &argv[optind++][sp+1]; else if (++optind >= argc) { Msg("%s: option requires an argument -- ", progname, c); sp = 1; return('?'); } else optarg = argv[optind++]; sp = 1; } else { if (argv[optind][++sp] == '\0') { sp = 1; optind++; } optarg = NULL; } return(c); } /////////////////////////////////////////////////////////////////////////////