diff -uNr braz-1.0a1/CHANGELOG braz-1.0a2/CHANGELOG --- braz-1.0a1/CHANGELOG Thu Jan 1 01:00:00 1970 +++ braz-1.0a2/CHANGELOG Tue Oct 30 04:25:58 2001 @@ -0,0 +1,29 @@ +-*- text -*- + +------------------------------------------------------------------------------ +braz v1.0 alpha 2 (2001-10-30) +------------------------------------------------------------------------------ + +* All reads are made using a block size optimal for the filesystem braz's + working on. + +* File linking is now verbose. + +* The wording of the estimation message is now different if linking is + actually undertaken. + +* Added an option to byte compare two files before linking. + +* Added file inclusion and exclusion patterns. + +* Linking can now be restricted to files having the same uid, gid, + permissions, user flags, or any other combination the the four. + + +------------------------------------------------------------------------------ +braz v1.0 alpha 1 (2001-10-03) +------------------------------------------------------------------------------ + +* Initial release. + +EOF \ No newline at end of file diff -uNr braz-1.0a1/Makefile braz-1.0a2/Makefile --- braz-1.0a1/Makefile Mon Oct 1 05:06:34 2001 +++ braz-1.0a2/Makefile Mon Oct 1 05:06:34 2001 @@ -29,7 +29,7 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################## -# $Name: v1_0a1 $ +# $Name: v1_0a2 $ # $Date: 2001/10/01 03:06:34 $ # $Revision: 1.7 $ ############################################################################## diff -uNr braz-1.0a1/README braz-1.0a2/README --- braz-1.0a1/README Wed Oct 3 04:42:03 2001 +++ braz-1.0a2/README Tue Oct 30 04:25:58 2001 @@ -1,6 +1,6 @@ ************************************ -*** braz -- Find and link *** v 1.0a1, 2001-10-03 +*** braz -- Find and link *** v 1.0a2, 2001-10-30 *** together identical files *** (c) 2001 Bertrand Petit ************************************ Release notes @@ -15,11 +15,11 @@ ============================================================================== The software, currently in alpha stage, is currently known to work on -FreeBSD systems. Currently it can only be built under FreeBSD -operating systems: braz needs the Message Digest library (libmd) that -seems to be specific. Replacements that I'm not aware of may exist. If -you know such libraries, please send any necessary patches to adapt -braz to them. +FreeBSD and NetBSD systems. Currently it can only be built under +systems providing the Message Digest library (libmd) that seems to be +specific to the BSD world. Replacements that I'm not aware of may +exist. If you know such libraries, please send any necessary patches +to adapt braz to them. ============================================================================== diff -uNr braz-1.0a1/TODO braz-1.0a2/TODO --- braz-1.0a1/TODO Mon Oct 1 05:06:34 2001 +++ braz-1.0a2/TODO Tue Oct 30 04:25:58 2001 @@ -2,11 +2,6 @@ Some raw ideas of things to add to braz: -* For the paranoids provide an option to effectively compare byte by - byte files that seems identical. - -* Add some regexp linking exclusion patterns. - * Dates of linked files should be compared and keep the oldest/newest date. @@ -18,8 +13,15 @@ * We could provide the option of convert soft links to hard links. -* Soft links pointing outside the examined file tree could be replaced by - regular files in the the tree. Thoses new files should also be subject - to analysis. +* Soft links pointing outside the examined file tree could be replaced + by regular files in the tree. Thoses new files should also be + subject to analysis. + +* The generated shell script should be prefixed with comments stating + it's kind, the date, host, and root directory where it was created. + +* Permissions, owner, group and user flags should be listed in the + produced shell script as comments to aid manual reviewing of the + links it will perform. EOF diff -uNr braz-1.0a1/braz.8 braz-1.0a2/braz.8 --- braz-1.0a1/braz.8 Wed Oct 3 04:42:03 2001 +++ braz-1.0a2/braz.8 Tue Oct 30 04:25:58 2001 @@ -27,19 +27,21 @@ .\" OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $Name: v1_0a1 $ -.\" $Date: 2001/10/03 02:42:03 $ -.\" $Revision: 1.5 $ +.\" $Name: v1_0a2 $ +.\" $Date: 2001/10/30 03:25:58 $ +.\" $Revision: 1.9 $ .\" -.Dd October 3, 2001 +.Dd October 30, 2001 .Dt BRAZ 1 .Sh NAME .Nm braz .Nd Find and link together identical files. .Sh SYNOPSIS .Nm braz -.Op Fl ev +.Op Fl acefgopv .Op Fl n | s +.Op Fl i Ar regex +.Op Fl x Ar regex .Sh DESCRIPTION .Nm braz searches in the file-tree rooted at the current working directory for @@ -48,15 +50,37 @@ The following options are available: .Bl -tag -width flag +.It Fl a +Restrict linking to files having the same status. This option is a +shorthand setting the +.Fl f , +.Fl g , +.Fl o , +and +.Fl p +options. +.It Fl c +Each pair of files to be linked together are byte-to-byte compared to +ensure they are really identical. .It Fl e Output, after file-system analysis, the number of block that were reclaimed, or, if the .Fl s option was specified, would be reclaimed if the generated script is executed. +.It Fl f +Restrict linking to files having the same user flags. +.It Fl g +Restrict linking to files having the same group ID. +.It Fl i +Specify an inclusion pattern. .It Fl n The linking operations will not be undertaken. Implicitly sets the .Fl e option. +.It Fl o +Restrict linking to files having the same owner user ID. +.It Fl p +Restrict linking to files having the same permission flags. .It Fl s The linking operations will no be directly undertaken, instead a .Nm sh @@ -70,11 +94,13 @@ options are omitted. .It Fl v Increase verbosity, more Vs means more verbosity. +.It Fl x +Specify an exclusion pattern. .El This command is potentially dangerous as the file-tree may be -irremediably be modified if misused or if it's effects are not well -understood. It should be used with caution! +irremediably modified against your wishes if misused or if it's +effects are not well understood. It should be used with caution! .Sh ALGORITHM .Nm braz is build of a two passes algorithm. The first pass completely explores @@ -85,14 +111,37 @@ The second pass searches for identical files in the database built by the first pass. Files are sorted according to their digest, in the -resulting ordered set subsequent files whose digest signatures are the -same are considered identical. If two of those files are stored behind -a different inode then they are linked together after unlinking one of -them. +resulting ordered set subsequent files whose size and digest +signatures are the same are considered identical. If two of those +files are stored behind a different inode then they are linked +together after unlinking one of them. +.Sh FILES SELECTION +The set of files analyzed by +.Nm braz +can be controlled by a set of inclusion and/or exclusion patterns. The +patterns are standard extended regular expressions specified via the +.Fl i +and +.Fl x +options, respectively for selecting or excluding files. Both kind of +patterns can be freely mixed without limit. The string matched against +the patterns is the complete path (including the file name) relative +to the root the the examined file-tree. + +The patterns are applied in the command-line order, the first match is +used to select or reject the tested filename depending on the kind of +the matched pattern. If not match is found then a default action is +used depending on the kind of the first specified pattern. If the +first specification is an exclusion pattern then the file is selected +for analysis; and similarly if the first pattern is inclusive then the +default action is to reject the file. If no pattern is specified then +all files are selected for analysis. .Sh SEE ALSO .Xr link 2 , .Xr unlink 2 , -.Xr sh 1 . +.Xr re_format 7 , +.Xr sh 1 , +.Xr stat 2 . R. Rivest, .Em The MD5 Message-Digest Algorithm, diff -uNr braz-1.0a1/braz.c braz-1.0a2/braz.c --- braz-1.0a1/braz.c Mon Oct 1 05:06:35 2001 +++ braz-1.0a2/braz.c Tue Oct 30 04:25:58 2001 @@ -37,9 +37,9 @@ ****************************************************************************/ /* - * $Name: v1_0a1 $ - * $Date: 2001/10/01 03:06:35 $ - * $Revision: 1.8 $ + * $Name: v1_0a2 $ + * $Date: 2001/10/30 03:25:58 $ + * $Revision: 1.12 $ */ /**************************************************************************** @@ -56,6 +56,7 @@ #include #include #include +#include /**************************************************************************** * Datatypes definitions. * @@ -78,10 +79,28 @@ COMMON_ITEM_MEMBERS ino_t inode; int64_t blocks; + off_t size; + mode_t perms; + uid_t owner; + gid_t group; + u_int32_t uflags; unsigned char signature[16]; struct file_t *next; }file_t; +typedef enum +{ + include, + exclude +}filtertype_t; + +typedef struct filter +{ + filtertype_t type; + regex_t expression; + struct filter *next; +}filter_t; + /**************************************************************************** * Global variables. * ****************************************************************************/ @@ -89,6 +108,11 @@ int DoNotAct=0, DoShellScript=0, DoEstimation=0, + DoByteComparison=0, + OnlySamePerms=0, + OnlySameOwner=0, + OnlySameGroup=0, + OnlySameUflags=0, Verbose=0, TreeDepth=0, FilesystemChanged=0, @@ -102,6 +126,37 @@ *FilesChangedEnd=NULL; const item_t **ItemStack; dev_t Filesystem; +u_int32_t OptimalBlockSize=8192; +unsigned char *IObuffer1=NULL, + *IObuffer2=NULL; +filter_t *FiltersBeg=NULL, + *FiltersEnd=NULL; + +/* Error messages for the regex library status codes. */ +struct +{ + int code; + char *message; +}RegErrors[]= +{ + {REG_NOMATCH,"regexec() failed to match"}, + {REG_BADPAT,"invalid regular expression"}, + {REG_ECOLLATE,"invalid collating element"}, + {REG_ECTYPE,"invalid character class"}, + {REG_EESCAPE,"\\ applied to unescapable character"}, + {REG_ESUBREG,"invalid backreference number"}, + {REG_EBRACK,"brackets [ ] not balanced"}, + {REG_EPAREN,"parentheses ( ) not balanced"}, + {REG_EBRACE,"braces { } not balanced"}, + {REG_BADBR,"invalid repetition count(s) in { }"}, + {REG_ERANGE,"invalid character range in [ ]"}, + {REG_ESPACE,"ran out of memory"}, + {REG_BADRPT,"?, *, or + operand invalid"}, + {REG_EMPTY,"empty (sub)expression"}, + {REG_ASSERT,"``can't happen''--you found a bug"}, + {REG_INVARG,"invalid argument, e.g. negative-length string"}, + {0,NULL} +}; /**************************************************************************** * Build in the supplied buffer the full path the to supplied item. * @@ -189,10 +244,123 @@ } /**************************************************************************** - * "Compare" two file_t structure for qsort. The two signatures are used * + * Performs the operations needed to link two files. This can either be * + * direct filesystem alteration or shell-script command generation, or * + * nothing. * + ****************************************************************************/ +static int LinkTwoFiles(const char *CursorPath, const char *HeadPath) +{ + /* Output shell commands to perform the linking, if requested. */ + if(DoShellScript) + { + printf("rm \'%s\'\n",CursorPath); + printf("ln \'%s\' \'%s\'\n",HeadPath,CursorPath); + } + else + { + if(Verbose>1) + fprintf(stderr,"%s: linking %s to %s\n", + ProgName,HeadPath,CursorPath); + + /* Perform the linking, if not prohibited. */ + if(!DoNotAct) + { + /* First remode the cursor file. */ + if(unlink(CursorPath)) + { + fprintf(stderr,"%s: can't unlink file %s (%s).\n",ProgName,CursorPath,strerror(errno)); + return(1); + } + FilesystemChanged=1; + + /* Then perform the link to the head file. */ + if(link(HeadPath,CursorPath)) + { + fprintf(stderr,"%s: can't link file %s to file %s (%s); %s file is now lost.\n",ProgName,HeadPath,CursorPath,strerror(errno),CursorPath); + return(1); + } + } + } + + return(0); +} + +/**************************************************************************** + * Byte to byte comparison between two files. Returns zero if the files * + * are the same, 1 if different, or -1 on error. Both files should be of * + * the same size. IObuffer1 and IObuffer2 globals arrays are used. * + ****************************************************************************/ +static int CompareTwoFiles(const char *File1, const char *File2) +{ + int fd1, + fd2; + static const char *OpenErrorMessage="%s: can't open file %s for reading (%s).\n"; + ssize_t ReadSize1, + ReadSize2; + + /* What are we doing? */ + if(Verbose>2) + { + fprintf(stderr,"%s: comparing file %s againg file %s\n",ProgName,File1,File2); + fflush(stderr); + } + + /* Open the two files for reading. */ + fd1=open(File1,O_RDONLY); + if(fd1<0) + { + fprintf(stderr,OpenErrorMessage,ProgName,File1,strerror(errno)); + return(-1); + } + fd2=open(File2,O_RDONLY); + if(fd2<0) + { + fprintf(stderr,OpenErrorMessage,ProgName,File2,strerror(errno)); + return(-1); + } + + /* Comparison loop. */ + do + { + ReadSize1=read(fd1,IObuffer1,OptimalBlockSize); + if(ReadSize1<0) + { + fprintf(stderr,"%s: read error on file '%s' (%s).\n",ProgName,File1,strerror(errno)); + close(fd1); + close(fd2); + return(-1); + } + ReadSize2=read(fd2,IObuffer2,OptimalBlockSize); + if(ReadSize2<0) + { + fprintf(stderr,"%s: read error on file '%s' (%s).\n",ProgName,File2,strerror(errno)); + close(fd1); + close(fd2); + return(-1); + } + + /* Compare the two blocks. If different in size or content + * then stop there. + */ + if(ReadSize1!=ReadSize2 || memcmp(IObuffer1,IObuffer2,(size_t)ReadSize1)!=0) + { + close(fd1); + close(fd2); + return(1); + } + }while(ReadSize1>0); + + /* The files are the same. */ + close(fd1); + close(fd2); + return(0); +} + +/**************************************************************************** + * Order two file_t structure for qsort. The two signatures are used * * to order them. * ****************************************************************************/ -static int CompareFiles(const void *File1, const void *File2) +static int FilesSignaturesComparator(const void *File1, const void *File2) { return(memcmp((*(file_t * const *)File1)->signature, (*(file_t * const *)File2)->signature,16)); @@ -209,7 +377,8 @@ **CursorFile, *Steper; int i, - j; + j, + Status; /* Times four for quote escapes. */ char HeadPath[MAXPATHLEN*4+1], CursorPath[MAXPATHLEN*4+1]; @@ -228,7 +397,7 @@ /* Build the table of files and sort it. */ for(Steper=FilesBeg,i=0;inext) AllFiles[i]=Steper; - qsort(AllFiles,(size_t)FilesCount,sizeof(file_t *),CompareFiles); + qsort(AllFiles,(size_t)FilesCount,sizeof(file_t *),FilesSignaturesComparator); /* Now step over all the files. Those that are identical are now * consecutive, they need to be linked together. @@ -240,17 +409,25 @@ /* The head path is not yet computed. */ *HeadPath=0; - /* The cursor ans the head have the same signature then link + /* The cursor and the head have the same signature then link * them. */ CursorFile=HeadFile+1; j=i+1; while(jsignature,(*CursorFile)->signature,16)==0) { - /* If the head and cursor inodes are different then the - * files are not already linked and needs to be. + /* If the head and cursor inodes are different and if the + * two files are of the same size then the files are + * identical and not already linked. If requested we check + * the permission bits, the group and owner id. If all of + * this match then the files needs to be linked. */ - if((*HeadFile)->inode!=(*CursorFile)->inode) + if((*HeadFile)->inode!=(*CursorFile)->inode && + (*HeadFile)->size==(*CursorFile)->size && + (!OnlySamePerms || (*HeadFile)->perms==(*CursorFile)->perms) && + (!OnlySameGroup || (*HeadFile)->group==(*CursorFile)->group) && + (!OnlySameOwner || (*HeadFile)->owner==(*CursorFile)->owner) && + (!OnlySameUflags || (*HeadFile)->uflags==(*CursorFile)->uflags)) { /* Build if head access path, if necessary, and the * cursor access path. @@ -260,44 +437,25 @@ return(1); if(ItemToPath(CursorPath,MAXPATHLEN*4+1,*CursorFile,DoShellScript)) return(1); - - /* This many blocks will be reclaimed. */ - SpaceSaved+=(*CursorFile)->blocks; - /* Output shell commands to perform the linking, if - * requested. + /* If requested the two files are compared before + * being linked. */ - if(DoShellScript) - { - printf("rm \'%s\'\n",CursorPath); - printf("ln \'%s\' \'%s\'\n",HeadPath,CursorPath); - } - else + if(DoByteComparison) + if((Status=CompareTwoFiles(CursorPath,HeadPath))<0) + return(1); + + /* Do things if the files are found identical (or if + * we thrust the MD5 signature). + */ + if(!DoByteComparison || Status==0) { - if(Verbose>1) - { - fprintf(stderr,"%s: linking %s to %s\n", - ProgName,HeadPath,CursorPath); - } - - /* Perform the linking, if not prohibited. */ - if(!DoNotAct) - { - /* First remode the cursor file. */ - if(unlink(CursorPath)) - { - fprintf(stderr,"%s: can't unlink file %s (%s).\n",ProgName,CursorPath,strerror(errno)); - return(1); - } - FilesystemChanged=1; - - /* Then perform the link to the head file. */ - if(link(HeadPath,CursorPath)) - { - fprintf(stderr,"%s: can't link file %s to file %s (%s); %s file is now lost.\n",ProgName,HeadPath,CursorPath,strerror(errno),CursorPath); - return(1); - } - } + /* This many blocks will be reclaimed. */ + SpaceSaved+=(*CursorFile)->blocks; + + /* Link the files. */ + if(LinkTwoFiles(CursorPath,HeadPath)) + return(1); } } @@ -318,9 +476,9 @@ { if(DoShellScript) fputs("# ",stdout); - printf("%qu blocks %s be reclaimed (%qu kB)\n", + printf("%qu blocks %s reclaimed (%qu kB)\n", SpaceSaved, - DoShellScript||DoNotAct?"will":"were", + DoShellScript||DoNotAct?"will be":"were", SpaceSaved>>1); } @@ -329,6 +487,76 @@ } /**************************************************************************** + * Return a string explaining the failure of a regex operation. Code must * + * be a value that was returned by regcomp() or regexec(). * + ****************************************************************************/ +static const char *RegErrorString(int Code) +{ + int i; + static char MessageBuf[64]; + + /* Search the predefined error codes list until a match is + * found. + */ + for(i=0;RegErrors[i].message!=NULL;i++) + if(RegErrors[i].code==Code) + return(RegErrors[i].message); + + /* If ta predefined messages does not exist then build a custon + * message with the numerical code. + */ + snprintf(MessageBuf,64,"unknown error code %d",Code); + return(MessageBuf); +} + +/**************************************************************************** + * Test a file's complete path against inclusion/exclusion regex filters. * + * A positive value is returned if the file is rejected, 0 if it is * + * accepted, and a negative value means that an error occured. * + ****************************************************************************/ +static int IsFileFiltered(const char *Path) +{ + filter_t *Filter=FiltersBeg; + int Status; + + /* Test against the defined filters. The first match is + * definitive. + */ + while(Filter!=NULL) + { + /* Is this a match? */ + Status=regexec(&Filter->expression,Path,0,NULL,0); + if(Status==0) + return(Filter->type==include?0:1); + + /* Was there an error? */ + if(Status!=REG_NOMATCH) + { + fprintf(stderr,"%s: error while executing a regex (%s).\n",ProgName,RegErrorString(Status)); + return(-1); + } + + /* Next filter. */ + Filter=Filter->next; + } + + /* There was no match, the final status is the inverse of the + * first filter: if it is an inclusion filter then the file is + * rejected, if it an exclusion filter then the file is accepted. + */ + switch(FiltersBeg->type) + { + case include: + return(1); + case exclude: + return(0); + } + + /* This return is there just to please gcc: it is unreachable. */ + return(-1); +} + +/**************************************************************************** * Compute the MD5 signature of a file. We do not use the MD5File() * * function because here we handle signatures as raw binary data in order * * to save some space in the item_t structure. * @@ -336,7 +564,6 @@ static int FileSignature(const char * const Path, const char * const Filename, unsigned char *Signature) { int fd; - unsigned char Buffer[8192]; ssize_t ReadSize; MD5_CTX Context; @@ -350,8 +577,8 @@ /* Initialize the MD5 context and loop over the whole file. */ MD5Init(&Context); - while((ReadSize=read(fd,Buffer,8192))>0) - MD5Update(&Context,Buffer,(unsigned int)ReadSize); + while((ReadSize=read(fd,IObuffer1,OptimalBlockSize))>0) + MD5Update(&Context,IObuffer1,(unsigned int)ReadSize); /* Is this the end of the file or an error? */ close(fd); @@ -489,6 +716,39 @@ /* A plain file. */ case DT_REG: + /* If filters were defined then test that file against + * them. + */ + if(FiltersBeg!=NULL) + { + int Accepted; + + /* Build a full access path to the file for regex + * matching. + */ + strcpy(PathEnd,DirItem->d_name); + + /* Perform the matching. */ + Accepted=!IsFileFiltered(CurPath); + + /* Strip the filename from the path, it's no longer + * needed. + */ + *PathEnd=0; + + /* Pattern matching may fail. */ + if(Accepted<0) + { + closedir(Dir); + return(1); + } + + /* If the file is not accepted then skip it. */ + if(!Accepted) + break; + } + + /* That file will be analysed. */ if(Verbose>2) { fprintf(stderr,"%s: analysing file %s/%s\n",ProgName,CurPath,DirItem->d_name); @@ -506,11 +766,19 @@ FilesCount++; /* This is a plain file it's signature needs to be - * computed. + * computed and some parameters kept. We store the + * plain size of the file and the number of blocks + * used to correctly handle sparse files for reclaimed + * space estimation. */ NewFile->name=FilenameCopy; NewFile->inode=StatBuf.st_ino; NewFile->blocks=StatBuf.st_blocks; + NewFile->size=StatBuf.st_size; + NewFile->perms=StatBuf.st_mode; + NewFile->owner=StatBuf.st_uid; + NewFile->group=StatBuf.st_gid; + NewFile->uflags=StatBuf.st_flags; if(FileSignature(CurPath,DirItem->d_name,NewFile->signature)) { closedir(Dir); @@ -567,6 +835,26 @@ */ Filesystem=StatBuf.st_dev; + /* Keep the preferred IO block size for that file system and + * allocate two buffers of that size. + */ + OptimalBlockSize=StatBuf.st_blksize; + IObuffer1=(unsigned char *)malloc(OptimalBlockSize); + if(IObuffer1==NULL) + { + fprintf(stderr,"%s: not enough memory.\n",ProgName); + free(DirsBeg); + return(1); + } + IObuffer2=(unsigned char *)malloc(OptimalBlockSize); + if(IObuffer2==NULL) + { + fprintf(stderr,"%s: not enough memory.\n",ProgName); + free(IObuffer1); + free(DirsBeg); + return(1); + } + /* Fill-in the root directory structure. */ DirsBeg->name="."; DirsBeg->parent=NULL; @@ -583,18 +871,66 @@ } /**************************************************************************** + * Add a new filter (inclusion or exclusion) to the filters set. * + ****************************************************************************/ +static int AddFilter(filtertype_t Type, const char *Pattern) +{ + filter_t *New; + int Status; + + /* Allocate a new filter structure. */ + New=(filter_t *)malloc(sizeof(filter_t)); + if(New==NULL) + { + fprintf(stderr,"%s: not enuigh memory.\n",ProgName); + return(1); + } + New->type=Type; + New->next=NULL; + + /* Compile the regular expression. If it fails, a predefined error + * message for known error conditions is printed, else the numeric + * status code is reported. + */ + if((Status=regcomp(&New->expression,Pattern,REG_EXTENDED|REG_NOSUB))) + { + fprintf(stderr,"%s: can't compile regex `%s' (%s).\n",ProgName,Pattern,RegErrorString(Status)); + free(New); + return(1); + } + + /* Add the new filter to the filters list. */ + if(FiltersBeg==NULL) + FiltersBeg=FiltersEnd=New; + else + { + FiltersEnd->next=New; + FiltersEnd=New; + } + + /* Ok. */ + return(0); +} + +/**************************************************************************** * Print the program usage informations. * ****************************************************************************/ static void PrintUsage(void) { - fprintf(stderr,"Usage: %s [-ev] [-n|-s] [-x [-x ...]]\n",ProgName); - fputs("\t-e Print reclaimed space estimation\n" + fprintf(stderr,"Usage: %s [-acegopv] [-n|-s] [-i ] [-x ]\n",ProgName); + fputs("\t-a Restrict linking to files of the same status (shorthand for\n" + "\t the -fgop options)\n" + "\t-c Files are byte-to-byte compared before linking\n" + "\t-e Print reclaimed space estimation\n" + "\t-f Restrict linking to files having the same user flags\n" + "\t-g Restrict linking to files of the same group\n" + "\t-i Inclusion regexp\n" "\t-n Do not alter filesystem, imply -e\n" + "\t-o Restrict linking to files of the same ownership\n" + "\t-p Restrict linking to files having the same permissions bits\n" "\t-s Output a shell-script instead of modiying filesystem\n" "\t-v Be verbose\n" -#if 0 - "\t-x Exclusion regexp (not yet)\n" -#endif + "\t-x Exclusion regexp\n" , stderr); } @@ -606,13 +942,39 @@ { int Flag; - while((Flag=getopt(argc,argv,"ensv"))!=-1) + while((Flag=getopt(argc,argv,"acefgi:nopsvx:"))!=-1) switch(Flag) { + /* Turn on all file status restrictions. */ + case 'a': + OnlySameUflags=OnlySameGroup=OnlySameOwner=OnlySamePerms=1; + break; + + /* Byte to byte comparison. */ + case 'c': + DoByteComparison=1; + break; + /* Space estimation. */ case 'e': DoEstimation=1; break; + + /* Same user flags restriction. */ + case 'f': + OnlySameUflags=1; + break; + + /* Same group restriction. */ + case 'g': + OnlySameGroup=1; + break; + + /* Add an inclusion regular expression. */ + case 'i': + if(AddFilter(include,optarg)) + return(1); + break; /* Test run. */ case 'n': @@ -624,6 +986,16 @@ DoNotAct=1; break; + /* Same owner restriction. */ + case 'o': + OnlySameOwner=1; + break; + + /* Same permissions restriction. */ + case 'p': + OnlySamePerms=1; + break; + /* Build a shell script. */ case 's': if(DoNotAct) @@ -637,6 +1009,12 @@ /* Verbosity. */ case 'v': Verbose++; + break; + + /* Add an exclusion regular expression. */ + case 'x': + if(AddFilter(exclude,optarg)) + return(1); break; case '?':