tmi2_fluffos_v2/
tmi2_fluffos_v2/bin/
tmi2_fluffos_v2/etc/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/ChangeLog.old/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/Win32/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/compat/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/compat/simuls/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/include/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/clone/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/command/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/data/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/etc/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/include/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/inherit/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/inherit/master/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/log/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/compiler/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/efuns/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/operators/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/u/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/tmp/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/windows/
tmi2_fluffos_v2/lib/
tmi2_fluffos_v2/lib/adm/
tmi2_fluffos_v2/lib/adm/daemons/languages/
tmi2_fluffos_v2/lib/adm/daemons/network/I3/
tmi2_fluffos_v2/lib/adm/daemons/virtual/
tmi2_fluffos_v2/lib/adm/daemons/virtual/template/
tmi2_fluffos_v2/lib/adm/news/
tmi2_fluffos_v2/lib/adm/obj/
tmi2_fluffos_v2/lib/adm/obj/master/
tmi2_fluffos_v2/lib/adm/priv/
tmi2_fluffos_v2/lib/adm/shell/
tmi2_fluffos_v2/lib/adm/tmp/
tmi2_fluffos_v2/lib/cmds/
tmi2_fluffos_v2/lib/d/
tmi2_fluffos_v2/lib/d/Conf/
tmi2_fluffos_v2/lib/d/Conf/adm/
tmi2_fluffos_v2/lib/d/Conf/boards/
tmi2_fluffos_v2/lib/d/Conf/cmds/
tmi2_fluffos_v2/lib/d/Conf/data/
tmi2_fluffos_v2/lib/d/Conf/logs/
tmi2_fluffos_v2/lib/d/Conf/obj/
tmi2_fluffos_v2/lib/d/Conf/text/help/
tmi2_fluffos_v2/lib/d/Fooland/adm/
tmi2_fluffos_v2/lib/d/Fooland/data/
tmi2_fluffos_v2/lib/d/Fooland/data/attic/
tmi2_fluffos_v2/lib/d/Fooland/items/
tmi2_fluffos_v2/lib/d/TMI/
tmi2_fluffos_v2/lib/d/TMI/adm/
tmi2_fluffos_v2/lib/d/TMI/boards/
tmi2_fluffos_v2/lib/d/TMI/data/
tmi2_fluffos_v2/lib/d/TMI/rooms/
tmi2_fluffos_v2/lib/d/grid/
tmi2_fluffos_v2/lib/d/grid/adm/
tmi2_fluffos_v2/lib/d/grid/data/
tmi2_fluffos_v2/lib/d/std/
tmi2_fluffos_v2/lib/d/std/adm/
tmi2_fluffos_v2/lib/data/adm/
tmi2_fluffos_v2/lib/data/adm/daemons/
tmi2_fluffos_v2/lib/data/adm/daemons/doc_d/
tmi2_fluffos_v2/lib/data/adm/daemons/emoted/
tmi2_fluffos_v2/lib/data/adm/daemons/network/http/
tmi2_fluffos_v2/lib/data/adm/daemons/network/services/mail_q/
tmi2_fluffos_v2/lib/data/adm/daemons/network/smtp/
tmi2_fluffos_v2/lib/data/adm/daemons/news/archives/
tmi2_fluffos_v2/lib/data/attic/connection/
tmi2_fluffos_v2/lib/data/attic/user/
tmi2_fluffos_v2/lib/data/std/connection/b/
tmi2_fluffos_v2/lib/data/std/connection/l/
tmi2_fluffos_v2/lib/data/std/user/a/
tmi2_fluffos_v2/lib/data/std/user/b/
tmi2_fluffos_v2/lib/data/std/user/d/
tmi2_fluffos_v2/lib/data/std/user/f/
tmi2_fluffos_v2/lib/data/std/user/l/
tmi2_fluffos_v2/lib/data/std/user/x/
tmi2_fluffos_v2/lib/data/u/d/dm/working/doc_d/
tmi2_fluffos_v2/lib/data/u/l/leto/doc_d/
tmi2_fluffos_v2/lib/data/u/l/leto/smtp/
tmi2_fluffos_v2/lib/doc/
tmi2_fluffos_v2/lib/doc/driverdoc/applies/
tmi2_fluffos_v2/lib/doc/driverdoc/applies/interactive/
tmi2_fluffos_v2/lib/doc/driverdoc/concepts/
tmi2_fluffos_v2/lib/doc/driverdoc/driver/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/arrays/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/buffers/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/compile/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/ed/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/filesystem/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/floats/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/functions/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/general/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/mappings/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/numbers/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/parsing/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/constructs/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/preprocessor/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/types/
tmi2_fluffos_v2/lib/doc/driverdoc/platforms/
tmi2_fluffos_v2/lib/doc/mudlib/
tmi2_fluffos_v2/lib/ftp/
tmi2_fluffos_v2/lib/include/driver/
tmi2_fluffos_v2/lib/log/
tmi2_fluffos_v2/lib/log/driver/
tmi2_fluffos_v2/lib/obj/net/
tmi2_fluffos_v2/lib/obj/shells/
tmi2_fluffos_v2/lib/obj/tools/
tmi2_fluffos_v2/lib/std/adt/
tmi2_fluffos_v2/lib/std/board/
tmi2_fluffos_v2/lib/std/body/
tmi2_fluffos_v2/lib/std/fun/
tmi2_fluffos_v2/lib/std/living/
tmi2_fluffos_v2/lib/std/object/
tmi2_fluffos_v2/lib/std/shop/
tmi2_fluffos_v2/lib/std/socket/
tmi2_fluffos_v2/lib/std/user/
tmi2_fluffos_v2/lib/std/virtual/
tmi2_fluffos_v2/lib/student/
tmi2_fluffos_v2/lib/student/kalypso/
tmi2_fluffos_v2/lib/student/kalypso/armor/
tmi2_fluffos_v2/lib/student/kalypso/rooms/
tmi2_fluffos_v2/lib/student/kalypso/weapons/
tmi2_fluffos_v2/lib/u/l/leto/
tmi2_fluffos_v2/lib/u/l/leto/cmds/
tmi2_fluffos_v2/lib/www/errors/
tmi2_fluffos_v2/lib/www/gateways/
tmi2_fluffos_v2/lib/www/images/
tmi2_fluffos_v2/old/
tmi2_fluffos_v2/win32/
/*
// Val@TMI (Jan 95) <Tero.Pelander@utu.fi>
// Use at own risk.  It should work but no guaranties.
// Currently all context lines must match when locating a match.
// Val added unified diff and added some protection from loops 29-Nov-95
*/
// #define cmd_patch cmd_patch2 // test version

#define TMPFILE ("/tmp/patch."+geteuid(previous_object()))

// Define this if explode() removes only one of the delimiters
// from the begining.
#undef NEW_EXPLODE

// Strip I first characters from string.
#define STRIP(S,I) (S[I..<1])

#define MIN(A,B) (((A)<(B))?(A):(B))
#define MAX(A,B) (((A)>(B))?(A):(B))

#define IS_VERBOSE is_verbose
#define WRITE(X) write(X)
#define VERBOSE(X) {if( IS_VERBOSE ) WRITE(X);}
#define ERR(X) write(X)

#define NORMAL_DIFF	1
#define CONTEXT_DIFF	2
#define UNIFIED_DIFF	3
#define DIFF_TYPE_LIST ({ "normal", "context", "unified" })
#define HUNK_MAX	100
#define H_OPT	2
#define H_LEN	4
#define H_LOC	6


#include <mudlib.h>

inherit DAEMON;

int diff_type, diff_line, diff_max, last_offset, last_frozen_line;
int is_verbose;

// Apply the hunk.
// If hunk == 0 return all unreturned lines from target.
// Returns: new lines upto the end of fixed lines.
string *
apply_hunk( string *target, mixed *hunk )
{
    int old_frozen, upto_line;

    // No more changes to apply.
    if( !hunk ) return target[last_frozen_line..sizeof(target)-1];

    old_frozen = last_frozen_line;
    upto_line = hunk[H_LOC] + last_offset - 1;
    if( hunk[H_LEN] ) upto_line -= 1;
    last_frozen_line = upto_line + hunk[H_LEN] + 1;
    if( upto_line + 1 < old_frozen ) {
	// This one shouldn`t appear if the locate_hunk() is correct.
	ERR( "misordered hunks! output would be garbled\n" );
	throw( "Error\n" );
	return 0;
    }

    return target[old_frozen..upto_line]
	+ hunk[1][0..hunk[H_LEN+1]-1];
}

// Create the output for reject file for hunks that could not be applied.
// The output is always in contex diff format.
string *
abort_hunk( mixed *hunk )
{
    int i, need0, need1;
    string head0, head1;

    for( i = hunk[H_LEN]; i--; ) {
	hunk[0][i] = sprintf( "%c %s", hunk[H_OPT][i], hunk[0][i] );
	if( hunk[H_OPT][i] != ' ' ) need0 = 1;
    }
    for( i = hunk[H_LEN+1]; i--; ) {
	hunk[1][i] = sprintf( "%c %s", hunk[H_OPT+1][i], hunk[1][i] );
	if( hunk[H_OPT+1][i] != ' ' ) need1 = 1;
    }

    if( !hunk[H_LEN] ) hunk[H_LEN]++;
    if( !hunk[H_LEN+1] ) hunk[H_LEN+1]++;
    head0 = sprintf("*** %d,%d ****", hunk[H_LOC], hunk[H_LOC]+hunk[H_LEN]-1 );
    head1 = sprintf("--- %d,%d ----", hunk[H_LOC+1]+last_offset,
		    hunk[H_LOC+1]+last_offset+hunk[H_LEN+1]-1 );

    return ({ "***************", head0 }) +
	( need0 ? hunk[0][0..hunk[H_LEN]]   : ({}) ) +
	({ head1 }) +
	( need1 ? hunk[1][0..hunk[H_LEN+1]] : ({}) );
}

// Find the location where the hunk should be applied.
// Sets globals: last_offset
// Returns: The fuzz factor. -1 if the location could not be found.
// Bugs: the fuzz (=how many context lines was ignored) is not used.
int
locate_hunk( string *target, mixed *hunk )
{
    int first_guess, max_pos_offset, max_neg_offset;
    int offset, offset_max, old_offset;

    old_offset = last_offset;
    first_guess = hunk[H_LOC] + last_offset;
    if( hunk[H_LEN] ) first_guess--;
    max_pos_offset = sizeof( target ) - first_guess - hunk[H_LEN];
    max_neg_offset = first_guess - last_frozen_line;
    if( max_pos_offset < 0 ) {
	max_neg_offset += max_pos_offset;
	first_guess += max_pos_offset;
	last_offset += max_pos_offset;
	max_pos_offset = 0;
    }
    if( max_neg_offset < 0 ) {
	if( ( max_pos_offset += max_neg_offset ) < 0 ) {
	    last_offset = old_offset;
	    return -1;
	}
	first_guess -= max_neg_offset;
	last_offset -= max_neg_offset;
	max_neg_offset = 0;
    }

    // Nothing known about the original file.
    if( !hunk[H_LEN] ) return 0;

    {
	int i, start, max;

	max = hunk[H_LEN];
	start = first_guess;
	while( i < max && hunk[0][i++] == target[start++] ) ;
	if( i == max ) return 0;

	for( offset_max=MAX(max_pos_offset,max_neg_offset);
	     offset++<offset_max; )
	{
	    if( offset <= max_pos_offset ) {
		start = first_guess + offset; i = 0;
		while( i < max && hunk[0][i++] == target[start++] ) ;
		if( i == max ) {
		    last_offset += offset;
		    return 0;
		}
	    }
	    if( offset <= max_neg_offset ) {
		start = first_guess - offset; i = 0;
		while( i < max && hunk[0][i++] == target[start++] ) ;
		if( i == max ) {
		    last_offset -= offset;
		    return 0;
		}
	    }
	} // for
    }

    // Failed to find a match.
    last_offset = old_offset;
    return -1;
}


// For -R flag.
void
reverse_hunk( mixed *hunk )
{
    int tmp;
#define SWAP(A,B,T) T=A;A=B;B=T
    SWAP(hunk[0], hunk[1], tmp);
    SWAP(hunk[2], hunk[3], tmp);
    SWAP(hunk[4], hunk[5], tmp);
#undef SWAP
}


#define ERR_AT(X) (ERR(sprintf("Malformed diff at line %d.\n\"%s\"\n",(X)+1,\
  diff[X][0..77])),-1)
// Find the limits of the next hunk.
// Changes the contents of 'hunk'.
// Returns: >0 if found, 0 if not, -1 if error.
int
next_hunk( string *diff, mixed *hunk )
{
    int i, tmp, part, fail;
    string str;

    if( diff_line >= diff_max ) return 0;
    i = diff_line;

    hunk[H_LEN] = hunk[H_LEN+1] = 0;

    if( diff_type == UNIFIED_DIFF ) {
	int part1, part2;

	str = diff[diff_line];
	if( sscanf( str, "@@ -%d,%d +%d,%d @@%s",
		   hunk[H_LOC], hunk[H_LEN], hunk[H_LOC+1], hunk[H_LEN+1],
		   str ) != 5 )
	{
	    return 0;
	}
	if( hunk[H_LEN] > HUNK_MAX || hunk[H_LEN+1] > HUNK_MAX ) {
	    ERR( sprintf( "Too big hunk at line %d.\n", i+1 ) );
	    return -1;
	}
	part = hunk[H_LEN] + hunk[H_LEN+1];
	part1 = part2 = 0;
	while( ++i < diff_max && part1 + part2 < part ) {
	    str = STRIP( diff[i], 1 );
	    switch( diff[i][0] ) {
	      case 0:
		// Special case for empty lines.
		str = "";
	      case ' ':
		if( hunk[H_LEN] > part1 && hunk[H_LEN+1] > part2 ) {
		    hunk[H_OPT][part1] = hunk[H_OPT+1][part2] = ' ';
		    hunk[0][part1++] = hunk[1][part2++] = str;
		    break;
		}
		// fallthrough
	      default:
		return ERR_AT( i );
	      case '-':
		if( hunk[H_LEN] > part1 ) {
		    hunk[H_OPT][part1] = '-';
		    hunk[0][part1++] = str;
		    break;
		}
		return ERR_AT( i );
	      case '+':
		if( hunk[H_LEN+1] > part2 ) {
		    hunk[H_OPT+1][part2] = '+';
		    hunk[1][part2++] = str;
		    break;
		}
		return ERR_AT( i );
	    }
	}
	if( part1 + part2 < part ) {
	    ERR( sprintf( "unexpected end of hunk at line %d\n", i+1 ) );
	    return -1;
	}

	diff_line = i;

    } else
    if( diff_type == CONTEXT_DIFF ) {
	int start_missing, replace_needed, part_len, hunk_part;

	if( diff[diff_line][0..7] != "********" ) return 0;
	if( ++i >= diff_max || !sscanf( diff[i], "*** %s", str ) ) {
	    return ERR_AT( i );
	}
	if( sscanf( str, "%d,%d", tmp, part_len ) == 2 && tmp ) {
	    part_len -= tmp - 1;
	} else {
	    part_len = tmp?1:0;
	}
	hunk[H_LOC] = tmp;
	if( part_len > HUNK_MAX ) {
	    ERR( sprintf( "Too big hunk at line %d.\n", i+1 ) );
	    return -1;
	}
	while( ++i < diff_max ) {
	    switch( ( str = diff[i] )[0] ) {
	      case '-':
		if( sscanf( str, "--- %s", str ) ) {
		    if( part != 0 && part != 1 ) return ERR_AT( i );
		    if( part_len ) {
			if( part == 1 ) return ERR_AT( i );
			start_missing = replace_needed = 1;
		    }
		    part = 2;
		    if( sscanf( str, "%d,%d", tmp, part_len ) == 2 && tmp ) {
			part_len -= tmp - 1;
		    } else {
			part_len = tmp?1:0;
		    }
		    hunk[H_LOC+1] = tmp;
		    hunk_part = 1;
		    if( part_len > HUNK_MAX ) {
			ERR( sprintf( "Too big hunk at line %d.\n", i+1 ) );
			return -1;
		    }
		    break;
		}
		replace_needed--;
		// fall through
	      case '!': case '+':
		replace_needed++;
		// fall through
	      case ' ':
		if( str[1] != ' ' || !part_len ) fail = 1;
		else {
		    part_len--;
		    if( !(part & 1) && part++ == 2 ) hunk[H_LEN+1] = 0;
		    tmp = str[0];
		    str = STRIP(str,2);
		    if( part == 1 || (start_missing && tmp==' ') ) {
			hunk[H_OPT][hunk[H_LEN]] = tmp;
			hunk[0][hunk[H_LEN]++] = str;
		    }
		    if( part == 3 || tmp == ' ' ) {
			hunk[H_OPT+1][hunk[H_LEN+1]] = tmp;
			hunk[1][hunk[H_LEN+1]++] = str;
		    }
		}
		break;
	      default:
		fail = 1;
	    }
	    if( fail ) break;
	}
	if( (part == 3) ? part_len : (part == 2) ? replace_needed : 1 ) {
	    ERR( sprintf( "unexpected end of hunk at line %d\n", i+1 ) );
	    return -1;
	}
	diff_line = i;

    } else {
	int len1, len2;

	if( !sscanf( diff[diff_line], "%d%s", tmp, str ) ) return 0;
	if( sscanf( str, ",%d%s", len1, str ) ) {
	    len1 -= tmp - 1;
	} else {
	    len1 = tmp?1:0;
	}
	hunk[H_LOC] = tmp;
	if( ( part = member_array( str[0], "dca" ) ) == -1 ||
	    !sscanf( STRIP(str,1), "%d%s", tmp, str ) )
	{
	    return 0;
	}
	if( sscanf( str, ",%d%s", len2, str ) ) {
	    len2 -= tmp - 1;
	} else {
	    len2 = tmp?1:0;
	}
	hunk[H_LOC+1] = tmp;
	if( str != "" ) return 0;
	if( part == 0 ) {
	    if( len2 > 1 ) return 0;
	    len2 = 0;
	} else if( part == 2 ) {
	    if( len1 > 1 ) return 0;
	    len1 = 0;
	}

	if( len1 > HUNK_MAX || len2 > HUNK_MAX ) {
	    ERR( sprintf( "Too big hunk at line %d.\n", diff_line+1 ) );
	    return -1;
	}

	i = diff_line;
	while( ++i < diff_max ) {
	    switch( ( str = diff[i] )[0] ) {
	      case '<':
		if( part == 2 || str[1] != ' ' || !len1 ) {
		    fail = 1;
		} else {
		    len1--;
		    hunk[H_OPT][hunk[H_LEN]] = '-';
		    hunk[0][hunk[H_LEN]++] = STRIP(str,2);
		}
		break;
	      case '>':
		if( part != 2 || str[1] != ' ' || !len2 ) {
		    fail = 1;
		} else {
		    len2--;
		    hunk[H_OPT+1][hunk[H_LEN+1]] = '+';
		    hunk[1][hunk[H_LEN+1]++] = STRIP(str,2);
		}
		break;
	      case '-':
		if( str[0..2] == "---" && part == 1 && !len1 ) {
		    part++;
		    break;
		}
		// fall through
	      default:
		fail = 1;
	    }
	    if( fail ) break;
	}
	if( len1 || len2 ) return ERR_AT( i );
	diff_line = i;
    }

    return diff_line;
}


// Has to be private (or static) because of file_size().
// Called by next_patch().
private string
make_fname( string file, string path, int strip_path )
{
    if( file ) {
	sscanf( file, "%s ", file );
	sscanf( file, "%s\t", file );
	if( strip_path ) {
	    while( sscanf( file, "%*s/%s", file ) );
	}
	file = resolv_path( path, file );
	if( file_size( file ) >= 0 ) return file;
    }
}

// Namelines: '*** ', '--- ', '+++ ', 'Index:'
// Looks for the first diff line and what mode it is.
// Sets globals: diff_type, diff_line, last_offset, last_frozen_line
// Returns: file_name or "" if none found. 0 is returned at errors.
static string
next_patch( string *diff, string file_name, string file_dir )
{
    int i, guess_type, start_line;
    string str, s1, fname_old, fname_new, fname_ind;

    // Set global variables
    diff_type = last_offset = last_frozen_line = 0;

    if( diff_line >= diff_max ) {
	VERBOSE( "done\n" );
	return 0;
    }
    VERBOSE( "Hmm..." );

    for( i = diff_line - 1; !diff_type && ++i < diff_max; ) {
	switch( ( str = diff[i] )[0] ) {
	  case '*':
	    if( str[0..7] ==  "********" ) {
		guess_type = CONTEXT_DIFF;
		start_line = i;
	    } else
	    if( guess_type == CONTEXT_DIFF ) {
		if( sscanf( str, "*** %*d" ) ) {
		    diff_type = CONTEXT_DIFF;
		}
	    } else {
		sscanf( str, "*** %s", fname_old );
	    }
	    break;
	  case '-':
	    sscanf( str, "--- %s", fname_new );
	    break;
	  case '+':
	    // Actually old/new are switched in unified diff
	    if( sscanf( str, "+++ %s", fname_old ) ) {
		guess_type = UNIFIED_DIFF;
		start_line = i;
	    }
	    break;
	  case '@':
	    if( guess_type == UNIFIED_DIFF && str[0..3] == "@@ -" ) {
		diff_type = UNIFIED_DIFF;
		start_line = i;
	    }
	    break;
	  case 'I':
	    if( sscanf( str, "Index:%s", fname_ind ) ) {
		sscanf( fname_ind, " %s", fname_ind );
	    }
	    break;
	  case '0'..'9':
	    if( ( sscanf( str, "%sa%s", s1, str ) ||
		  sscanf( str, "%sc%s", s1, str ) ||
		  sscanf( str, "%sd%s", s1, str ) ) &&
	        !sizeof( regexp( ({ s1+str }), "[^0-9,]" ) ) )
	    {
		guess_type = NORMAL_DIFF;
		start_line = i;
	    }
	    break;
	  case '>': case '<':
	    if( str[1] == ' ' && guess_type == NORMAL_DIFF ) {
		diff_type = NORMAL_DIFF;
	    }
	} // switch
	if( guess_type && i != start_line ) guess_type = 0;
    }
    if( !diff_type ) {
	if( diff_line ) {
	    VERBOSE( "  Ignoring the trailing garbage.\ndone\n" );
	} else {
	    ERR( "  I can't seem to find a patch in there anywhere.\n" );
	}
	return 0;
    }

    VERBOSE( sprintf( "%sooks like a %s diff to me...\n",
		     (diff_line ? "The next patch l" : "L"),
		     DIFF_TYPE_LIST[ diff_type-1 ]  ) );
    if( diff_line != start_line && IS_VERBOSE ) {
	int flag;

	if( flag = (start_line - 10 > diff_line) ) {
	    diff_line = start_line - 10;
	}
	WRITE( sprintf( "The text leading up to this was%s:\n",
		      flag ? " (last 10 lines)" : "" ) );
	WRITE( "--------------------------\n" );
	while( diff_line < start_line ) {
	    WRITE( sprintf( "|%s\n", diff[diff_line++] ) );
	}
	WRITE( "--------------------------\n" );
    }
    diff_line = start_line;

    for( i = 0; !file_name && i < 2; i++ ) {
	string tmp_new, tmp_old;

	tmp_new = make_fname( fname_new, file_dir, i );
	tmp_old = make_fname( fname_old, file_dir, i );
	if( tmp_new && tmp_old ) {
	    file_name = (strlen(tmp_new)<strlen(tmp_old)) ? tmp_new : tmp_old;
	} else if( tmp_new ) {
	    file_name = tmp_new;
	} else if( tmp_old ) {
	    file_name = tmp_old;
	} else {
	    file_name = make_fname( fname_ind, file_dir, i );
	}
    }

    if( file_name ) {
	return file_name;
    }

    ERR( "No file to patch.  Skipping...\n" );
    return "";
}

private string *
get_lines( string file_name )
{
    string file;

    switch( file_size( file_name ) ) {
      case -2: { write( file_name + " is a directory.\n" ); return 0; }
      case -1: { write( file_name + " is not a file or no permission.\n" );
		 return 0; }
    }
    file = read_file( file_name );
    if( !file ) {
	write( file_name + " is unreadable.\n" );
	return 0;
    }
#ifdef NEW_EXPLODE
    if( file[0] == '\n' ) {
	return ({ "" }) + explode( file, "\n" );
    }
#else
    {
	int i;

	while( file[i] == '\n' ) i++;
	if( i ) {
	    string *tmp;

	    tmp = allocate( i );
	    while( i-- ) tmp[i] = "";
	    return tmp + explode( file, "\n" );
	}
    }
#endif
    return explode( file, "\n" );
}

int
cmd_patch( string str )
{
    string *cmdline, *diff, target_file, target_dir;
    int reverse;

    seteuid( getuid( previous_object() ) );
    is_verbose = 1;

    cmdline = str ? explode( str, " " ) : ({ });
    while( sizeof( cmdline ) && cmdline[0][0] == '-' ) {
	int i;

	str = cmdline[0];
	cmdline = cmdline[1..sizeof(cmdline)-1];
	if( str == "-" ) break;
	i = 1;
	do {
	    switch( str[i] ) {
	      case 'R':
		reverse = 1;
		break;
	      case 's':
		is_verbose = 0;
		break;
	      default:
		notify_fail( sprintf( "Illegal flag: %c\n", str[i] ) );
		return 0;
	    }
	} while( ++i < strlen( str ) );
    }
    if( sizeof( cmdline ) < 1 || sizeof( cmdline ) > 2 ) {
	notify_fail( "Syntax: patch [-Rs] <diff_file> [target]\n" );
	return 0;
    }
    if( sizeof( cmdline ) == 2 ) {
	switch( file_size( target_file = resolv_path( "cwd", cmdline[1] ) ) ) {
	  case -1:
	    notify_fail( target_file +
			 " is not a file or directory or no permission.\n");
	    return 0;
	  case -2:
	    target_dir = target_file;
	    target_file = 0;
	}
    } else {
	target_dir = "cwd";
    }

    if( !pointerp( diff = get_lines( resolv_path( "cwd", cmdline[0] ) ) ) ) {
	return 1;
    }


    {
	string *target, *patched, *reject, file_name;
	int hunk_idx, fuzz, err;
	mixed *hunk;

	// Reset global variables
	diff_line = 0;
	diff_max = sizeof( diff );

	// Go through the diff file and apply each patch
	while( file_name = next_patch( diff, target_file, target_dir ) ){
	    if( file_name == "" || !( target = get_lines( file_name ) ) ) {
		file_name = 0;
	    } else {
		VERBOSE( sprintf( "Patching file %s...\n", file_name ) );
	    }

	    hunk = ({ allocate(HUNK_MAX), allocate(HUNK_MAX),
		      allocate(HUNK_MAX), allocate(HUNK_MAX),
		      0, 0, 0, 0, 0 });
	    patched = reject = ({ });

	    for( hunk_idx = 1; err = next_hunk( diff, hunk ); hunk_idx++ ) {
		if( err < 0 ) return 1;
		if( reverse ) reverse_hunk( hunk );
		if( !file_name ) {
		    VERBOSE( sprintf( "Hunk #%d ignored at %d.\n",
				      hunk_idx, hunk[H_LOC+1] + last_offset
				     ) );
		} else if( (fuzz=locate_hunk( target, hunk )) < 0 ){
		    reject += abort_hunk( hunk );
		    VERBOSE( sprintf( "Hunk #%d failed at %d.\n",
				      hunk_idx, hunk[H_LOC+1] + last_offset
				     ) );
		} else {
		    patched += apply_hunk( target, hunk );
		    VERBOSE( sprintf( "Hunk #%d succeeded at %d%s.\n",
				      hunk_idx, hunk[H_LOC+1] + last_offset,
				     //	fuzz not implemented yet
				     // fuzz?sprintf(" with fuzz %d", fuzz):"",
				      last_offset?sprintf(" (offset %d lines)",
							  last_offset) : ""
				     ) );
		}
	    }

	    // Just a sanity check
	    if( hunk_idx == 1 ) {
		VERBOSE( "Sorry. I was wrong.\n" );
		diff_line++;
		continue;
	    }

	    // Time to write the results to disk.
	    if( file_name ) {
		string tmp_file;

		patched += apply_hunk( target, 0 );
		rm( tmp_file = TMPFILE );
		write_file( tmp_file,
			    sizeof(patched)?implode( patched, "\n" )+"\n":"");
		rm( str = file_name + "~" );
		// Oh why doesn`t the return values of rename() get reversed?
		if( rename( file_name, str ) ) {
		    ERR( sprintf(
			"Failed to make a backup.  The patch is in %s.\n",
				 tmp_file ) );
		} else {
		    rename( tmp_file, file_name );
		}
		if( sizeof( reject ) ) {
		    rm( str = file_name + ".rej" );
		    write_file( str, implode( reject, "\n" ) + "\n" );
		    ERR( sprintf( "Wrote rejects to %s.\n", str ) );
		}
	    }
	} // while
    }
    return 1;
}

int
help()
{
    write( "Syntax: patch [-Rs] <diff_file> [target]\n\
Applies a normal, context or unified diff to a file.  The target can be a\n\
file or a directory.  If it is a directory it is used instead of current\n\
directory to resolve the file name in context diff.\n\
 -R  make a reverse patch\n\
 -s  work silently unless an error occurs\n"
	  );
    return 1;
}