Commit message: None
CVE: CVE-2019-13638
CWE: CWE-78
Vulnerability description: GNU patch through 2.7.6 is vulnerable to OS shell command injection that can be exploited by opening a crafted patch file that contains an ed style diff payload with shell metacharacters. The ed editor does not need to be present on the vulnerable system. This is different from CVE-2018-1000156.

Project: savannah

another_hunk (enum diff difftype, bool rev)
{
    char *s;
    lin context = 0;
    size_t chars_read;
    char numbuf0[LINENUM_LENGTH_BOUND + 1];
    char numbuf1[LINENUM_LENGTH_BOUND + 1];
    char numbuf2[LINENUM_LENGTH_BOUND + 1];
    char numbuf3[LINENUM_LENGTH_BOUND + 1];

    set_hunkmax();

    while (p_end >= 0) {
	if (p_end == p_efake)
	    p_end = p_bfake;		/* don't free twice */
	else
	    free(p_line[p_end]);
	p_end--;
    }
    assert(p_end == -1);
    p_efake = -1;

    if (p_c_function)
      {
	free (p_c_function);
	p_c_function = NULL;
      }

    p_max = hunkmax;			/* gets reduced when --- found */
    if (difftype == CONTEXT_DIFF || difftype == NEW_CONTEXT_DIFF) {
	file_offset line_beginning = file_tell (pfp);
					/* file pos of the current line */
	lin repl_beginning = 0;		/* index of --- line */
	lin fillcnt = 0;	/* #lines of missing ptrn or repl */
	lin fillsrc;		/* index of first line to copy */
	lin filldst;		/* index of first missing line */
	bool ptrn_spaces_eaten = false;	/* ptrn was slightly misformed */
	bool some_context = false;	/* (perhaps internal) context seen */
	bool repl_could_be_missing = true;
	bool ptrn_missing = false;	/* The pattern was missing.  */
	bool repl_missing = false;	/* Likewise for replacement.  */
	file_offset repl_backtrack_position = 0;
					/* file pos of first repl line */
	lin repl_patch_line;		/* input line number for same */
	lin repl_context;		/* context for same */
	lin ptrn_prefix_context = -1;	/* lines in pattern prefix context */
	lin ptrn_suffix_context = -1;	/* lines in pattern suffix context */
	lin repl_prefix_context = -1;	/* lines in replac. prefix context */
	lin ptrn_copiable = 0;		/* # of copiable lines in ptrn */
	lin repl_copiable = 0;		/* Likewise for replacement.  */

	/* Pacify 'gcc -Wall'.  */
	fillsrc = filldst = repl_patch_line = repl_context = 0;

	chars_read = get_line ();
	if (chars_read == (size_t) -1
	    || chars_read <= 8
	    || strncmp (buf, "********", 8) != 0) {
	    next_intuit_at(line_beginning,p_input_line);
	    return chars_read == (size_t) -1 ? -1 : 0;
	}
	s = buf;
	while (*s == '*')
	    s++;
	if (*s == ' ')
	  {
	    p_c_function = s;
	    while (*s != '\n')
		s++;
	    *s = '\0';
	    p_c_function = savestr (p_c_function);
	    if (! p_c_function)
	      return -1;
	  }
	p_hunk_beg = p_input_line + 1;
	while (p_end < p_max) {
	    chars_read = get_line ();
	    if (chars_read == (size_t) -1)
	      return -1;
	    if (!chars_read) {
		if (repl_beginning && repl_could_be_missing) {
		    repl_missing = true;
		    goto hunk_done;
		}
		if (p_max - p_end < 4) {
		    strcpy (buf, "  \n");  /* assume blank lines got chopped */
		    chars_read = 3;
		} else {
		    fatal ("unexpected end of file in patch");
		}
	    }
	    p_end++;
	    if (p_end == hunkmax)
	      fatal ("unterminated hunk starting at line %s; giving up at line %s: %s",
		     format_linenum (numbuf0, pch_hunk_beg ()),
		     format_linenum (numbuf1, p_input_line), buf);
	    assert(p_end < hunkmax);
	    p_Char[p_end] = *buf;
	    p_len[p_end] = 0;
	    p_line[p_end] = 0;
	    switch (*buf) {
	    case '*':
		if (strnEQ(buf, "********", 8)) {
		    if (repl_beginning && repl_could_be_missing) {
			repl_missing = true;
			goto hunk_done;
		    }
		    else
		      fatal ("unexpected end of hunk at line %s",
			     format_linenum (numbuf0, p_input_line));
		}
		if (p_end != 0) {
		    if (repl_beginning && repl_could_be_missing) {
			repl_missing = true;
			goto hunk_done;
		    }
		    fatal ("unexpected '***' at line %s: %s",
			   format_linenum (numbuf0, p_input_line), buf);
		}
		context = 0;
		p_len[p_end] = strlen (buf);
		if (! (p_line[p_end] = savestr (buf))) {
		    p_end--;
		    return -1;
		}
		for (s = buf;  *s && !ISDIGIT (*s);  s++)
		  /* do nothing */ ;
		if (strnEQ(s,"0,0",3))
		    remove_prefix (s, 2);
		s = scan_linenum (s, &p_first);
		if (*s == ',') {
		    while (*s && !ISDIGIT (*s))
		      s++;
		    scan_linenum (s, &p_ptrn_lines);
		    p_ptrn_lines += 1 - p_first;
		    if (p_ptrn_lines < 0)
		      malformed ();
		}
		else if (p_first)
		    p_ptrn_lines = 1;
		else {
		    p_ptrn_lines = 0;
		    p_first = 1;
		}
		if (p_first >= LINENUM_MAX - p_ptrn_lines ||
		    p_ptrn_lines >= LINENUM_MAX - 6)
		  malformed ();
		p_max = p_ptrn_lines + 6;	/* we need this much at least */
		while (p_max + 1 >= hunkmax)
		    if (! grow_hunkmax ())
			return -1;
		p_max = hunkmax;
		break;
	    case '-':
		if (buf[1] != '-')
		  goto change_line;
		if (ptrn_prefix_context == -1)
		  ptrn_prefix_context = context;
		ptrn_suffix_context = context;
		if (repl_beginning
		    || (p_end
			!= p_ptrn_lines + 1 + (p_Char[p_end - 1] == '\n')))
		  {
		    if (p_end == 1)
		      {
			/* 'Old' lines were omitted.  Set up to fill
			   them in from 'new' context lines.  */
			ptrn_missing = true;
			p_end = p_ptrn_lines + 1;
			ptrn_prefix_context = ptrn_suffix_context = -1;
			fillsrc = p_end + 1;
			filldst = 1;
			fillcnt = p_ptrn_lines;
		      }
		    else if (! repl_beginning)
		      fatal ("%s '---' at line %s; check line numbers at line %s",
			     (p_end <= p_ptrn_lines
			      ? "Premature"
			      : "Overdue"),
			     format_linenum (numbuf0, p_input_line),
			     format_linenum (numbuf1, p_hunk_beg));
		    else if (! repl_could_be_missing)
		      fatal ("duplicate '---' at line %s; check line numbers at line %s",
			     format_linenum (numbuf0, p_input_line),
			     format_linenum (numbuf1,
					     p_hunk_beg + repl_beginning));
		    else
		      {
			repl_missing = true;
			goto hunk_done;
		      }
		  }
		repl_beginning = p_end;
		repl_backtrack_position = file_tell (pfp);
		repl_patch_line = p_input_line;
		repl_context = context;
		p_len[p_end] = strlen (buf);
		if (! (p_line[p_end] = savestr (buf)))
		  {
		    p_end--;
		    return -1;
		  }
		p_Char[p_end] = '=';
		for (s = buf;  *s && ! ISDIGIT (*s);  s++)
		  /* do nothing */ ;
		s = scan_linenum (s, &p_newfirst);
		if (*s == ',')
		  {
		    do
		      {
			if (!*++s)
			  malformed ();
		      }
		    while (! ISDIGIT (*s));
		    scan_linenum (s, &p_repl_lines);
		    p_repl_lines += 1 - p_newfirst;
		    if (p_repl_lines < 0)
		      malformed ();
		  }
		else if (p_newfirst)
		  p_repl_lines = 1;
		else
		  {
		    p_repl_lines = 0;
		    p_newfirst = 1;
		  }
		if (p_newfirst >= LINENUM_MAX - p_repl_lines ||
		    p_repl_lines >= LINENUM_MAX - p_end)
		  malformed ();
		p_max = p_repl_lines + p_end;
		while (p_max + 1 >= hunkmax)
		  if (! grow_hunkmax ())
		    return -1;
		if (p_repl_lines != ptrn_copiable
		    && (p_prefix_context != 0
			|| context != 0
			|| p_repl_lines != 1))
		  repl_could_be_missing = false;
		context = 0;
		break;
	    case '+':  case '!':
		repl_could_be_missing = false;
	      change_line:
		s = buf + 1;
		chars_read--;
		if (*s == '\n' && canonicalize_ws) {
		    strcpy (s, " \n");
		    chars_read = 2;
		}
		if (*s == ' ' || *s == '\t') {
		    s++;
		    chars_read--;
		} else if (repl_beginning && repl_could_be_missing) {
		    repl_missing = true;
		    goto hunk_done;
		}
		if (! repl_beginning)
		  {
		    if (ptrn_prefix_context == -1)
		      ptrn_prefix_context = context;
		  }
		else
		  {
		    if (repl_prefix_context == -1)
		      repl_prefix_context = context;
		  }
		chars_read -=
		  (1 < chars_read
		   && p_end == (repl_beginning ? p_max : p_ptrn_lines)
		   && incomplete_line ());
		p_len[p_end] = chars_read;
		p_line[p_end] = savebuf (s, chars_read);
		if (chars_read && ! p_line[p_end]) {
		    p_end--;
		    return -1;
	        }
		context = 0;
		break;
	    case '\t': case '\n':	/* assume spaces got eaten */
		s = buf;
		if (*buf == '\t') {
		    s++;
		    chars_read--;
		}
		if (repl_beginning && repl_could_be_missing &&
		    (!ptrn_spaces_eaten || difftype == NEW_CONTEXT_DIFF) ) {
		    repl_missing = true;
		    goto hunk_done;
		}
		chars_read -=
		  (1 < chars_read
		   && p_end == (repl_beginning ? p_max : p_ptrn_lines)
		   && incomplete_line ());
		p_len[p_end] = chars_read;
		p_line[p_end] = savebuf (buf, chars_read);
		if (chars_read && ! p_line[p_end]) {
		    p_end--;
		    return -1;
		}
		if (p_end != p_ptrn_lines + 1) {
		    ptrn_spaces_eaten |= (repl_beginning != 0);
		    some_context = true;
		    context++;
		    if (repl_beginning)
			repl_copiable++;
		    else
			ptrn_copiable++;
		    p_Char[p_end] = ' ';
		}
		break;
	    case ' ':
		s = buf + 1;
		chars_read--;
		if (*s == '\n' && canonicalize_ws) {
		    strcpy (s, "\n");
		    chars_read = 2;
		}
		if (*s == ' ' || *s == '\t') {
		    s++;
		    chars_read--;
		} else if (repl_beginning && repl_could_be_missing) {
		    repl_missing = true;
		    goto hunk_done;
		}
		some_context = true;
		context++;
		if (repl_beginning)
		    repl_copiable++;
		else
		    ptrn_copiable++;
		chars_read -=
		  (1 < chars_read
		   && p_end == (repl_beginning ? p_max : p_ptrn_lines)
		   && incomplete_line ());
		p_len[p_end] = chars_read;
		p_line[p_end] = savebuf (s, chars_read);
		if (chars_read && ! p_line[p_end]) {
		    p_end--;
		    return -1;
		}
		break;
	    default:
		if (repl_beginning && repl_could_be_missing) {
		    repl_missing = true;
		    goto hunk_done;
		}
		malformed ();
	    }
	}

    hunk_done:
	if (p_end >=0 && !repl_beginning)
	  fatal ("no '---' found in patch at line %s",
		 format_linenum (numbuf0, pch_hunk_beg ()));

	if (repl_missing) {

	    /* reset state back to just after --- */
	    p_input_line = repl_patch_line;
	    context = repl_context;
	    for (p_end--; p_end > repl_beginning; p_end--)
		free(p_line[p_end]);
	    Fseek (pfp, repl_backtrack_position, SEEK_SET);

	    /* redundant 'new' context lines were omitted - set */
	    /* up to fill them in from the old file context */
	    fillsrc = 1;
	    filldst = repl_beginning+1;
	    fillcnt = p_repl_lines;
	    p_end = p_max;
	}
	else if (! ptrn_missing && ptrn_copiable != repl_copiable)
	  fatal ("context mangled in hunk at line %s",
		 format_linenum (numbuf0, p_hunk_beg));
	else if (!some_context && fillcnt == 1) {
	    /* the first hunk was a null hunk with no context */
	    /* and we were expecting one line -- fix it up. */
	    while (filldst < p_end) {
		p_line[filldst] = p_line[filldst+1];
		p_Char[filldst] = p_Char[filldst+1];
		p_len[filldst] = p_len[filldst+1];
		filldst++;
	    }
#if 0
	    repl_beginning--;		/* this doesn't need to be fixed */
#endif
	    p_end--;
	    p_first++;			/* do append rather than insert */
	    fillcnt = 0;
	    p_ptrn_lines = 0;
	}

	p_prefix_context = ((repl_prefix_context == -1
			     || (ptrn_prefix_context != -1
				 && ptrn_prefix_context < repl_prefix_context))
			    ? ptrn_prefix_context : repl_prefix_context);
	p_suffix_context = ((ptrn_suffix_context != -1
			     && ptrn_suffix_context < context)
			    ? ptrn_suffix_context : context);
	if (p_prefix_context == -1 || p_suffix_context == -1)
	    fatal ("replacement text or line numbers mangled in hunk at line %s",
		   format_linenum (numbuf0, p_hunk_beg));

	if (difftype == CONTEXT_DIFF
	    && (fillcnt
		|| (p_first > 1
		    && p_prefix_context + p_suffix_context < ptrn_copiable))) {
	    if (verbosity == VERBOSE)
		say ("%s\n%s\n%s\n",
"(Fascinating -- this is really a new-style context diff but without",
"the telltale extra asterisks on the *** line that usually indicate",
"the new style...)");
	    diff_type = difftype = NEW_CONTEXT_DIFF;
	}

	/* if there were omitted context lines, fill them in now */
	if (fillcnt) {
	    p_bfake = filldst;		/* remember where not to free() */
	    p_efake = filldst + fillcnt - 1;
	    while (fillcnt-- > 0) {
		while (fillsrc <= p_end && fillsrc != repl_beginning
		       && p_Char[fillsrc] != ' ')
		    fillsrc++;
		if (p_end < fillsrc || fillsrc == repl_beginning)
		  {
		    fatal ("replacement text or line numbers mangled in hunk at line %s",
			   format_linenum (numbuf0, p_hunk_beg));
		  }
		p_line[filldst] = p_line[fillsrc];
		p_Char[filldst] = p_Char[fillsrc];
		p_len[filldst] = p_len[fillsrc];
		fillsrc++; filldst++;
	    }
	    while (fillsrc <= p_end && fillsrc != repl_beginning)
	      {
		if (p_Char[fillsrc] == ' ')
		  fatal ("replacement text or line numbers mangled in hunk at line %s",
			 format_linenum (numbuf0, p_hunk_beg));
		fillsrc++;
	      }
	    if (debug & 64)
	      printf ("fillsrc %s, filldst %s, rb %s, e+1 %s\n",
		      format_linenum (numbuf0, fillsrc),
		      format_linenum (numbuf1, filldst),
		      format_linenum (numbuf2, repl_beginning),
		      format_linenum (numbuf3, p_end + 1));
	    assert(fillsrc==p_end+1 || fillsrc==repl_beginning);
	    assert(filldst==p_end+1 || filldst==repl_beginning);
	}
    }
    else if (difftype == UNI_DIFF) {
	file_offset line_beginning = file_tell (pfp);  /* file pos of the current line */
	lin fillsrc;  /* index of old lines */
	lin filldst;  /* index of new lines */
	char ch = '\0';

	chars_read = get_line ();
	if (chars_read == (size_t) -1
	    || chars_read <= 4
	    || strncmp (buf, "@@ -", 4) != 0) {
	    next_intuit_at(line_beginning,p_input_line);
	    return chars_read == (size_t) -1 ? -1 : 0;
	}
	s = scan_linenum (buf + 4, &p_first);
	if (*s == ',')
	    s = scan_linenum (s + 1, &p_ptrn_lines);
	else
	    p_ptrn_lines = 1;
	if (p_first >= LINENUM_MAX - p_ptrn_lines)
	  malformed ();
	if (*s == ' ') s++;
	if (*s != '+')
	    malformed ();
	s = scan_linenum (s + 1, &p_newfirst);
	if (*s == ',')
	    s = scan_linenum (s + 1, &p_repl_lines);
	else
	    p_repl_lines = 1;
	if (p_newfirst >= LINENUM_MAX - p_repl_lines)
	  malformed ();
	if (*s == ' ') s++;
	if (*s++ != '@')
	    malformed ();
	if (*s++ == '@' && *s == ' ')
	  {
	    p_c_function = s;
	    while (*s != '\n')
		s++;
	    *s = '\0';
	    p_c_function = savestr (p_c_function);
	    if (! p_c_function)
	      return -1;
	  }
	if (!p_ptrn_lines)
	    p_first++;			/* do append rather than insert */
	if (!p_repl_lines)
	    p_newfirst++;
	if (p_ptrn_lines >= LINENUM_MAX - (p_repl_lines + 1))
	  malformed ();
	p_max = p_ptrn_lines + p_repl_lines + 1;
	while (p_max + 1 >= hunkmax)
	    if (! grow_hunkmax ())
		return -1;
	fillsrc = 1;
	filldst = fillsrc + p_ptrn_lines;
	p_end = filldst + p_repl_lines;
	sprintf (buf, "*** %s,%s ****\n",
		 format_linenum (numbuf0, p_first),
		 format_linenum (numbuf1, p_first + p_ptrn_lines - 1));
	p_len[0] = strlen (buf);
	if (! (p_line[0] = savestr (buf))) {
	    p_end = -1;
	    return -1;
	}
	p_Char[0] = '*';
	sprintf (buf, "--- %s,%s ----\n",
		 format_linenum (numbuf0, p_newfirst),
		 format_linenum (numbuf1, p_newfirst + p_repl_lines - 1));
	p_len[filldst] = strlen (buf);
	if (! (p_line[filldst] = savestr (buf))) {
	    p_end = 0;
	    return -1;
	}
	p_Char[filldst++] = '=';
	p_prefix_context = -1;
	p_hunk_beg = p_input_line + 1;
	while (fillsrc <= p_ptrn_lines || filldst <= p_end) {
	    chars_read = get_line ();
	    if (!chars_read) {
		if (p_max - filldst < 3) {
		    strcpy (buf, " \n");  /* assume blank lines got chopped */
		    chars_read = 2;
		} else {
		    fatal ("unexpected end of file in patch");
		}
	    }
	    if (chars_read == (size_t) -1)
		s = 0;
	    else if (*buf == '\t' || *buf == '\n') {
		ch = ' ';		/* assume the space got eaten */
		s = savebuf (buf, chars_read);
	    }
	    else {
		ch = *buf;
		s = savebuf (buf+1, --chars_read);
	    }
	    if (chars_read && ! s)
	      {
		while (--filldst > p_ptrn_lines)
		    free(p_line[filldst]);
		p_end = fillsrc-1;
		return -1;
	      }
	    switch (ch) {
	    case '-':
		if (fillsrc > p_ptrn_lines) {
		    free(s);
		    p_end = filldst-1;
		    malformed ();
		}
		chars_read -= fillsrc == p_ptrn_lines && incomplete_line ();
		p_Char[fillsrc] = ch;
		p_line[fillsrc] = s;
		p_len[fillsrc++] = chars_read;
		break;
	    case '=':
		ch = ' ';
		/* FALL THROUGH */
	    case ' ':
		if (fillsrc > p_ptrn_lines) {
		    free(s);
		    while (--filldst > p_ptrn_lines)
			free(p_line[filldst]);
		    p_end = fillsrc-1;
		    malformed ();
		}
		context++;
		chars_read -= fillsrc == p_ptrn_lines && incomplete_line ();
		p_Char[fillsrc] = ch;
		p_line[fillsrc] = s;
		p_len[fillsrc++] = chars_read;
		s = savebuf (s, chars_read);
		if (chars_read && ! s) {
		    while (--filldst > p_ptrn_lines)
			free(p_line[filldst]);
		    p_end = fillsrc-1;
		    return -1;
		}
		/* FALL THROUGH */
	    case '+':
		if (filldst > p_end) {
		    free(s);
		    while (--filldst > p_ptrn_lines)
			free(p_line[filldst]);
		    p_end = fillsrc-1;
		    malformed ();
		}
		chars_read -= filldst == p_end && incomplete_line ();
		p_Char[filldst] = ch;
		p_line[filldst] = s;
		p_len[filldst++] = chars_read;
		break;
	    default:
		p_end = filldst;
		malformed ();
	    }
	    if (ch != ' ') {
		if (p_prefix_context == -1)
		    p_prefix_context = context;
		context = 0;
	    }
	}/* while */
	if (p_prefix_context == -1)
	  malformed ();
	p_suffix_context = context;
    }
    else {				/* normal diff--fake it up */
	char hunk_type;
	int i;
	lin min, max;
	file_offset line_beginning = file_tell (pfp);

	p_prefix_context = p_suffix_context = 0;
	chars_read = get_line ();
	if (chars_read == (size_t) -1 || !chars_read || !ISDIGIT (*buf)) {
	    next_intuit_at(line_beginning,p_input_line);
	    return chars_read == (size_t) -1 ? -1 : 0;
	}
	s = scan_linenum (buf, &p_first);
	if (*s == ',') {
	    s = scan_linenum (s + 1, &p_ptrn_lines);
	    p_ptrn_lines += 1 - p_first;
	}
	else
	    p_ptrn_lines = (*s != 'a');
	if (p_first >= LINENUM_MAX - p_ptrn_lines)
	  malformed ();
	hunk_type = *s;
	if (hunk_type == 'a')
	    p_first++;			/* do append rather than insert */
	s = scan_linenum (s + 1, &min);
	if (*s == ',')
	    scan_linenum (s + 1, &max);
	else
	    max = min;
	if (min > max || max - min == LINENUM_MAX)
	  malformed ();
	if (hunk_type == 'd')
	    min++;
	p_newfirst = min;
	p_repl_lines = max - min + 1;
	if (p_newfirst >= LINENUM_MAX - p_repl_lines)
	  malformed ();
	if (p_ptrn_lines >= LINENUM_MAX - (p_repl_lines + 1))
	  malformed ();
	p_end = p_ptrn_lines + p_repl_lines + 1;
	while (p_end + 1 >= hunkmax)
	  if (! grow_hunkmax ())
	    {
	      p_end = -1;
	      return -1;
	    }
	sprintf (buf, "*** %s,%s\n",
		 format_linenum (numbuf0, p_first),
		 format_linenum (numbuf1, p_first + p_ptrn_lines - 1));
	p_len[0] = strlen (buf);
	if (! (p_line[0] = savestr (buf))) {
	    p_end = -1;
	    return -1;
	}
	p_Char[0] = '*';
	for (i=1; i<=p_ptrn_lines; i++) {
	    chars_read = get_line ();
	    if (chars_read == (size_t) -1)
	      {
		p_end = i - 1;
		return -1;
	      }
	    if (!chars_read)
	      fatal ("unexpected end of file in patch at line %s",
		     format_linenum (numbuf0, p_input_line));
	    if (buf[0] != '<' || (buf[1] != ' ' && buf[1] != '\t'))
	      fatal ("'<' followed by space or tab expected at line %s of patch",
		     format_linenum (numbuf0, p_input_line));
	    chars_read -= 2 + (i == p_ptrn_lines && incomplete_line ());
	    p_len[i] = chars_read;
	    p_line[i] = savebuf (buf + 2, chars_read);
	    if (chars_read && ! p_line[i]) {
		p_end = i-1;
		return -1;
	    }
	    p_Char[i] = '-';
	}
	if (hunk_type == 'c') {
	    chars_read = get_line ();
	    if (chars_read == (size_t) -1)
	      {
		p_end = i - 1;
		return -1;
	      }
	    if (! chars_read)
	      fatal ("unexpected end of file in patch at line %s",
		     format_linenum (numbuf0, p_input_line));
	    if (*buf != '-')
	      fatal ("'---' expected at line %s of patch",
		     format_linenum (numbuf0, p_input_line));
	}
	sprintf (buf, "--- %s,%s\n",
		 format_linenum (numbuf0, min),
		 format_linenum (numbuf1, max));
	p_len[i] = strlen (buf);
	if (! (p_line[i] = savestr (buf))) {
	    p_end = i-1;
	    return -1;
	}
	p_Char[i] = '=';
	for (i++; i<=p_end; i++) {
	    chars_read = get_line ();
	    if (chars_read == (size_t) -1)
	      {
		p_end = i - 1;
		return -1;
	      }
	    if (!chars_read)
	      fatal ("unexpected end of file in patch at line %s",
		     format_linenum (numbuf0, p_input_line));
	    if (buf[0] != '>' || (buf[1] != ' ' && buf[1] != '\t'))
	      fatal ("'>' followed by space or tab expected at line %s of patch",
		     format_linenum (numbuf0, p_input_line));
	    chars_read -= 2 + (i == p_end && incomplete_line ());
	    p_len[i] = chars_read;
	    p_line[i] = savebuf (buf + 2, chars_read);
	    if (chars_read && ! p_line[i]) {
		p_end = i-1;
		return -1;
	    }
	    p_Char[i] = '+';
	}
    }
    if (rev)				/* backwards patch? */
	if (!pch_swap())
	    say ("Not enough memory to swap next hunk!\n");
    assert (p_end + 1 < hunkmax);
    p_Char[p_end + 1] = '^';  /* add a stopper for apply_hunk */
    if (debug & 2) {
	lin i;

	for (i = 0; i <= p_end + 1; i++) {
	    fprintf (stderr, "%s %c",
		     format_linenum (numbuf0, i),
		     p_Char[i]);
	    if (p_Char[i] == '*')
	      fprintf (stderr, " %s,%s\n",
		       format_linenum (numbuf0, p_first),
		       format_linenum (numbuf1, p_ptrn_lines));
	    else if (p_Char[i] == '=')
	      fprintf (stderr, " %s,%s\n",
		       format_linenum (numbuf0, p_newfirst),
		       format_linenum (numbuf1, p_repl_lines));
	    else if (p_Char[i] != '^')
	      {
		fputs(" |", stderr);
		pch_write_line (i, stderr);
	      }
	    else
	      fputc('\n', stderr);
	}
	fflush (stderr);
    }
    return 1;
}

Project: savannah

best_name (char *const *name, int const *ignore)
{
  enum nametype i;
  int components[3];
  int components_min = INT_MAX;
  size_t basename_len[3];
  size_t basename_len_min = SIZE_MAX;
  size_t len[3];
  size_t len_min = SIZE_MAX;

  for (i = OLD;  i <= INDEX;  i++)
    if (name[i] && !ignore[i])
      {
	/* Take the names with the fewest prefix components.  */
	components[i] = prefix_components (name[i], false);
	if (components_min < components[i])
	  continue;
	components_min = components[i];

	/* Of those, take the names with the shortest basename.  */
	basename_len[i] = base_len (name[i]);
	if (basename_len_min < basename_len[i])
	  continue;
	basename_len_min = basename_len[i];

	/* Of those, take the shortest names.  */
	len[i] = strlen (name[i]);
	if (len_min < len[i])
	  continue;
	len_min = len[i];
      }

  /* Of those, take the first name.  */
  for (i = OLD;  i <= INDEX;  i++)
    if (name[i] && !ignore[i]
	&& name_is_valid (name[i])
	&& components[i] == components_min
	&& basename_len[i] == basename_len_min
	&& len[i] == len_min)
      break;

  return i;
}

Project: savannah

fetchmode (char const *str)
{
   const char *s;
   mode_t mode;

   while (ISSPACE ((unsigned char) *str))
     str++;

   for (s = str, mode = 0; s < str + 6; s++)
     {
       if (*s >= '0' && *s <= '7')
	mode = (mode << 3) + (*s - '0');
       else
	{
	 mode = 0;
	 break;
	}
     }
   if (*s == '\r')
     s++;
   if (*s != '\n')
     mode = 0;

    /* NOTE: The "diff --git" format always sets the file mode permission
       bits of symlinks to 0.  (On Linux, symlinks actually always have
       0777 permissions, so this is not even consistent.)  */

   return mode;
}

Project: savannah

get_ed_command_letter (char const *line)
{
  char const *p = line;
  char letter;
  bool pair = false;

  if (ISDIGIT (*p))
    {
      while (ISDIGIT (*++p))
	/* do nothing */ ;
      if (*p == ',')
	{
	  if (! ISDIGIT (*++p))
	    return 0;
	  while (ISDIGIT (*++p))
	    /* do nothing */ ;
	  pair = true;
	}
    }

  letter = *p++;

  switch (letter)
    {
    case 'a':
    case 'i':
      if (pair)
	return 0;
      break;

    case 'c':
    case 'd':
      break;

    case 's':
      if (strncmp (p, "/.//", 4) != 0)
	return 0;
      p += 4;
      break;

    default:
      return 0;
    }

  while (*p == ' ' || *p == '\t')
    p++;
  if (*p == '\n')
    return letter;
  return 0;
}

Project: savannah

get_line (void)
{
   return pget_line (p_indent, p_rfc934_nesting, p_strip_trailing_cr,
		     p_pass_comments_through);
}

Project: savannah

get_sha1(char **sha1, char const *start, char const *end)
{
  unsigned int len = end - start;
  *sha1 = xmalloc (len + 1);
  memcpy (*sha1, start, len);
  (*sha1)[len] = 0;
}

Project: savannah

grow_hunkmax (void)
{
    hunkmax *= 2;
    assert (p_line && p_len && p_Char);
    if ((p_line = realloc (p_line, hunkmax * sizeof (*p_line)))
	&& (p_len = realloc (p_len, hunkmax * sizeof (*p_len)))
	&& (p_Char = realloc (p_Char, hunkmax * sizeof (*p_Char))))
      return true;
    if (!using_plan_a)
      xalloc_die ();
    /* Don't free previous values of p_line etc.,
       since some broken implementations free them for us.
       Whatever is null will be allocated again from within plan_a (),
       of all places.  */
    return false;
}

Project: savannah

incomplete_line (void)
{
  FILE *fp = pfp;
  int c;
  file_offset line_beginning = file_tell (fp);

  if (getc (fp) == '\\')
    {
      while ((c = getc (fp)) != '\n'  &&  c != EOF)
	/* do nothing */ ;
      return true;
    }
  else
    {
      /* We don't trust ungetc.  */
      Fseek (pfp, line_beginning, SEEK_SET);
      return false;
    }
}

Project: savannah

malformed (void)
{
    char numbuf[LINENUM_LENGTH_BOUND + 1];
    fatal ("malformed patch at line %s: %s",
	   format_linenum (numbuf, p_input_line), buf);
		/* about as informative as "Syntax error" in C */
}

Project: savannah

maybe_reverse (char const *name, bool nonexistent, bool is_empty)
{
  bool looks_reversed = (! is_empty) < p_says_nonexistent[reverse ^ is_empty];

  /* Allow to create and delete empty files when we know that they are empty:
     in the "diff --git" format, we know that from the index header.  */
  if (is_empty
      && p_says_nonexistent[reverse ^ nonexistent] == 1
      && p_says_nonexistent[! reverse ^ nonexistent] == 2)
    return false;

  if (looks_reversed)
    reverse ^=
      ok_to_reverse ("The next patch%s would %s the file %s,\nwhich %s!",
		     reverse ? ", when reversed," : "",
		     (nonexistent ? "delete"
		      : is_empty ? "empty out"
		      : "create"),
		     quotearg (name),
		     (nonexistent ? "does not exist"
		      : is_empty ? "is already empty"
		      : "already exists"));
  return looks_reversed;
}

Project: savannah

name_is_valid (char const *name)
{
  int i;
  bool is_valid = true;

  for (i = 0; i < ARRAY_SIZE (invalid_names); i++)
    {
      if (! invalid_names[i])
	break;
      if (! strcmp (invalid_names[i], name))
	return false;
    }

  is_valid = filename_is_safe (name);

  /* Allow any filename if we are in the filesystem root.  */
  if (! is_valid && cwd_is_root (name))
    is_valid = true;

  if (! is_valid)
    {
      say ("Ignoring potentially dangerous file name %s\n", quotearg (name));
      if (i < ARRAY_SIZE (invalid_names))
	invalid_names[i] = name;
    }
  return is_valid;
}

Project: savannah

next_intuit_at (file_offset file_pos, lin file_line)
{
    p_base = file_pos;
    p_bline = file_line;
}

Project: savannah

open_patch_file (char const *filename)
{
    file_offset file_pos = 0;
    file_offset pos;
    struct stat st;

    if (!filename || !*filename || strEQ (filename, "-"))
      pfp = stdin;
    else
      {
	pfp = fopen (filename, binary_transput ? "rb" : "r");
	if (!pfp)
	  pfatal ("Can't open patch file %s", quotearg (filename));
      }
#if HAVE_SETMODE_DOS
    if (binary_transput)
      {
	if (isatty (fileno (pfp)))
	  fatal ("cannot read binary data from tty on this platform");
	setmode (fileno (pfp), O_BINARY);
      }
#endif
    if (fstat (fileno (pfp), &st) != 0)
      pfatal ("fstat");
    if (S_ISREG (st.st_mode) && (pos = file_tell (pfp)) != -1)
      file_pos = pos;
    else
      {
	size_t charsread;
	int fd;
	FILE *read_pfp = pfp;
	fd = make_tempfile (&TMPPATNAME, 'p', NULL, O_RDWR | O_BINARY, 0);
	if (fd == -1)
	  pfatal ("Can't create temporary file %s", TMPPATNAME);
	TMPPATNAME_needs_removal = true;
	pfp = fdopen (fd, "w+b");
	if (! pfp)
	  pfatal ("Can't open stream for file %s", quotearg (TMPPATNAME));
	for (st.st_size = 0;
	     (charsread = fread (buf, 1, bufsize, read_pfp)) != 0;
	     st.st_size += charsread)
	  if (fwrite (buf, 1, charsread, pfp) != charsread)
	    write_fatal ();
	if (ferror (read_pfp) || fclose (read_pfp) != 0)
	  read_fatal ();
	if (fflush (pfp) != 0
	    || file_seek (pfp, (file_offset) 0, SEEK_SET) != 0)
	  write_fatal ();
      }
    p_filesize = st.st_size;
    if (p_filesize != (file_offset) p_filesize)
      fatal ("patch file is too long");
    next_intuit_at (file_pos, 1);
}

Project: savannah

pch_char (lin line)
{
    return p_Char[line];
}

Project: savannah

pch_end (void)
{
    return p_end;
}

Project: savannah

pch_git_diff (void)
{
  return p_git_diff;
}

Project: savannah

pch_hunk_beg (void)
{
    return p_hunk_beg;
}

Project: savannah

pch_line_len (lin line)
{
    return p_len[line];
}

Project: savannah

pch_mode (bool which)
{
  return p_mode[which];
}

Project: savannah

pch_name (enum nametype type)
{
  return type == NONE ? NULL : p_name[type];
}

Project: savannah

pch_newfirst (void)
{
    return p_newfirst;
}

Project: savannah

pch_prefix_context (void)
{
    return p_prefix_context;
}

Project: savannah

bool pch_rename (void)
{
  return p_rename[OLD] && p_rename[NEW];
}

Project: savannah

pch_repl_lines (void)
{
    return p_repl_lines;
}

Project: savannah

pch_says_nonexistent (bool which)
{
  return p_says_nonexistent[which];
}

Project: savannah

pch_sha1 (bool which)
{
  return p_sha1[which];
}

Project: savannah

pch_suffix_context (void)
{
    return p_suffix_context;
}

Project: savannah

pch_timestr (bool which)
{
  return p_timestr[which];
}

Project: savannah

pget_line (size_t indent, int rfc934_nesting, bool strip_trailing_cr,
	   bool pass_comments_through)
{
  FILE *fp = pfp;
  int c;
  size_t i;
  char *b;
  size_t s;

  do
    {
      i = 0;
      for (;;)
	{
	  c = getc (fp);
	  if (c == EOF)
	    {
	      if (ferror (fp))
		read_fatal ();
	      return 0;
	    }
	  if (indent <= i)
	    break;
	  if (c == ' ' || c == 'X')
	    i++;
	  else if (c == '\t')
	    i = (i + 8) & ~7;
	  else
	    break;
	}

      i = 0;
      b = buf;

      while (c == '-' && 0 <= --rfc934_nesting)
	{
	  c = getc (fp);
	  if (c == EOF)
	    goto patch_ends_in_middle_of_line;
	  if (c != ' ')
	    {
	      i = 1;
	      b[0] = '-';
	      break;
	    }
	  c = getc (fp);
	  if (c == EOF)
	    goto patch_ends_in_middle_of_line;
	}

      s = bufsize;

      for (;;)
	{
	  if (i == s - 1)
	    {
	      s *= 2;
	      b = realloc (b, s);
	      if (!b)
		{
		  if (!using_plan_a)
		    xalloc_die ();
		  return (size_t) -1;
		}
	      buf = b;
	      bufsize = s;
	    }
	  b[i++] = c;
	  if (c == '\n')
	    break;
	  c = getc (fp);
	  if (c == EOF)
	    goto patch_ends_in_middle_of_line;
	}

      p_input_line++;
    }
  while (*b == '#' && !pass_comments_through);

  if (strip_trailing_cr && 2 <= i && b[i - 2] == '\r')
    b[i-- - 2] = '\n';
  b[i] = '\0';
  return i;

 patch_ends_in_middle_of_line:
  if (ferror (fp))
    read_fatal ();
  say ("patch unexpectedly ends in middle of line\n");
  return 0;
}

Project: savannah

prefix_components (char *filename, bool checkdirs)
{
  int count = 0;
  struct stat stat_buf;
  int stat_result;
  char *f = filename + FILE_SYSTEM_PREFIX_LEN (filename);

  if (*f)
    while (*++f)
      if (ISSLASH (f[0]) && ! ISSLASH (f[-1]))
	{
	  if (checkdirs)
	    {
	      *f = '\0';
	      stat_result = safe_stat (filename, &stat_buf);
	      *f = '/';
	      if (! (stat_result == 0 && S_ISDIR (stat_buf.st_mode)))
		break;
	    }

	  count++;
	}

  return count;
}

Project: savannah

re_patch (void)
{
    p_first = 0;
    p_newfirst = 0;
    p_ptrn_lines = 0;
    p_repl_lines = 0;
    p_end = -1;
    p_max = 0;
    p_indent = 0;
    p_strip_trailing_cr = false;
}

Project: savannah

set_hunkmax (void)
{
    if (!p_line)
	p_line = xmalloc (hunkmax * sizeof *p_line);
    if (!p_len)
	p_len = xmalloc (hunkmax * sizeof *p_len);
    if (!p_Char)
	p_Char = xmalloc (hunkmax * sizeof *p_Char);
}

Project: savannah

skip_hex_digits (char const *str)
{
  char const *s;

  for (s = str; (*s >= '0' && *s <= '9') || (*s >= 'a' && *s <= 'f'); s++)
    /* do nothing */ ;
  return s == str ? NULL : s;
}

Project: savannah

skip_to (file_offset file_pos, lin file_line)
{
    FILE *i = pfp;
    FILE *o = stdout;
    int c;

    assert(p_base <= file_pos);
    if ((verbosity == VERBOSE || !inname) && p_base < file_pos) {
	Fseek (i, p_base, SEEK_SET);
	say ("The text leading up to this was:\n--------------------------\n");

	while (file_tell (i) < file_pos)
	  {
	    putc ('|', o);
	    do
	      {
		if ((c = getc (i)) == EOF)
		  read_fatal ();
		putc (c, o);
	      }
	    while (c != '\n');
	  }

	say ("--------------------------\n");
    }
    else
	Fseek (i, file_pos, SEEK_SET);
    p_input_line = file_line - 1;
}

Project: savannah

there_is_another_patch (bool need_header, mode_t *file_type)
{
    if (p_base != 0 && p_base >= p_filesize) {
	if (verbosity == VERBOSE)
	    say ("done\n");
	return false;
    }
    if (verbosity == VERBOSE)
	say ("Hmm...");
    diff_type = intuit_diff_type (need_header, file_type);
    if (diff_type == NO_DIFF) {
	if (verbosity == VERBOSE)
	  say (p_base
	       ? "  Ignoring the trailing garbage.\ndone\n"
	       : "  I can't seem to find a patch in there anywhere.\n");
	if (! p_base && p_filesize)
	  fatal ("Only garbage was found in the patch input.");
	return false;
    }
    if (skip_rest_of_patch)
      {
	Fseek (pfp, p_start, SEEK_SET);
	p_input_line = p_sline - 1;
	return true;
      }
    if (verbosity == VERBOSE)
	say ("  %sooks like %s to me...\n",
	    (p_base == 0 ? "L" : "The next patch l"),
	    diff_type == UNI_DIFF ? "a unified diff" :
	    diff_type == CONTEXT_DIFF ? "a context diff" :
	    diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
	    diff_type == NORMAL_DIFF ? "a normal diff" :
	    diff_type == GIT_BINARY_DIFF ? "a git binary diff" :
	    "an ed script" );

    if (no_strip_trailing_cr)
      p_strip_trailing_cr = false;

    if (verbosity != SILENT)
      {
	if (p_indent)
	  say ("(Patch is indented %lu space%s.)\n",
	       (unsigned long int) p_indent, p_indent==1?"":"s");
	if (p_strip_trailing_cr)
	  say ("(Stripping trailing CRs from patch; use --binary to disable.)\n");
	if (! inname)
	  {
	    char numbuf[LINENUM_LENGTH_BOUND + 1];
	    say ("can't find file to patch at input line %s\n",
		 format_linenum (numbuf, p_sline));
	    if (diff_type != ED_DIFF && diff_type != NORMAL_DIFF)
	      say (strippath == -1
		   ? "Perhaps you should have used the -p or --strip option?\n"
		   : "Perhaps you used the wrong -p or --strip option?\n");
	  }
      }

    skip_to(p_start,p_sline);
    while (!inname) {
	char *t;
	if (force | batch) {
	    say ("No file to patch.  Skipping patch.\n");
	    skip_rest_of_patch = true;
	    return true;
	}
	ask ("File to patch: ");
	t = buf + strlen (buf);
	if (t > buf + 1 && *(t - 1) == '\n')
	  {
	    inname = xmemdup0 (buf, t - buf - 1);
	    inerrno = stat_file (inname, &instat);
	    if (inerrno)
	      {
		perror (inname);
		fflush (stderr);
		free (inname);
		inname = 0;
	      }
	    else
	      invc = -1;
	  }
	if (!inname) {
	    ask ("Skip this patch? [y] ");
	    if (*buf != 'n') {
		if (verbosity != SILENT)
		    say ("Skipping patch.\n");
		skip_rest_of_patch = true;
		return true;
	    }
	}
    }
    return true;
}