diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h --- a/usr.bin/patch/common.h +++ b/usr.bin/patch/common.h @@ -84,6 +84,7 @@ extern int debug; #endif +extern bool search; extern bool force; extern bool batch; extern bool verbose; diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1 --- a/usr.bin/patch/patch.1 +++ b/usr.bin/patch/patch.1 @@ -21,7 +21,7 @@ .\" .\" $OpenBSD: patch.1,v 1.27 2014/04/15 06:26:54 jmc Exp $ .\" $FreeBSD$ -.Dd November 3, 2019 +.Dd May 10, 2021 .Dt PATCH 1 .Os .Sh NAME @@ -39,6 +39,7 @@ .Op Fl o Ar out-file .Op Fl p Ar strip-count .Op Fl r Ar rej-name +.Op Fl S .Op Fl V Cm t | nil | never | none .Op Fl x Ar number .Op Fl z Ar backup-ext @@ -281,6 +282,16 @@ Makes .Nm do its work silently, unless an error occurs. +.It Fl S , Fl Fl search +Tells +.Nm +to search the current directory for a maching file by +.Xr basename 1 , +if the file in the patch doesn't exist. +To change the searching directory use the +.Fl d +option. +When searching for a file, the first match is selected, if any. .It Fl t , Fl Fl batch Similar to .Fl f , diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c --- a/usr.bin/patch/patch.c +++ b/usr.bin/patch/patch.c @@ -75,6 +75,7 @@ int debug = 0; #endif +bool search = false; bool force = false; bool batch = false; bool verbose = true; @@ -519,7 +520,7 @@ static void get_some_switches(void) { - const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:"; + const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RsStuvV:x:z:"; static struct option longopts[] = { {"backup", no_argument, 0, 'b'}, {"batch", no_argument, 0, 't'}, @@ -543,6 +544,7 @@ {"remove-empty-files", no_argument, 0, 'E'}, {"reverse", no_argument, 0, 'R'}, {"silent", no_argument, 0, 's'}, + {"search", no_argument, 0, 'S'}, {"strip", required_argument, 0, 'p'}, {"suffix", required_argument, 0, 'z'}, {"unified", no_argument, 0, 'u'}, @@ -642,6 +644,9 @@ case 's': verbose = false; break; + case 'S': + search = true; + break; case 't': batch = true; break; @@ -691,7 +696,7 @@ "usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]\n" " [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n" " [-r rej-name] [-V t | nil | never | none] [-x number]\n" -" [-z backup-ext] [--posix] [origfile [patchfile]]\n" +" [-z backup-ext] [--posix] [-S] [origfile [patchfile]]\n" " patch #include #include +#include #include "common.h" #include "util.h" @@ -422,3 +423,82 @@ unlink(TMPPATNAME); exit(status); } + +/* + * Search for file recursivly. + */ +char * +find_file(const char *bname, const char *dname, const char *fname, int *pmatch) +{ +#if __BSD_VISIBLE == 0 + struct stat filestat; + char *stmp; +#endif + struct dirent *dp; + char *retval; + char *ddir; + DIR *dirp; + int len; + + if (strcmp(bname, ".") == 0 && strcmp(dname, ".") == 0) { + if (asprintf(&ddir, ".") < 0) + return (NULL); + } else { + if (asprintf(&ddir, "%s/%s", bname, dname) < 0) + return (NULL); + } + + dirp = opendir(ddir); + retval = NULL; + + if (dirp == NULL) { + free(ddir); + return (NULL); + } + + len = strlen(fname); + while ((dp = readdir(dirp)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) + continue; +#if __BSD_VISIBLE == 0 + if (asprintf(&stmp, "%s/%s", ddir, dp->d_name) < 0) + continue; + stat(stmp, &filestat); + free(stmp); + + if (S_ISDIR(filestat.st_mode)) + goto found_directory; + else if (S_ISREG(filestat.st_mode)) + goto found_regular_file; + else + continue; +#else + switch (dp->d_type) { + case DT_DIR: + goto found_directory; + case DT_REG: + goto found_regular_file; + default: + continue; + } +#endif +found_directory: + retval = find_file(ddir, dp->d_name, fname, pmatch); + if (retval != NULL) + goto done; + continue; +found_regular_file: + if (dp->d_namlen == len && + strcmp(dp->d_name, fname) == 0 && + (*pmatch)-- == 0) { + if (asprintf(&retval, "%s/%s", ddir, fname) < 0) + retval = NULL; + goto done; + } + } +done: + closedir(dirp); + free(ddir); + return (retval); +}