cp.c revision 99744
11556Srgrimes/* 21556Srgrimes * Copyright (c) 1988, 1993, 1994 31556Srgrimes * The Regents of the University of California. All rights reserved. 41556Srgrimes * 51556Srgrimes * This code is derived from software contributed to Berkeley by 61556Srgrimes * David Hitz of Auspex Systems Inc. 71556Srgrimes * 81556Srgrimes * Redistribution and use in source and binary forms, with or without 91556Srgrimes * modification, are permitted provided that the following conditions 101556Srgrimes * are met: 111556Srgrimes * 1. Redistributions of source code must retain the above copyright 121556Srgrimes * notice, this list of conditions and the following disclaimer. 131556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 141556Srgrimes * notice, this list of conditions and the following disclaimer in the 151556Srgrimes * documentation and/or other materials provided with the distribution. 161556Srgrimes * 3. All advertising materials mentioning features or use of this software 171556Srgrimes * must display the following acknowledgement: 181556Srgrimes * This product includes software developed by the University of 191556Srgrimes * California, Berkeley and its contributors. 201556Srgrimes * 4. Neither the name of the University nor the names of its contributors 211556Srgrimes * may be used to endorse or promote products derived from this software 221556Srgrimes * without specific prior written permission. 231556Srgrimes * 241556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 251556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 261556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 271556Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 281556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 291556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 301556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 311556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 321556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 331556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 341556Srgrimes * SUCH DAMAGE. 351556Srgrimes */ 361556Srgrimes 371556Srgrimes#ifndef lint 3820412Sstevestatic char const copyright[] = 391556Srgrimes"@(#) Copyright (c) 1988, 1993, 1994\n\ 401556Srgrimes The Regents of the University of California. All rights reserved.\n"; 411556Srgrimes#endif /* not lint */ 421556Srgrimes 431556Srgrimes#ifndef lint 4435773Scharnier#if 0 4536003Scharnierstatic char sccsid[] = "@(#)cp.c 8.2 (Berkeley) 4/1/94"; 4635773Scharnier#endif 471556Srgrimes#endif /* not lint */ 4899109Sobrien#include <sys/cdefs.h> 4999109Sobrien__FBSDID("$FreeBSD: head/bin/cp/cp.c 99744 2002-07-10 20:44:55Z dillon $"); 501556Srgrimes 511556Srgrimes/* 521556Srgrimes * Cp copies source files to target files. 538855Srgrimes * 541556Srgrimes * The global PATH_T structure "to" always contains the path to the 551556Srgrimes * current target file. Since fts(3) does not change directories, 5620412Ssteve * this path can be either absolute or dot-relative. 578855Srgrimes * 581556Srgrimes * The basic algorithm is to initialize "to" and use fts(3) to traverse 591556Srgrimes * the file hierarchy rooted in the argument list. A trivial case is the 601556Srgrimes * case of 'cp file1 file2'. The more interesting case is the case of 611556Srgrimes * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the 621556Srgrimes * path (relative to the root of the traversal) is appended to dir (stored 631556Srgrimes * in "to") to form the final target path. 641556Srgrimes */ 651556Srgrimes 661556Srgrimes#include <sys/param.h> 671556Srgrimes#include <sys/stat.h> 681556Srgrimes 691556Srgrimes#include <err.h> 701556Srgrimes#include <errno.h> 711556Srgrimes#include <fts.h> 7276693Simp#include <limits.h> 7350381Smharo#include <stdio.h> 7478469Sdes#include <stdlib.h> 751556Srgrimes#include <string.h> 761556Srgrimes#include <unistd.h> 771556Srgrimes 781556Srgrimes#include "extern.h" 791556Srgrimes 801556Srgrimes#define STRIP_TRAILING_SLASH(p) { \ 815292Sbde while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ 821556Srgrimes *--(p).p_end = 0; \ 831556Srgrimes} 841556Srgrimes 8591087Smarkmstatic char emptystring[] = ""; 861556Srgrimes 8791087SmarkmPATH_T to = { to.p_path, emptystring, "" }; 881556Srgrimes 8991087Smarkmint iflag, pflag, fflag; 9091087Smarkmstatic int Rflag, rflag, vflag; 9191087Smarkm 921556Srgrimesenum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; 931556Srgrimes 9499363Smarkmstatic int copy(char *[], enum op, int); 9599363Smarkmstatic int mastercmp(const FTSENT **, const FTSENT **); 961556Srgrimes 971556Srgrimesint 9890107Simpmain(int argc, char *argv[]) 991556Srgrimes{ 1001556Srgrimes struct stat to_stat, tmp_stat; 1011556Srgrimes enum op type; 10296808Sache int Hflag, Lflag, Pflag, ch, fts_options, r, have_trailing_slash; 10396809Sache char *target; 1041556Srgrimes 10514157Spst Hflag = Lflag = Pflag = 0; 10650381Smharo while ((ch = getopt(argc, argv, "HLPRfiprv")) != -1) 1071556Srgrimes switch (ch) { 1081556Srgrimes case 'H': 1091556Srgrimes Hflag = 1; 1101556Srgrimes Lflag = Pflag = 0; 1111556Srgrimes break; 1121556Srgrimes case 'L': 1131556Srgrimes Lflag = 1; 1141556Srgrimes Hflag = Pflag = 0; 1151556Srgrimes break; 1161556Srgrimes case 'P': 1171556Srgrimes Pflag = 1; 1181556Srgrimes Hflag = Lflag = 0; 1191556Srgrimes break; 1201556Srgrimes case 'R': 1211556Srgrimes Rflag = 1; 1221556Srgrimes break; 1231556Srgrimes case 'f': 12414416Swosch fflag = 1; 1251556Srgrimes iflag = 0; 1261556Srgrimes break; 1271556Srgrimes case 'i': 12814416Swosch iflag = 1; 12914416Swosch fflag = 0; 1301556Srgrimes break; 1311556Srgrimes case 'p': 1321556Srgrimes pflag = 1; 1331556Srgrimes break; 1341556Srgrimes case 'r': 1351556Srgrimes rflag = 1; 1361556Srgrimes break; 13750381Smharo case 'v': 13850381Smharo vflag = 1; 13950381Smharo break; 1401556Srgrimes default: 1411556Srgrimes usage(); 1421556Srgrimes break; 1431556Srgrimes } 1441556Srgrimes argc -= optind; 1451556Srgrimes argv += optind; 1461556Srgrimes 1471556Srgrimes if (argc < 2) 1481556Srgrimes usage(); 1491556Srgrimes 1501556Srgrimes fts_options = FTS_NOCHDIR | FTS_PHYSICAL; 1511556Srgrimes if (rflag) { 1521556Srgrimes if (Rflag) 1531556Srgrimes errx(1, 1541556Srgrimes "the -R and -r options may not be specified together."); 1551556Srgrimes if (Hflag || Lflag || Pflag) 1561556Srgrimes errx(1, 1571556Srgrimes "the -H, -L, and -P options may not be specified with the -r option."); 1581556Srgrimes fts_options &= ~FTS_PHYSICAL; 1591556Srgrimes fts_options |= FTS_LOGICAL; 1601556Srgrimes } 1611556Srgrimes if (Rflag) { 1621556Srgrimes if (Hflag) 1631556Srgrimes fts_options |= FTS_COMFOLLOW; 1641556Srgrimes if (Lflag) { 1651556Srgrimes fts_options &= ~FTS_PHYSICAL; 1661556Srgrimes fts_options |= FTS_LOGICAL; 1671556Srgrimes } 1681556Srgrimes } else { 1691556Srgrimes fts_options &= ~FTS_PHYSICAL; 17098171Stjr fts_options |= FTS_LOGICAL | FTS_COMFOLLOW; 1711556Srgrimes } 1721556Srgrimes 1731556Srgrimes /* Save the target base in "to". */ 1741556Srgrimes target = argv[--argc]; 17576693Simp if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path)) 1761556Srgrimes errx(1, "%s: name too long", target); 1771556Srgrimes to.p_end = to.p_path + strlen(to.p_path); 1781556Srgrimes if (to.p_path == to.p_end) { 1791556Srgrimes *to.p_end++ = '.'; 1801556Srgrimes *to.p_end = 0; 1811556Srgrimes } 18296809Sache have_trailing_slash = (to.p_end[-1] == '/'); 18396809Sache if (have_trailing_slash) 18496809Sache STRIP_TRAILING_SLASH(to); 1851556Srgrimes to.target_end = to.p_end; 1861556Srgrimes 1871556Srgrimes /* Set end of argument list for fts(3). */ 1888855Srgrimes argv[argc] = NULL; 1898855Srgrimes 1901556Srgrimes /* 1911556Srgrimes * Cp has two distinct cases: 1921556Srgrimes * 1931556Srgrimes * cp [-R] source target 1941556Srgrimes * cp [-R] source1 ... sourceN directory 1951556Srgrimes * 1961556Srgrimes * In both cases, source can be either a file or a directory. 1971556Srgrimes * 1981556Srgrimes * In (1), the target becomes a copy of the source. That is, if the 1991556Srgrimes * source is a file, the target will be a file, and likewise for 2001556Srgrimes * directories. 2011556Srgrimes * 2021556Srgrimes * In (2), the real target is not directory, but "directory/source". 2031556Srgrimes */ 2041556Srgrimes r = stat(to.p_path, &to_stat); 2051556Srgrimes if (r == -1 && errno != ENOENT) 2061556Srgrimes err(1, "%s", to.p_path); 2071556Srgrimes if (r == -1 || !S_ISDIR(to_stat.st_mode)) { 2081556Srgrimes /* 2091556Srgrimes * Case (1). Target is not a directory. 2108855Srgrimes */ 2111556Srgrimes if (argc > 1) { 2121556Srgrimes usage(); 2131556Srgrimes exit(1); 2141556Srgrimes } 2151556Srgrimes /* 2161556Srgrimes * Need to detect the case: 2171556Srgrimes * cp -R dir foo 2181556Srgrimes * Where dir is a directory and foo does not exist, where 2191556Srgrimes * we want pathname concatenations turned on but not for 2201556Srgrimes * the initial mkdir(). 2211556Srgrimes */ 2221556Srgrimes if (r == -1) { 2231556Srgrimes if (rflag || (Rflag && (Lflag || Hflag))) 2241556Srgrimes stat(*argv, &tmp_stat); 2251556Srgrimes else 2261556Srgrimes lstat(*argv, &tmp_stat); 2278855Srgrimes 2281556Srgrimes if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag)) 2291556Srgrimes type = DIR_TO_DNE; 2301556Srgrimes else 2311556Srgrimes type = FILE_TO_FILE; 2321556Srgrimes } else 2331556Srgrimes type = FILE_TO_FILE; 23496808Sache 23596808Sache if (have_trailing_slash && type == FILE_TO_FILE) { 23696808Sache if (r == -1) 23796808Sache errx(1, "directory %s does not exist", 23896808Sache to.p_path); 23996808Sache else 24096808Sache errx(1, "%s is not a directory", to.p_path); 24196808Sache } 2421556Srgrimes } else 2431556Srgrimes /* 2441556Srgrimes * Case (2). Target is a directory. 2451556Srgrimes */ 2461556Srgrimes type = FILE_TO_DIR; 2471556Srgrimes 2481556Srgrimes exit (copy(argv, type, fts_options)); 2491556Srgrimes} 2501556Srgrimes 2511556Srgrimesint 25290107Simpcopy(char *argv[], enum op type, int fts_options) 2531556Srgrimes{ 2541556Srgrimes struct stat to_stat; 2551556Srgrimes FTS *ftsp; 2561556Srgrimes FTSENT *curr; 25791087Smarkm int base = 0, dne, badcp, rval; 25891087Smarkm size_t nlen; 2595292Sbde char *p, *target_mid; 26088439Smckay mode_t mask, mode; 2611556Srgrimes 26287655Smckay /* 26387655Smckay * Keep an inverted copy of the umask, for use in correcting 26487655Smckay * permissions on created directories when not using -p. 26587655Smckay */ 26687655Smckay mask = ~umask(0777); 26787655Smckay umask(~mask); 26887655Smckay 2691556Srgrimes if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL) 27099744Sdillon err(1, "fts_open"); 27153819Smharo for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) { 2721556Srgrimes switch (curr->fts_info) { 2731556Srgrimes case FTS_NS: 27436812Sdt case FTS_DNR: 2751556Srgrimes case FTS_ERR: 2761556Srgrimes warnx("%s: %s", 2771556Srgrimes curr->fts_path, strerror(curr->fts_errno)); 27853819Smharo badcp = rval = 1; 2791556Srgrimes continue; 2801556Srgrimes case FTS_DC: /* Warn, continue. */ 2811556Srgrimes warnx("%s: directory causes a cycle", curr->fts_path); 28253819Smharo badcp = rval = 1; 2831556Srgrimes continue; 28491087Smarkm default: 28596371Salfred ; 2861556Srgrimes } 2871556Srgrimes 2881556Srgrimes /* 2898855Srgrimes * If we are in case (2) or (3) above, we need to append the 2908855Srgrimes * source name to the target name. 2911556Srgrimes */ 2921556Srgrimes if (type != FILE_TO_FILE) { 2931556Srgrimes /* 2941556Srgrimes * Need to remember the roots of traversals to create 2951556Srgrimes * correct pathnames. If there's a directory being 2961556Srgrimes * copied to a non-existent directory, e.g. 2971556Srgrimes * cp -R a/dir noexist 2981556Srgrimes * the resulting path name should be noexist/foo, not 2991556Srgrimes * noexist/dir/foo (where foo is a file in dir), which 3001556Srgrimes * is the case where the target exists. 3011556Srgrimes * 3021556Srgrimes * Also, check for "..". This is for correct path 30346684Skris * concatenation for paths ending in "..", e.g. 3041556Srgrimes * cp -R .. /tmp 3051556Srgrimes * Paths ending in ".." are changed to ".". This is 3061556Srgrimes * tricky, but seems the easiest way to fix the problem. 3071556Srgrimes * 3081556Srgrimes * XXX 3091556Srgrimes * Since the first level MUST be FTS_ROOTLEVEL, base 3101556Srgrimes * is always initialized. 3111556Srgrimes */ 31246073Simp if (curr->fts_level == FTS_ROOTLEVEL) { 3131556Srgrimes if (type != DIR_TO_DNE) { 3141556Srgrimes p = strrchr(curr->fts_path, '/'); 3158855Srgrimes base = (p == NULL) ? 0 : 3161556Srgrimes (int)(p - curr->fts_path + 1); 3171556Srgrimes 3188855Srgrimes if (!strcmp(&curr->fts_path[base], 3191556Srgrimes "..")) 3201556Srgrimes base += 1; 3211556Srgrimes } else 3221556Srgrimes base = curr->fts_pathlen; 32346073Simp } 3241556Srgrimes 3251556Srgrimes p = &curr->fts_path[base]; 3261556Srgrimes nlen = curr->fts_pathlen - base; 3275292Sbde target_mid = to.target_end; 3285292Sbde if (*p != '/' && target_mid[-1] != '/') 3295292Sbde *target_mid++ = '/'; 3305292Sbde *target_mid = 0; 33176693Simp if (target_mid - to.p_path + nlen >= PATH_MAX) { 3328855Srgrimes warnx("%s%s: name too long (not copied)", 3335292Sbde to.p_path, p); 33453819Smharo badcp = rval = 1; 3355292Sbde continue; 3365292Sbde } 3375292Sbde (void)strncat(target_mid, p, nlen); 3385292Sbde to.p_end = target_mid + nlen; 3391556Srgrimes *to.p_end = 0; 3401556Srgrimes STRIP_TRAILING_SLASH(to); 3411556Srgrimes } 3421556Srgrimes 34387655Smckay if (curr->fts_info == FTS_DP) { 34487655Smckay /* 34588755Smckay * We are nearly finished with this directory. If we 34688755Smckay * didn't actually copy it, or otherwise don't need to 34788755Smckay * change its attributes, then we are done. 34887655Smckay */ 34988439Smckay if (!curr->fts_number) 35088439Smckay continue; 35188439Smckay /* 35288439Smckay * If -p is in effect, set all the attributes. 35388439Smckay * Otherwise, set the correct permissions, limited 35488755Smckay * by the umask. Optimise by avoiding a chmod() 35588755Smckay * if possible (which is usually the case if we 35688755Smckay * made the directory). Note that mkdir() does not 35788755Smckay * honour setuid, setgid and sticky bits, but we 35888755Smckay * normally want to preserve them on directories. 35988439Smckay */ 36087655Smckay if (pflag) 36187655Smckay rval = setfile(curr->fts_statp, 0); 36288439Smckay else { 36388439Smckay mode = curr->fts_statp->st_mode; 36488439Smckay if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) || 36588439Smckay ((mode | S_IRWXU) & mask) != (mode & mask)) 36688439Smckay if (chmod(to.p_path, mode & mask) != 0){ 36788439Smckay warn("chmod: %s", to.p_path); 36888439Smckay rval = 1; 36988439Smckay } 37087655Smckay } 37187655Smckay continue; 37287655Smckay } 37387655Smckay 3741556Srgrimes /* Not an error but need to remember it happened */ 3751556Srgrimes if (stat(to.p_path, &to_stat) == -1) 3761556Srgrimes dne = 1; 3771556Srgrimes else { 3781556Srgrimes if (to_stat.st_dev == curr->fts_statp->st_dev && 3791556Srgrimes to_stat.st_ino == curr->fts_statp->st_ino) { 3801556Srgrimes warnx("%s and %s are identical (not copied).", 3811556Srgrimes to.p_path, curr->fts_path); 38253819Smharo badcp = rval = 1; 3831556Srgrimes if (S_ISDIR(curr->fts_statp->st_mode)) 3841556Srgrimes (void)fts_set(ftsp, curr, FTS_SKIP); 3851556Srgrimes continue; 3861556Srgrimes } 38720412Ssteve if (!S_ISDIR(curr->fts_statp->st_mode) && 38820412Ssteve S_ISDIR(to_stat.st_mode)) { 38991087Smarkm warnx("cannot overwrite directory %s with " 39091087Smarkm "non-directory %s", 39120412Ssteve to.p_path, curr->fts_path); 39253819Smharo badcp = rval = 1; 39320412Ssteve continue; 39420412Ssteve } 3951556Srgrimes dne = 0; 3961556Srgrimes } 3971556Srgrimes 3981556Srgrimes switch (curr->fts_statp->st_mode & S_IFMT) { 3991556Srgrimes case S_IFLNK: 40098171Stjr /* Catch special case of a non-dangling symlink */ 40198171Stjr if ((fts_options & FTS_LOGICAL) || 40298171Stjr ((fts_options & FTS_COMFOLLOW) && 40398171Stjr curr->fts_level == 0)) { 40498171Stjr if (copy_file(curr, dne)) 40598171Stjr badcp = rval = 1; 40698171Stjr } else { 40798171Stjr if (copy_link(curr, !dne)) 40898171Stjr badcp = rval = 1; 40998171Stjr } 4101556Srgrimes break; 4111556Srgrimes case S_IFDIR: 4121556Srgrimes if (!Rflag && !rflag) { 4131556Srgrimes warnx("%s is a directory (not copied).", 4141556Srgrimes curr->fts_path); 4151556Srgrimes (void)fts_set(ftsp, curr, FTS_SKIP); 41653819Smharo badcp = rval = 1; 4171556Srgrimes break; 4181556Srgrimes } 4191556Srgrimes /* 4201556Srgrimes * If the directory doesn't exist, create the new 4211556Srgrimes * one with the from file mode plus owner RWX bits, 4221556Srgrimes * modified by the umask. Trade-off between being 4231556Srgrimes * able to write the directory (if from directory is 4241556Srgrimes * 555) and not causing a permissions race. If the 4251556Srgrimes * umask blocks owner writes, we fail.. 4261556Srgrimes */ 4271556Srgrimes if (dne) { 4288855Srgrimes if (mkdir(to.p_path, 4291556Srgrimes curr->fts_statp->st_mode | S_IRWXU) < 0) 4301556Srgrimes err(1, "%s", to.p_path); 4311556Srgrimes } else if (!S_ISDIR(to_stat.st_mode)) { 4321556Srgrimes errno = ENOTDIR; 4335879Sdg err(1, "%s", to.p_path); 4341556Srgrimes } 4351556Srgrimes /* 43688439Smckay * Arrange to correct directory attributes later 43787655Smckay * (in the post-order phase) if this is a new 43888439Smckay * directory, or if the -p flag is in effect. 4391556Srgrimes */ 44088439Smckay curr->fts_number = pflag || dne; 4411556Srgrimes break; 4421556Srgrimes case S_IFBLK: 4431556Srgrimes case S_IFCHR: 4441556Srgrimes if (Rflag) { 4451556Srgrimes if (copy_special(curr->fts_statp, !dne)) 44653819Smharo badcp = rval = 1; 4477572Sbde } else { 4481556Srgrimes if (copy_file(curr, dne)) 44953819Smharo badcp = rval = 1; 4507572Sbde } 4511556Srgrimes break; 4521556Srgrimes case S_IFIFO: 4537572Sbde if (Rflag) { 4541556Srgrimes if (copy_fifo(curr->fts_statp, !dne)) 45553819Smharo badcp = rval = 1; 4567572Sbde } else { 4571556Srgrimes if (copy_file(curr, dne)) 45853819Smharo badcp = rval = 1; 4597572Sbde } 4601556Srgrimes break; 4611556Srgrimes default: 4621556Srgrimes if (copy_file(curr, dne)) 46353819Smharo badcp = rval = 1; 4641556Srgrimes break; 4651556Srgrimes } 46653819Smharo if (vflag && !badcp) 46750543Smharo (void)printf("%s -> %s\n", curr->fts_path, to.p_path); 4681556Srgrimes } 4691556Srgrimes if (errno) 4701556Srgrimes err(1, "fts_read"); 4711556Srgrimes return (rval); 4721556Srgrimes} 4731556Srgrimes 4741556Srgrimes/* 4751556Srgrimes * mastercmp -- 4761556Srgrimes * The comparison function for the copy order. The order is to copy 4771556Srgrimes * non-directory files before directory files. The reason for this 4781556Srgrimes * is because files tend to be in the same cylinder group as their 4791556Srgrimes * parent directory, whereas directories tend not to be. Copying the 4801556Srgrimes * files first reduces seeking. 4811556Srgrimes */ 4821556Srgrimesint 48390107Simpmastercmp(const FTSENT **a, const FTSENT **b) 4841556Srgrimes{ 4851556Srgrimes int a_info, b_info; 4861556Srgrimes 4871556Srgrimes a_info = (*a)->fts_info; 4881556Srgrimes if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR) 4891556Srgrimes return (0); 4901556Srgrimes b_info = (*b)->fts_info; 4911556Srgrimes if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR) 4921556Srgrimes return (0); 4931556Srgrimes if (a_info == FTS_D) 4941556Srgrimes return (-1); 4951556Srgrimes if (b_info == FTS_D) 4961556Srgrimes return (1); 4971556Srgrimes return (0); 4981556Srgrimes} 499