Index: usr.bin/patch/patch.c =================================================================== --- usr.bin/patch/patch.c +++ usr.bin/patch/patch.c @@ -103,6 +103,7 @@ static bool patch_match(LINENUM, LINENUM, LINENUM); static bool similar(const char *, const char *, int); static void usage(void); +static bool handle_creation(bool, bool *); /* true if -E was specified on command line. */ static bool remove_empty_files = false; @@ -147,8 +148,10 @@ int main(int argc, char *argv[]) { + struct stat statbuf; int error = 0, hunk, failed, i, fd; - bool patch_seen, reverse_seen; + bool out_creating, out_existed, patch_seen, remove_file; + bool reverse_seen; LINENUM where = 0, newwhere, fuzz, mymaxfuzz; const char *tmpdir; char *v; @@ -219,6 +222,12 @@ reinitialize_almost_everything()) { /* for each patch in patch file */ + if (source_file != NULL && (diff_type == CONTEXT_DIFF || + diff_type == NEW_CONTEXT_DIFF || + diff_type == UNI_DIFF)) + out_creating = strcmp(source_file, _PATH_DEVNULL) == 0; + else + out_creating = false; patch_seen = true; warn_on_invalid_line = true; @@ -226,6 +235,8 @@ if (outname == NULL) outname = xstrdup(filearg[0]); + out_existed = stat(outname, &statbuf) == 0; + /* for ed script just up and do it and exit */ if (diff_type == ED_DIFF) { do_ed_script(); @@ -252,9 +263,15 @@ failed = 0; reverse_seen = false; out_of_mem = false; + remove_file = false; while (another_hunk()) { hunk++; fuzz = 0; + + if (out_creating) + reverse_seen = handle_creation(out_existed, + &remove_file); + mymaxfuzz = pch_context(); if (maxfuzz < mymaxfuzz) mymaxfuzz = maxfuzz; @@ -372,7 +389,6 @@ /* and put the output where desired */ ignore_signals(); if (!skip_rest_of_patch) { - struct stat statbuf; char *realout = outname; if (!check_only) { @@ -383,7 +399,7 @@ } else chmod(outname, filemode); - if (remove_empty_files && + if ((remove_empty_files || remove_file) && stat(realout, &statbuf) == 0 && statbuf.st_size == 0) { if (verbose) @@ -444,6 +460,9 @@ filearg[0] = NULL; } + free(source_file); + source_file = NULL; + free(outname); outname = NULL; @@ -1084,3 +1103,58 @@ return true; /* actually, this is not reached */ /* since there is always a \n */ } + +static bool +handle_creation(bool out_existed, bool *remove) +{ + bool reverse_seen; + + reverse_seen = false; + if (reverse && out_existed) { + *remove = true; + } else if (!reverse && out_existed) { + if (force) { + skip_rest_of_patch = true; + return (false); + } + if (noreverse) { + say("Ignoring previously applied (or reversed) patch.\n"); + skip_rest_of_patch = true; + return (false); + } + + /* Unreversed... suspicious if the file existed. */ + if (!pch_swap()) + fatal("lost hunk on alloc error!\n"); + + reverse = !reverse; + + if (batch) { + if (verbose) + say("Patch creates file that already exists, %s %seversed", + reverse ? "Assuming" : "Ignoring", + reverse ? "R" : "Unr"); + } else { + ask("Patch creates file that already exists! %s -R? [y] ", + reverse ? "Assume" : "Ignore"); + + if (*buf == 'n') { + ask("Apply anyway? [n]"); + if (*buf != 'y') + /* Don't apply; error out */ + skip_rest_of_patch = true; + else + /* Attempt to apply */ + reverse_seen = true; + reverse = !reverse; + if (!pch_swap()) + fatal("lost hunk on alloc error!\n"); + } else { + /* They've opted to assume -R */ + *remove = true; + } + } + } + + return (reverse_seen); +} Index: usr.bin/patch/pch.h =================================================================== --- usr.bin/patch/pch.h +++ usr.bin/patch/pch.h @@ -37,6 +37,8 @@ bool exists; }; +extern char *source_file; + void re_patch(void); void open_patch_file(const char *); void set_hunkmax(void); Index: usr.bin/patch/pch.c =================================================================== --- usr.bin/patch/pch.c +++ usr.bin/patch/pch.c @@ -70,6 +70,8 @@ static FILE *pfp = NULL; /* patch file pointer */ static char *bestguess = NULL; /* guess at correct filename */ +char *source_file; + static void grow_hunkmax(void); static int intuit_diff_type(void); static void next_intuit_at(off_t, LINENUM); @@ -218,7 +220,12 @@ bestguess = xstrdup(buf); filearg[0] = fetchname(buf, &exists, 0); } - if (!exists) { + /* + * fetchname can now return buf = NULL, exists = true, to + * indicate to the caller that /dev/null was specified. Retain + * previous behavior for now until this can be better evaluted. + */ + if (filearg[0] == NULL || !exists) { int def_skip = *bestguess == '\0'; ask("No file found--skip this patch? [%c] ", def_skip ? 'y' : 'n'); @@ -403,6 +410,23 @@ names[OLD_FILE] = names[NEW_FILE]; names[NEW_FILE] = tmp; } + + /* Invalidated */ + free(source_file); + source_file = NULL; + + if (retval != 0) { + /* + * In the case of success, path == NULL means _PATH_DEVNULL if + * exists is set. Explicitly specify it here to make it easier + * to detect later on that we're actually creating a file and + * not that we've just goofed something up. + */ + if (names[OLD_FILE].path != NULL) + source_file = xstrdup(names[OLD_FILE].path); + else if (names[OLD_FILE].exists) + source_file = xstrdup(_PATH_DEVNULL); + } if (filearg[0] == NULL) { if (posix) filearg[0] = posix_name(names, ok_to_create_file); Index: usr.bin/patch/tests/unified_patch_test.sh =================================================================== --- usr.bin/patch/tests/unified_patch_test.sh +++ usr.bin/patch/tests/unified_patch_test.sh @@ -108,22 +108,17 @@ file_nodupe_body() { - # WIP - atf_expect_fail "patch(1) erroneously duplicates created files" echo "x" > foo diff -u /dev/null foo > foo.diff - atf_check -x "patch -s < foo.diff" - atf_check -s not-exit:0 -x "patch -fs < foo.diff" + atf_check -s not-exit:0 -o ignore -x "patch -Ns < foo.diff" + atf_check -s not-exit:0 -o ignore -x "patch -fs < foo.diff" } atf_test_case file_removal file_removal_body() { - # WIP - atf_expect_fail "patch(1) does not yet recognize /dev/null as creation" - echo "x" > foo diff -u /dev/null foo > foo.diff Index: usr.bin/patch/util.c =================================================================== --- usr.bin/patch/util.c +++ usr.bin/patch/util.c @@ -366,8 +366,10 @@ say("fetchname %s %d\n", at, strip_leading); #endif /* So files can be created by diffing against /dev/null. */ - if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) + if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) { + *exists = true; return NULL; + } name = fullname = t = savestr(at); tab = strchr(t, '\t') != NULL;