# To unbundle, run this file mkdir rmsame echo rmsame/README sed 's/^X//' >rmsame/README <<'!' cleanname.c is a Plan 9 source file, so cleanname.NOTICE and cleanname.LICENSE apply to it. The terms are fairly liberal; you may redistribute it, modify it or sell it, but read the terms to be certain. ! echo rmsame/cleanname.LICENSE sed 's/^X//' >rmsame/cleanname.LICENSE <<'!' The Plan 9 software is provided under the terms of the Lucent Public License, Version 1.02, reproduced below, with the following exceptions: 1. No right is granted to create derivative works of or X to redistribute (other than with the Plan 9 Operating System) X the screen imprinter fonts identified in subdirectory X /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida X Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans X Typewriter83), identified in subdirectory /sys/lib/postscript/font. X These directories contain material copyrights by B&H Inc. and Y&Y Inc. 2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font X are subject to the GNU GPL, reproduced in the file /LICENSE.gpl. 3. The ghostscript program in the subdirectory /sys/src/cmd/gs is X covered by the Aladdin Free Public License, reproduced in the file X /LICENSE.afpl. X=================================================================== Lucent Public License Version 1.02 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS X"Contribution" means: X a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original X Program, and X b. in the case of each Contributor, X i. changes to the Program, and X ii. additions to the Program; X where such changes and/or additions to the Program were added to the X Program by such Contributor itself or anyone acting on such X Contributor's behalf, and the Contributor explicitly consents, in X accordance with Section 3C, to characterization of the changes and/or X additions as Contributions. X"Contributor" means LUCENT and any other entity that has Contributed a Contribution to the Program. X"Distributor" means a Recipient that distributes the Program, modifications to the Program, or any part thereof. X"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. X"Original Program" means the original version of the software accompanying this Agreement as released by LUCENT, including source code, object code and documentation, if any. X"Program" means the Original Program and Contributions or any part thereof X"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS X a. Subject to the terms of this Agreement, each Contributor hereby X grants Recipient a non-exclusive, worldwide, royalty-free copyright X license to reproduce, prepare derivative works of, publicly display, X publicly perform, distribute and sublicense the Contribution of such X Contributor, if any, and such derivative works, in source code and X object code form. X X b. Subject to the terms of this Agreement, each Contributor hereby X grants Recipient a non-exclusive, worldwide, royalty-free patent X license under Licensed Patents to make, use, sell, offer to sell, X import and otherwise transfer the Contribution of such Contributor, if X any, in source code and object code form. The patent license granted X by a Contributor shall also apply to the combination of the X Contribution of that Contributor and the Program if, at the time the X Contribution is added by the Contributor, such addition of the X Contribution causes such combination to be covered by the Licensed X Patents. The patent license granted by a Contributor shall not apply X to (i) any other combinations which include the Contribution, nor to X (ii) Contributions of other Contributors. No hardware per se is X licensed hereunder. X X c. Recipient understands that although each Contributor grants the X licenses to its Contributions set forth herein, no assurances are X provided by any Contributor that the Program does not infringe the X patent or other intellectual property rights of any other entity. Each X Contributor disclaims any liability to Recipient for claims brought by X any other entity based on infringement of intellectual property rights X or otherwise. As a condition to exercising the rights and licenses X granted hereunder, each Recipient hereby assumes sole responsibility X to secure any other intellectual property rights needed, if any. For X example, if a third party patent license is required to allow X Recipient to distribute the Program, it is Recipient's responsibility X to acquire that license before distributing the Program. X d. Each Contributor represents that to its knowledge it has sufficient X copyright rights in its Contribution, if any, to grant the copyright X license set forth in this Agreement. 3. REQUIREMENTS A. Distributor may choose to distribute the Program in any form under this Agreement or under its own license agreement, provided that: X a. it complies with the terms and conditions of this Agreement; X b. if the Program is distributed in source code or other tangible X form, a copy of this Agreement or Distributor's own license agreement X is included with each copy of the Program; and X c. if distributed under Distributor's own license agreement, such X license agreement: X i. effectively disclaims on behalf of all Contributors all warranties X and conditions, express and implied, including warranties or X conditions of title and non-infringement, and implied warranties or X conditions of merchantability and fitness for a particular purpose; X ii. effectively excludes on behalf of all Contributors all liability X for damages, including direct, indirect, special, incidental and X consequential damages, such as lost profits; and X iii. states that any provisions which differ from this Agreement are X offered by that Contributor alone and not by any other party. B. Each Distributor must include the following in a conspicuous X location in the Program: X Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights X Reserved. C. In addition, each Contributor must identify itself as the originator of its Contribution in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. Also, each Contributor must agree that the additions and/or changes are intended to be a Contribution. Once a Contribution is contributed, it may not thereafter be revoked. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Distributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for Contributors. Therefore, if a Distributor includes the Program in a commercial product offering, such Distributor ("Commercial Distributor") hereby agrees to defend and indemnify every Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively"Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Distributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Distributor in writing of such claim, and b) allow the Commercial Distributor to control, and cooperate with the Commercial Distributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. XFor example, a Distributor might include the Program in a commercial product offering, Product X. That Distributor is then a Commercial Distributor. If that Commercial Distributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Distributor's responsibility alone. Under this section, the Commercial Distributor would have to defend claims against the Contributors related to those performance claims and warranties, and if a court requires any Contributor to pay any damages as a result, the Commercial Distributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. EXPORT CONTROL Recipient agrees that Recipient alone is responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries). 8. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. LUCENT may publish new versions (including revisions) of this Agreement from time to time. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. No one other than LUCENT has the right to modify this Agreement. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. ! echo rmsame/cleanname.NOTICE sed 's/^X//' >rmsame/cleanname.NOTICE <<'!' Copyright © 2002 Lucent Technologies Inc. All Rights Reserved ! echo rmsame/cleanname.c sed 's/^X//' >rmsame/cleanname.c <<'!' X/* X * In place, rewrite name to compress multiple /, eliminate ., and process .. X */ X#define SEP(x) ((x)=='/' || (x) == 0) char* cleanname(char *name) X{ X char *p, *q, *dotdot; X int rooted; X rooted = name[0] == '/'; X /* X * invariants: X * p points at beginning of path element we're considering. X * q points just past the last path element we wrote (no slash). X * dotdot points just past the point where .. cannot backtrack X * any further (no slash). X */ X p = q = dotdot = name+rooted; X while(*p) { X if(p[0] == '/') /* null element */ X p++; X else if(p[0] == '.' && SEP(p[1])) X p += 1; /* don't count the separator in case it is nul */ X else if(p[0] == '.' && p[1] == '.' && SEP(p[2])) { X p += 2; X if(q > dotdot) { /* can backtrack */ X while(--q > dotdot && *q != '/') X ; X } else if(!rooted) { /* /.. is / but ./../ is .. */ X if(q != name) X *q++ = '/'; X *q++ = '.'; X *q++ = '.'; X dotdot = q; X } X } else { /* real path element */ X if(q != name+rooted) X *q++ = '/'; X while((*q = *p) != '/' && *q != 0) X p++, q++; X } X } X if(q == name) /* empty string is really ``.'' */ X *q++ = '.'; X *q = '\0'; X return name; X} ! echo rmsame/mkfile sed 's/^X//' >rmsame/mkfile <<'!' Xrmsame/rmsame.c <<'!' X/* X * rmsame [-npsv] directory - remove files in . identical to those in directory. X * X * rmsame recursively descends the current directory subtree and X * removes any files that are identical to files present in the X * comparison directory. rmsame refuses to "rmsame ." ("rm -rf ." is X * faster and has the same effect). symbolic links to directories are X * not followed. In cases of doubt, the file is left alone. X * X * changed to read an entire directory's contents first, then process files, X * instead of reading one directory entry and then processing it. X * since rmsame may remove files as it runs, this helps to avoid missing X * directory entries. X * by Geoff Collyer, April 1996 X * Copyright (C) 1986,1987 Ron Wessels X * based on a shell script by Geoff Collyer X * X * Permission is hereby granted for general usage and re-distribution of X * this program as long as: X * a) it is not sold for profit X * b) the author's name and this notice remain intact in the program X */ X#include X#include X#define STREQ(a, b) (*(a) == *(b) && strcmp(a, b) == 0) X#ifndef MAXPATHLEN X#define MAXPATHLEN 1024 X#endif enum { X Differ = 0, X Same, X Missing, X Ignore, X /* X * size of comparison buffers: too large means reading blocks X * unnecessarily when files differ early on; too small means X * many reads when files are identical. X */ X Bufsize = 4*1024, X}; extern char* cleanname(char*); char *argv0 = "rmsame"; typedef struct dirname Dirname; struct dirname { X char *name; X Dirname *next; X}; typedef struct { X Dirname *dirnm; X int opened; X int count; X} Rddirres; static char cmpfile[MAXPATHLEN+1]; X/* X * holds full path of current directory at all times. X * used for error messages, not traversal. X */ static char fullcurrdir[MAXPATHLEN+1]; X/* X * always contains current directory, but may be just the name of the last X * component or may be the full path, depending upon the slow flag. X */ static char currdir[MAXPATHLEN+1]; static Dir filestat, cmpstat; static Dir *filedir, *cmpdir, *dotdir; X/* X * Execution flags. X */ static int verbose = 0; /* print out progress */ X/* X * don't use "..". no longer needed on Plan 9 & X * probably only rarely needed on Unix X */ static int slow = 0; static int prdiff, prsame, dontrm; static void rmsame(void); static int cmpplain(char *file1name, char *file2name); int samefiledirs(Dir *da, Dir *db) X{ X return da != nil && db != nil && X da->qid.type == db->qid.type && X da->qid.path == db->qid.path && X da->qid.vers == db->qid.vers && X da->dev == db->dev && X da->type == db->type; X} int samefile(char *a, char *b) X{ X int ret; X Dir *da, *db; X if(strcmp(a, b) == 0) X return 1; X da = dirstat(a); X db = dirstat(b); X ret = samefiledirs(da, db); X free(da); X free(db); X return ret; X} static int iscurrdir(char *dir, Dir *dotstp) X{ X USED(dotstp); X return samefile(dir, "."); X} static int chgdir(char *dir) X{ X int ret; X char okname[MAXPATHLEN+1]; X if (dir[0] == '#') { /* beware loonie component names on plan 9 */ X sprint(okname, "./%s", dir); X dir = okname; X } X ret = chdir(dir); X if (ret < 0) X fprint(2, "%s: chdir(%s) in %s: %r\n", argv0, dir, fullcurrdir); X return ret; X} X/* X * Get an absolute path for the comparison directory, X * and make sure it is not ".". X */ static void setup(char *dirname) X{ X dotdir = dirstat("."); X if (dotdir == nil) X sysfatal("stat of . at start: %r"); X if (verbose) X fprint(2, "getting current directory\n"); X if (getwd(currdir, sizeof currdir) == nil) { X /* sometimes life is just too complex for getwd */ X char *pwd = getenv("PWD"); X fprint(2, "%s: getcwd failed\n", argv0); X if (pwd != nil && iscurrdir(pwd, dotdir)) { X strncpy(currdir, pwd, sizeof currdir); X /* getcwd may have changed curr. dir. */ X if (chgdir(pwd) < 0) X sysfatal("can't chdir to initial dir"); X } else X sysfatal("can't get current dir"); X } X strcpy(fullcurrdir, currdir); X cmpfile[0] = '\0'; X if (dirname[0] != '/') { X strcat(cmpfile, currdir); X strcat(cmpfile, "/"); X } X strcat(cmpfile, dirname); X cleanname(cmpfile); X cmpdir = dirstat(cmpfile); X if (cmpdir == nil) X sysfatal("stat(%s) in %s: %r", cmpfile, fullcurrdir); X if (!(cmpdir->mode&DMDIR)) X sysfatal("%s: not a directory", cmpfile); X if (iscurrdir(cmpfile, dotdir)) X sysfatal("%s is \".\"", dirname); X} void main(int argc, char *argv[]) X{ X int errflag = 0; X ARGBEGIN { X case 'n': X dontrm = prdiff = 1; X break; X case 'p': X dontrm = prsame = 1; X break; X case 's': X slow++; /* but robust */ X break; X case 'v': X verbose++; X break; X case '?': X default: X errflag++; X break; X } ARGEND; X if (errflag || argc != 1) X sysfatal("Usage: %s [-npsv] directory", argv0); X setup(argv[0]); X if (verbose) X fprint(2, "beginning descent\n"); X rmsame(); X exits(0); X} static int dispose(char *compname, int same) X{ X int ret = 0; X if (dontrm) { X if (prdiff && (same == Differ || Same == Missing) || X prsame && same == Same) X print("%s\n", cmpfile); X } else if (same == Same) { X ret = remove(compname); X if (ret < 0) X fprint(2, "%s: unlink(%s) in %s: %r\n", X argv0, compname, fullcurrdir); X else if (verbose) X print("rm %s\n", compname); X } X return ret; X} static Rddirres readalldir(char *name) X{ X int dir, n; X Rddirres res; X Dir *dirs, *dp; X Dirname *newdir, *curdir = nil; X werrstr(""); X res.dirnm = nil; X res.opened = res.count = 0; X dir = open(name, OREAD); X if (dir < 0) X return res; X res.opened++; X /* read the whole directory into memory */ X while ((n = dirread(dir, &dirs)) > 0) { X for (dp = dirs; n-- > 0; dp++) { X newdir = malloc(sizeof *newdir); X if (newdir == nil) X abort(); X newdir->name = strdup(dp->name); X newdir->next = nil; X if (res.dirnm == nil) X res.dirnm = newdir; X else X curdir->next = newdir; X curdir = newdir; X res.count++; X } X free(dirs); X } X close(dir); X return res; X} static void freealldir(Dirname *alldir) X{ X Dirname *next, *dp; X for (dp = alldir; dp != nil; dp = next) { X next = dp->next; X dp->next = nil; X free(dp->name); X dp->name = nil; X free(dp); X } X} X/* X * Recursively rmsame the sub-directory. X */ static int samedir(char *compname, char *cdp) X{ X char *dirname; X *cdp = '/'; X strcpy(cdp+1, compname); /* maintain full path to . */ X if (slow) X dirname = fullcurrdir; X else X dirname = compname; X strcpy(currdir, dirname); X if (verbose) X fprint(2, "cd %s\n", dirname); X if (chgdir(dirname) < 0) /* descend */ X return Differ; /* no perms, perhaps */ X rmsame(); X *cdp = '\0'; /* maintain full path to . */ X if (!slow) X dirname = ".."; X if (verbose) X fprint(2, "cd %s\n", dirname); X if (chgdir(dirname) < 0) /* climb back up */ X exits("chdir failed: %r"); /* something is ill */ X /* Remove the directory entry if empty. */ X if (!dontrm && remove(compname) == 0 && verbose) X fprint(2, "rmdir %s\n", compname); X return Ignore; X} static int compare(char *compname, char *cmpname, char *cdp) X{ X /* X * skip to next file if: ".", "..", either is non-existent, X * differing file types, or matching device & i-number X * (which means we got tricked by symbolic links or namespace X * mapping) and link count is 1 (the last link). X */ X if (STREQ(compname, ".") || STREQ(compname, "..")) X return Ignore; X strcpy(cmpname, compname); X free(filedir); X filedir = dirstat(compname); X if (filedir == nil) { /* vanished since dirread? */ X fprint(2, "stat(%s) in %s: %r\n", compname, fullcurrdir); X return Missing; X } X free(cmpdir); X cmpdir = dirstat(cmpfile); X if (cmpdir == nil) /* not in other tree */ X return Missing; X if ((filedir->mode&DMDIR) != (cmpdir->mode&DMDIR) || X samefiledirs(filedir, cmpdir)) { X if (verbose) X print("ignoring %s\n", compname); X return Differ; X } X switch (filedir->mode&DMDIR) { X case DMDIR: X /* Recursively rmsame the sub-directory. */ X return samedir(compname, cdp); X case 0: X return filedir->length == cmpdir->length && X cmpplain(compname, cmpfile) == Same; X } X} static void examine(char *compname, char *cmpname, char *cdp) X{ X char okname[MAXPATHLEN+1]; X if (compname[0] == '#') { /* beware loonie component names on plan 9 */ X sprint(okname, "./%s", compname); X compname = okname; X } X if (verbose) X fprint(2, "name: %s\n", compname); X dispose(compname, compare(compname, cmpname, cdp)); X} static void rmsame(void) X{ X char *cmpname, *cdp; X Dirname *dp; X Rddirres dirres; X cmpname = &cmpfile[strlen(cmpfile)]; X *cmpname++ = '/'; X cdp = fullcurrdir + strlen(fullcurrdir); X dirres = readalldir("."); X if (!dirres.opened) { X fprint(2, "%s: opendir(.) in %s: %r\n", argv0, fullcurrdir); X /* X * this is probably a permission problem, so give up on this X * directory but keep going. X */ X } else { X for (dp = dirres.dirnm; dp != nil; dp = dp->next) X examine(dp->name, cmpname, cdp); X freealldir(dirres.dirnm); X } X *cmpname = '\0'; X} X/* read with warning if error */ static int wread(int fd, void *buf, long len, char *name) X{ X int bytes = read(fd, buf, len); X if (bytes < 0) X fprint(2, "%s: read(%s) in %s: %r\n", argv0, name, X fullcurrdir); X return bytes; X} X/* open with warning if error */ static int wopen(char *name, int mode) X{ X int fd = open(name, mode); X if (fd < 0) X fprint(2, "%s: open(%s) in %s: %r\n", argv0, name, X fullcurrdir); X return fd; X} static int cmpfds(char *name1, char *name2, int fd1, int fd2) X{ X int cnt1, cnt2; X char buf1[Bufsize], buf2[Bufsize]; X if (fd1 < 0 || fd2 < 0) X return Differ; X do { X cnt1 = wread(fd1, buf1, sizeof buf1, name1); X cnt2 = wread(fd2, buf2, sizeof buf2, name2); X if (cnt1 < 0 || cnt2 < 0) X return Differ; X if (cnt1 != cnt2) { /* can't happen */ X fprint(2, X "%s: read differing amounts from %s and %s\n", X argv0, name1, name2); X return Differ; X } X if (cnt1 == 0) /* both at EOF? */ X return Same; X } while (memcmp(buf1, buf2, cnt1) == 0); X return Differ; X} static int cmpplain(char *name1, char *name2) X{ X int fd1, fd2, result = Differ; X fd1 = wopen(name1, OREAD); X fd2 = wopen(name2, OREAD); X if (fd1 >= 0 && fd2 >= 0) X result = cmpfds(name1, name2, fd1, fd2); X if (fd1 >= 0) X close(fd1); X if (fd2 >= 0) X close(fd2); X return result; X} ! echo rmsame/rmsame.man sed 's/^X//' >rmsame/rmsame.man <<'!' X.TH RMSAME 1 X.SH NAME rmsame \- remove identical files X.SH SYNOPSIS X.B rmsame X[ X.B \-npsv X] X.I directory X.SH DESCRIPTION X.B rmsame will recursively compare files from the current directory X(and below) with corresponding files from the X.I directory argument. When files are identical, the local copy is deleted. After a subdirectory has been processed, it is removed if it is empty. X.PP This program is useful when maintaining a system that is constantly receiving new software releases. Instead of installing local revisions repeatedly into the new releases, one could load a new release into a scratch disk and run rmsame to leave only those files that have changed. These can then be examined and processed manually. X.SH OPTIONS X.PD 0 X.TP 5 X.B \-n don't remove any files, just print the names of those which differ from those under X.IR directory . X.TP 5 X.B \-p don't remove any files, just print the names of files identical to those under X.IR directory . X.TP 5 X.B \-s traverse directory trees slowly but reliably. X.TP 5 X.B \-v show the processing as it is taking place X(normally processing is silent except for error messages). X.PD X.SH "SEE ALSO" X.IR cmp (1), X.IR rm (1), X.IR rmdir (1) X.SH DIAGNOSTICS X.TP 5 X\fIname\fP is "." The directory given as the comparison directory is also the current directory. The command "rm\ \-r\ ." has the same effect, and is much faster. X.SH BUGS If the directory structure is too deep, the program runs out of file descriptors and exits. X.PP A symbolic link is considered to be only identical to another symbolic link pointing to the same name. If the names are different, no check is made to see if the paths are really the same. X.SH AUTHORS The original X.B rmsame was a shell script written by Geoff\ Collyer. It was later re-written as a C program by Ron\ Wessels. !