#include #include "reclinker.h" #ifndef GNU char * gnu_getcwd () { size_t size = 100; void * MALLOC (size_t); while (1) { char *buffer = (char *) MALLOC (size); if (getcwd (buffer, size) == buffer) return buffer; free (buffer); if (errno != ERANGE) return 0; size *= 2; } } #endif /* Erases "reduce_with" from "from" if it is an initial segment */ char * strreduce(char *from, const char *reduce_with) { while(*reduce_with == *from && *reduce_with != '\0') { reduce_with++; from++; } if(*reduce_with == '\0') return strdup(from); return NULL; } int strsubtest(char *from, const char *reduce_with) { while(*reduce_with == *from && *reduce_with != '\0') { reduce_with++; from++; } if(*reduce_with == '\0') return 1; return 0; } void * xmalloc (size_t size) { register void *value = (void*)malloc(size); if (value == 0) error ("virtual memory exhausted","malloc",NULL); return value; } void * xrealloc (char* buf,size_t size) { register void *value = (void*)realloc(buf,size); if (value == 0) error ("virtual memory exhausted","realloc",NULL); return value; } void error(char *msg,...) { va_list vl; char *s; va_start(vl,msg); while ((s = va_arg(vl, char *))) fprintf(stderr,s); va_end(vl); fprintf(stderr,": "); perror(msg); exit(FATAL); } void chowner() { if(uid == -1 && gid == -1) return; if (lchown(where->str,uid,gid) == 0) { /*lchown was not mentioned in glibc docs, but it does exist */ if (verbosity >= 2) { printf("%s%s: setting ",whereorep,where->strmid); if (usrname != NULL) printf("uid to %s ", usrname); if (usrname != NULL && grpname != NULL) printf( "and "); if (grpname != NULL) printf("gid to %s ", grpname); printf("\n"); } } else { program_retval = SMALLPROB; fprintf(stderr,"%s%s: setting ",whereorep,where->strmid); if (usrname != NULL) fprintf(stderr, "uid to %s ", usrname); if (usrname != NULL && grpname != NULL) fprintf(stderr, "and "); if (grpname != NULL) fprintf(stderr, "gid to %s ", grpname); fprintf(stderr,"failed\n"); } } /* How to proceed a directory (it's put to a separate function as both * main() and linker() uses this) */ int createdir() { int retval = mkdir(where->str, mode); int errno_saved = errno; if(retval == 0) { if(verbosity >= 1) { printf("%s%s/",whereorep,where->strmid); if (verbosity >= 2) printf(": dir created with mode %o", (int)(mode & ~mask)); putchar('\n'); } chowner(); } else if (errno == EEXIST #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ defined(__DragonFly__) /* work around weird BSD errno upon mkdir("/") */ || errno == EISDIR #endif ) { struct stat to_createstat; stat(where->str, &to_createstat); if ((to_createstat.st_mode & S_IFMT) == S_IFDIR) { if(verbosity >= 2) { printf("%s%s/: dir already exists\n",whereorep,where->strmid); } } else { /* uerror("the dir cannot be created"); */ errno = errno_saved; return -2; } } else { /* uerror("the dir cannot be created"); */ errno = errno_saved; return -2; } /* Do we really have to be so strict and raise an error? */ return retval; } void defaults() { program_retval = SUCCESS; verbosity=1; force=0; rel=0; /* mode = S_IRWXAUGS; */ onlydir=0; deletemode=0; firstmodeused=0; othermodeused=0; uid = -1; gid = -1; Uid = -1; Gid = -1; } void usage(FILE* desc) { fprintf(desc, "Recursive linking/deleting utility, version %s. Usage:\n\ \n\ %s [-l|-d|-t] [options] [...]\n",VERSION,me); } void showhelp() { printf( "\n\ The program has a linker, a deleter and a test mode. By default it's in\n\ linker mode. It gets into deleter mode if it's invoked as %s, and into\n\ test mode if it's invoked as %s. Linker/deleter/test mode may be\n\ forced with the -l/-d/-t switches.\n\ \n\ In linker mode it mirrors the directory tree under to under \n\ and symlinks files there. The symlinks point to the basename of the file\n\ processed, prefixed with . If is not given, it defaults\n\ to the absolute pathname of (ie., the symlink points to the\n\ correct absolute filename).\n\ \n\ In deleter mode it deletes non-broken symlinks (empty directories) of\n\ the form /foo, where /foo is an existing file (directory).\n\ (A dir is also considered empty if it gets empty during the deletion\n\ procedure.)\n\ \n\ In test mode it tests whether symlinks/dirs under corresponding\n\ to files/dirs under exist, and whether are they created properly.\n\ \n\ If extra arguments are given, then they will be treated as files named\n\ relatively from , and they will be proceeded individually \n\ (no mass action is taken). If \"-\" is among these extra arguments,\n\ then individual file names are also read from stdin; if \"0-\" is among\n\ them, individual file names are also read from stdin, being separated\n\ by '\\0'.\n\ \n\ Options common for all modes:\n\ \n\ -h\t\tprint this message and exit\n\ -U \tskip file under if it's not owned by \n\ -G \tskip file under if it doesn't belong to \n\ -v/-q\t\tincrease/decrease verbosity level\n\ \t\t(currently 0, 1, 2 are in use, default is 1)\n\ \n\ Options in linker mode are:\n\ \n\ -r\t\tproduce relative symlinks\n\ -f\t\toverwrite existing files\n\ -m \tcreate new directories with mode ( &'d with umask!)\n\ -o\t\tforce given by -m for all processed directories \n\ \t\t(if -m is not used, the mode of the actually processed\n\ \t\tdir is forced)\n\ -u \tnewly created dirs/symlinks shall be owned by \n\ -g \tnewly created dirs/symlinks shall belong to \n\ -D\t\tonly directories are proceeded \n\ -p \tprepend to link targets \n\ \n\ Options in deleter mode are:\n\ \n\ -f\t\tdelete corresponding file even it's not a symlink\n\ -m \tdeletes /foo only if /foo is of mode \n\ -o\t\tonly broken symlinks are deleted\n\ -u \tdeletes /foo only if it belongs to \n\ -g \tdeletes /foo only if it belongs to \n\ -D\t\tdon't delete directories \n\ \n\ Options in test mode are:\n\ \n\ -r\t\ttest symlinks as if they were created using -r in linker mode\n\ -m \tskip foo if /foo isn't of mode \n\ -u \tskip foo if /foo is not owned by \n\ -g \tskip foo if /foo doesn't belong to \n\ -p \ttest links assuming they were created using \"-p \" \n\ \n\ Return values: 0 - success; 1 - fatal error; 2 - some act failed but\n\ just kept on doing; 3 - arguments imply nothing happens (eg., bad\n\ options used)\n\ ",DELETE_INVOKE,TEST_INVOKE); } gid_t parsegid(char* grpnam) { struct group* grp; char *p = NULL; gid_t _gid_; _gid_ = strtoul(grpnam, &p, 10); /* Is it numeric? */ if (grpnam == p) { /* #ifdef __UCLIBC__ that was an oooold uClibc, now I dare to forget about this issue */ #if 0 /* It's a damn stupid bug in uClibc (I met with it in 0.9.21): if resides on the 0x*d-th row of /etc/group, then calling twice getgrnam() with no intermediate usage of getgrnam results in a segfault */ getgrnam("root"); #endif grp = getgrnam(grpnam); } else grp = getgrgid(_gid_); if (grp == NULL) return -1; return grp->gr_gid; } uid_t parseuid(char* usrnam) /* based on Busybox code */ { struct passwd* usr; char *p = NULL; uid_t _uid_; _uid_ = strtoul(usrnam, &p, 10); /* Is it numeric? */ if (usrnam == p) usr = getpwnam(usrnam); else usr = getpwuid(_uid_); if (usr == NULL) return -1; return usr->pw_uid; } mode_t parsemode(char* modestring) { char* modestring_orig = modestring; int i=0; while (*modestring != '\0') { if((i++ > 3) || (*modestring < '0') || (*modestring++ > '7')) goto badmode; } return strtol(optarg,NULL,8); badmode: fprintf(stderr,"%s: invalid mode value\n", modestring_orig); exit(BADOPT); } void cleanup_aux(int isnew) { if(isnew == 0) { int rmsuc = rmdir(where->str); if(rmsuc == 0) { if(verbosity >=2) printf("%s removed\n",whereorig); } else { error("cleanup failed",whereorig,NULL); } } } char * my_realpath(char *str) { char *aux = (char *)MALLOC(PATH_MAX+1); char *aux2 = (char *)realpath(str,aux); /* why do get I warnings if I omit the (char *) constraint ??? */ char *aux3; if (aux2 == NULL) { free(aux); return NULL; } aux3 = strdup(aux2); free(aux); return aux3; } char * my_readlink (const char *filename) /* based on sample code of the glibc manual */ { int size = STARTLENGTH; while (1) { char *buffer = (char *) MALLOC (size); int nchars = readlink (filename, buffer, size); if (nchars < 0) return NULL; if (nchars < size) { *(buffer + nchars) = '\0'; return buffer; } free (buffer); size *= 2; } } /* Now comes stuff for deciding whether a symlink points to file. */ char * soft_realpath(const char *filename) /* the same as my_realpath, it just gives a symlinkless for broken * symlinks as well (symlinkess, except for the symlink proceeded on */ { char *fname, *bname, *dname, *rdname, *finalname; fname = strdup(filename); if ((bname = (char *)basename(fname)) == NULL) return NULL; if ((dname = (char *)dirname(fname)) == NULL) return NULL; if ((rdname = (char *)my_realpath(dname)) == NULL) return NULL; /* free(dname); */ finalname = (char *)MALLOC(strlen(rdname) + strlen(bname) + 2); strcpy(finalname,rdname); free(rdname); strcat(finalname,"/"); strcat(finalname,bname); /* free(bname); */ return strdup(finalname); } char * canon_readlink(const char *filename) /* gives the soft_realpath of the target of a symlink */ /* freeing should also happen if a we return NULL due to a failed function call, * ehh... */ { char *pretarget, *target, *rtarget; if ((pretarget = (char *)my_readlink(filename)) == NULL) return NULL; if ((*pretarget) == '/') { target = pretarget; } else { char *rname, *dname; if ((rname = (char *)soft_realpath(filename)) == NULL) return NULL; if ((dname = (char *)dirname(rname)) == NULL) return NULL; /* free(rname); */ target = (char *)MALLOC(strlen(dname) + strlen(pretarget) + 2); strcpy(target,dname); /* free(dname); */ strcat(target,"/"); strcat(target,pretarget); free(pretarget); } if ((rtarget = (char *)soft_realpath(target)) == NULL) return NULL; free(target); return strdup(rtarget); } int rec_pointto(char *linkname, char *filename) { char *currpos, *newpos, *rname; int i; if((currpos = (char *)soft_realpath(linkname)) == NULL || (rname = (char *)soft_realpath(filename)) == NULL) return 0; for (i=0; i < MY_MAXSYMLINKS; i++) { if (strcmp(currpos,rname) == 0) return 1; if ((newpos = (char *)canon_readlink(currpos)) == NULL) return 0; free(currpos); currpos = newpos; } errno = ELOOP; return 0; } /* * Read at most len bytes from file into buf, until we hit EOF or sepchar or 0. * Upon hitting EOF or sepchar without error, buf is 0 terminated, and the * length of the resulting string (excluding the final zero) is returned. * * Else -- ie., we got an error, we read 0 when sepchar is not zero, * we read len bytes without sepchar or zero -- -1 is returned in * order to signal an error. */ int get_line_from_file(FILE *file, char *buf, size_t len, char sepchar) { int i = 0; char c; if (len == 0) return 0; for (; i < len; i++) { c = fgetc(file); if (c == EOF || c == sepchar) { if (ferror(file)) return -1; buf[i] = '\0'; return i; } if (c == '\0') return -1; buf[i] = c; } return -1; }