Subject: Re: linux-next: manual merge of the notifications tree with the pidfd tree

From: David Howells <[email-masked]>
Date: Wed, 27 Jan 2021 10:59:22 +0000

#!/usr/bin/perl -w
#
# This script can be used to add, remove, rename and renumber system calls in
# the kernel sources and resolve simple git conflicts when a branch carrying
# new system calls is merged into another that also has new system calls with
# conflicting numbers.
#
# Usage:
#
#  ./scripts/syscall-manage.pl --add <name> [--compat]
#
#	Add a new system call, giving it the specified name and allocating it
#	the next number, bumping __NR_syscalls.  If --compat is specified, then
#	__SC_COMP() will be used in lieu of __SYSCALL() and the script will
#	attempt to emit appropriate compatibility lines into the tables.
#
#  ./scripts/syscall-manage.pl --rm <name>
#
#	Remove the system call with the specified name, decrementing
#	__NR_syscalls if it was the final one.
#
#  ./scripts/syscall-manage.pl --rename <name> <new_name>
#
#	Rename the system call with the specified name to the new name.
#
#  ./scripts/syscall-manage.pl --renumber
#
#	Renumber the system calls between 424 and __NR_syscalls to remove any
#	holes and update __NR_syscalls.
#
#  ./scripts/syscall-manage.pl --resolve
#
#	Resolve simple git conflicts across all system call table files
#	resulting from one branch being merged into another where both branches
#	add system calls with conflicting numbers.  The new syscalls are
#	renumbered, __NR_syscalls is updated and the conflict markers and any
#	extra definition of __NR_syscalls are removed.
#
use strict;

#
# List of files that need to be altered and their insertion patterns
#
my $master = "include/uapi/asm-generic/unistd.h";
my $sys_ni = "kernel/sys_ni.c";
my @tables = (
    { file	=> "arch/alpha/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME",
      num_offset => 110 },
    { file	=> "arch/arm/tools/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    { file	=> "arch/ia64/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    { file	=> "arch/m68k/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    { file	=> "arch/microblaze/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    { file	=> "arch/mips/kernel/syscalls/syscall_n32.tbl",
      pattern	=> "%NUM n32 %NAME sys_%NAME",
      compat	=> 0 },
    { file	=> "arch/mips/kernel/syscalls/syscall_n32.tbl",
      pattern	=> "%NUM n32 %NAME compat_sys_%NAME",
      compat	=> 1 },
    { file	=> "arch/mips/kernel/syscalls/syscall_n64.tbl",
      pattern	=> "%NUM n64 %NAME sys_%NAME" },
    { file	=> "arch/mips/kernel/syscalls/syscall_o32.tbl",
      pattern	=> "%NUM o32 %NAME sys_%NAME",
      compat	=> 0 },
    { file	=> "arch/mips/kernel/syscalls/syscall_o32.tbl",
      pattern	=> "%NUM o32 %NAME sys_%NAME compat_sys_%NAME",
      compat	=> 1 },
    { file	=> "arch/parisc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME",
      compat	=> 0 },
    { file	=> "arch/parisc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME compat_sys_%NAME",
      compat	=> 1 },
    { file	=> "arch/powerpc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME",
      compat	=> 0 },
    { file	=> "arch/powerpc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME compat_sys_%NAME",
      compat	=> 1 },
    { file	=> "arch/s390/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME sys_%NAME",
      widths	=> [ 8, 8, 24, 32, 32],
      compat	=> 0 },
    { file	=> "arch/s390/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME compat_sys_%NAME",
      widths	=> [ 8, 8, 24, 32, 32],
      compat	=> 1 },
    { file	=> "arch/sh/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    { file	=> "arch/sparc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME",
      compat	=> 0 },
    { file	=> "arch/sparc/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME compat_sys_%NAME",
      compat	=> 1 },
    { file	=> "arch/x86/entry/syscalls/syscall_32.tbl",
      pattern	=> "%NUM i386 %NAME sys_%NAME __ia32_sys_%NAME",
      widths	=> [ 8, 8, 24, 32, 32],
      compat	=> 0 },
    { file	=> "arch/x86/entry/syscalls/syscall_32.tbl",
      pattern	=> "%NUM i386 %NAME sys_%NAME __ia32_compat_sys_%NAME",
      widths	=> [ 8, 8, 24, 32, 32],
      compat	=> 1 },
    { file	=> "arch/x86/entry/syscalls/syscall_64.tbl",
      pattern	=> "%NUM common %NAME __x64_sys_%NAME",
      widths	=> [ 8, 8, 24, 32, 32] },
    { file	=> "arch/xtensa/kernel/syscalls/syscall.tbl",
      pattern	=> "%NUM common %NAME sys_%NAME" },
    #{ file	=> "tools/perf/arch/powerpc/entry/syscalls/syscall.tbl",
    #  pattern	=> "%NUM common %NAME sys_%NAME" },
    #{ file	=> "tools/perf/arch/s390/entry/syscalls/syscall.tbl",
    #  pattern	=> "%NUM common %NAME sys_%NAME sys_%NAME" },
    #{ file	=> "tools/perf/arch/x86/entry/syscalls/syscall_64.tbl",
    #  pattern	=> "%NUM common %NAME __x64_sys_%NAME" },
    );

my $common_base = 424;

#
# Helpers
#
sub read_file($)
{
    my ($file) = @_;
    my @lines;

    open(FD, "<" . "$file") || die $file;
    while (<FD>) {
	chomp($_);
	push @lines, $_;
    }
    close(FD) || die $file;
    return \@lines;
}

sub write_file($$)
{
    my ($file, $lines) = @_;

    print "Writing $file\n";
    open(FD, ">" . "$file") || die $file;
    print FD $_, "\n" foreach(@{$lines});
    close(FD) || die $file;
}

###############################################################################
#
# Add a new syscall to the master list and return the syscall number allocated.
#
###############################################################################
sub add_to_master($$)
{
    my ($name, $compat) = @_;
    my $f = $master;
    my $lines = read_file($f);
    my $num = -1;
    my $nr = -1;
    my $i;
    my $j = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	if ($l =~ /^#define\s+__NR_syscalls\s+([0-9]+)/) {
	    die "$f:$i: Multiple __NR_syscalls definitions\n" if ($nr != -1);
	    $nr = $1;
	    $j = $i;
	}

	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    if ($1 eq $name) {
		die "$f:$i: Syscall multiply defined (", $num, ")\n" if ($num != -1);
		print STDERR "$f:$i: Syscall already exists (", $2, ")\n";
		$num = $2;
	    }
	}
    }

    die "$f: error: Can't find __NR_syscalls\n" if ($nr == -1);

    if ($num == -1) {
	# Update the last syscall number
	$num = $nr;
	print "Allocating syscall number ", $num, "\n";
	$lines->[$j] = "#define __NR_syscalls " . ($nr + 1);

	# Rewind to the last syscall number definition
	while ($j--, $j >= 0 && $lines->[$j] eq "") {}
	die "$f:$j: error: Expecting #undef __NR_syscalls\n"
	    unless ($lines->[$j] =~ /^#undef\s+__NR_syscalls/);
	while ($j--, $j >= 0 && $lines->[$j] eq "") {}
	die "$f:%j: error: Expecting __SYSCALL or __SC_COMP\n"
	    unless ($lines->[$j] =~ /^(__SYSCALL|__SC_COMP|__SC_3264|__SC_COMP_3264)/);
	if ($lines->[$j - 1] =~ /^#define __NR_([a-zA-Z0-9_]+) ([0-9]+)/) {
	    die "$f:$j: error: Incorrect syscall number ($2 != $num)\n"
		if ($2 != $num - 1);
	} else {
	    die "$f:$j: error: Expecting #define __NR_*\n";
	}
	$j++;

	# Insert the new syscall number
	if ($compat == 0) {
	    splice(@{$lines}, $j, 0,
		   ( "#define __NR_$name $num",
		     "__SYSCALL(__NR_$name, sys_$name)" ));
	} elsif ($compat == 1) {
	    splice(@{$lines}, $j, 0,
		   ( "#define __NR_$name $num",
		     "__SC_COMP(__NR_$name, sys_$name, compat_sys_$name)" ));
	} else {
	    die;
	}

	write_file($f, $lines);
    }

    return $num;
}

###############################################################################
#
# Add tabs to a string to pad it out
#
###############################################################################
sub tab_to($$)
{
    my ($s, $width) = @_;

    if ($width == 8) {
	return $s . "\t";
    } elsif ($width == 16) {
	return $s . "\t" if (length($s) > 7);
	return $s . "\t\t";
    } elsif ($width == 24) {
	return $s . "\t" if (length($s) > 15);
	return $s . "\t\t" if (length($s) > 7);
	return $s . "\t\t\t";
    } elsif ($width == 32) {
	return $s . "\t" if (length($s) > 23);
	return $s . "\t\t" if (length($s) > 15);
	return $s . "\t\t\t" if (length($s) > 7);
	return $s . "\t\t\t\t";
    } else {
	die "Width $width\n";
    }
}

###############################################################################
#
# Tabulate a table line appropriately.
#
###############################################################################
sub tabulate($$)
{
    my ($l, $widths) = @_;
    my @bits = split(/\s+/, $l);

    my $rl = tab_to($bits[0], $widths->[0]);	# Syscall number
    $rl .= tab_to($bits[1], $widths->[1]);	# Syscall type
    $rl .= tab_to($bits[2], $widths->[2]);	# Syscall name

    # Add the syscall handlers
    if ($#bits == 3) {
	$rl .= $bits[3];
    } elsif ($#bits == 4) {
	$rl .= tab_to($bits[3], $widths->[3]);
	$rl .= $bits[4];
    } elsif ($#bits == 5) {
	$rl .= tab_to($bits[3], $widths->[4]);
	$rl .= tab_to($bits[4], $widths->[5]);
	$rl .= $bits[5];
    } else {
	die "Too many handlers\n";
    }
}

###############################################################################
#
# Add a new syscall to a syscall.tbl file.
#
###############################################################################
sub add_to_table($$$)
{
    my ($name, $num, $table) = @_;
    my $f = $table->{file};
    my $pattern = $table->{pattern};
    my $widths = $table->{widths} ? $table->{widths} : [ 8, 8, 32, 32, 32 ];
    my $lines = read_file($f);
    my $i;
    my $j = -1;

    $num += $table->{num_offset} if (exists $table->{num_offset});

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	my @bits = split(/\s+/, $l);
	next if ($#bits == -1);
	if ($bits[0] eq $num - 1) {
	    die "$f:$i: Duplicate syscall ", $num - 1, "\n" if $j != -1;
	    $j = $i;
	}
	if ($bits[0] eq $num) {
	    if ($bits[2] eq $name) {
		print STDERR "$f:$i: Ignoring already-added syscall ", $num, "\n";
		return;
	    }
	    die "$f:$i: Conflicting syscall ", $num, "\n";
	}
    }

    die "$f: error: Can't find syscall ", $num - 1, "\n" if ($j == -1);

    $pattern =~ s/%NAME/$name/g;
    $pattern =~ s/%NUM/$num/g;
    $pattern = tabulate($pattern, $widths);

    # Insert the new syscall entry after the preceding one.
    splice(@{$lines}, $j + 1, 0, ( $pattern ));

    write_file($f, $lines);
}

###############################################################################
#
# Remove a syscall from the master list.
#
###############################################################################
sub remove_from_master($)
{
    my ($name) = @_;
    my $f = $master;
    my $lines = read_file($f);
    my $num = -1;
    my $nr = -1;
    my $i;
    my $i_nr = -1;
    my $i_num = -1;
    my $c = 1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	if ($l =~ /^#define\s+__NR_syscalls\s+([0-9]+)/) {
	    die "$f:$i: Multiple __NR_syscalls definitions\n" if ($nr != -1);
	    $nr = $1;
	    $i_nr = $i;
	}

	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    if ($1 eq $name) {
		if ($num != -1) {
		    print STDERR "$f:$i: Syscall multiply defined (", $num, ")\n"
		}
		$num = $2; # Remove the last instance only
		$i_num = $i;
	    }
	}
    }

    die "$f: error: Can't find __NR_syscalls\n" if ($i_nr == -1);

    if ($i_num == -1) {
	print "Syscall not found in unistd.h\n";
	return;
    }

    # If the syscall number is the last one, deallocate it
    if ($nr == $num + 1) {
	print "Deallocating syscall number ", $num, "\n";
	$lines->[$i_nr] = "#define __NR_syscalls " . ($nr - 1);
    }

    # Remove the __SYSCALL or __SC_COMP line also
    if ($lines->[$i_num + 1] =~ /^(__SYSCALL|__SC_COMP|__SC_3264|__SC_COMP_3264)[(]__NR_$name,/) {
	$c++;
	$c++ if ($lines->[$i_num + 1] =~ /\\$/);
    }

    splice(@{$lines}, $i_num, $c, ());
    write_file($f, $lines);
}

###############################################################################
#
# Remove a syscall from a syscall.tbl file.
#
###############################################################################
sub remove_from_table($$)
{
    my ($name, $table) = @_;
    my $f = $table->{file};
    my $lines = read_file($f);
    my $i;
    my $j = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	my @bits = split(/\s+/, $l);
	my $num = $bits[0];
	next if ($#bits < 2);

	if ($bits[2] eq $name) {
	    print STDERR "$f:$i: Duplicate syscall ", $num, "\n" if ($j != -1);
	    $j = $i;
	}
    }

    if ($j == -1) {
	print STDERR "$f: error: Can't find syscall ", $name, "\n";
	return;
    }

    # Remove the syscall entry
    splice(@{$lines}, $j, 1, ());

    write_file($f, $lines);
}

###############################################################################
#
# Remove a syscall from kernel/sys_ni.c
#
###############################################################################
sub remove_from_sys_ni($)
{
    my ($name) = @_;
    my $f = $sys_ni;
    my $lines = read_file($f);
    my $i;
    my $j = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /COND_SYSCALL[_A-Z]*[(]$name[)]/) {
	    if ($j == -1) {
		$j = $i;
	    } else {
		print STDERR "$f:$i: Multiple COND_SYSCALLs\n";
	    }
	}
    }

    return if ($j == -1);

    # Remove the COND_SYSCALL entry
    splice(@{$lines}, $j, 1, ());

    write_file($f, $lines);
}

###############################################################################
#
# Rename a syscall in the master list.
#
###############################################################################
sub rename_in_master($$)
{
    my ($name, $name2) = @_;
    my $f = $master;
    my $lines = read_file($f);
    my $num = -1;
    my $i;
    my $i_num = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    if ($1 eq $name) {
		if ($num != -1) {
		    print STDERR "$f:$i: Syscall multiply defined (", $num, ")\n"
		}
		$num = $2; # Rename the last instance only
		$i_num = $i;
	    }
	}
    }

    if ($i_num == -1) {
	print "Syscall not found in unistd.h\n";
	return;
    }

    # Rename the __SYSCALL or __SC_COMP line also
    $lines->[$i_num] =~ s/$name/$name2/g;
    if ($lines->[$i_num + 1] =~ /^(__SYSCALL|__SC_COMP|__SC_3264|__SC_COMP_3264)[(]__NR_$name,/) {
	$lines->[$i_num + 1] =~ s/$name/$name2/g;
	$lines->[$i_num + 2] =~ s/$name/$name2/g if ($lines->[$i_num + 1] =~ /\\$/);
    }

    write_file($f, $lines);
}

###############################################################################
#
# Rename a syscall in a syscall.tbl file.
#
###############################################################################
sub rename_in_table($$$)
{
    my ($name, $name2, $table) = @_;
    my $f = $table->{file};
    my $pattern = $table->{pattern};
    my $widths = $table->{widths} ? $table->{widths} : [ 8, 8, 32, 32, 32 ];
    my $lines = read_file($f);
    my $i;
    my $j = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	my @bits = split(/\s+/, $l);
	my $num = $bits[0];
	next if ($#bits < 2);

	if ($bits[2] eq $name) {
	    print STDERR "$f:$i: Duplicate syscall ", $num, "\n" if ($j != -1);
	    $j = $i;
	}
    }

    if ($j == -1) {
	print STDERR "$f: error: Can't find syscall ", $name, "\n";
	return;
    }

    # Rename the syscall entry
    my $l = $lines->[$j];
    $l =~ s/$name/$name2/g;
    $lines->[$j] = $pattern = tabulate($l, $widths);

    write_file($f, $lines);
}

###############################################################################
#
# Rename a syscall in kernel/sys_ni.c
#
###############################################################################
sub rename_in_sys_ni($$)
{
    my ($name, $name2) = @_;
    my $f = $sys_ni;
    my $lines = read_file($f);
    my $changed = 0;
    my $i;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /COND_SYSCALL[_A-Z]*[(]$name[)]/) {
	    $lines->[$i] =~ s/$name/$name2/;
	    $changed = 1;
	}
    }

    write_file($f, $lines) if ($changed);
}

###############################################################################
#
# Resolve git-conflicted syscalls in the master list.
#
###############################################################################
sub resolve_conflicts_in_master()
{
    my $f = $master;
    my $lines = read_file($f);
    my $nr = -1;
    my $i;
    my $i_nr = -1;
    my $begin = -1;
    my $mid = -1;
    my $end = -1;
    my $in_section = 0;
    my $nr_in_section = 0;
    my %conflict_list = ();

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /^#define\s+__NR_syscalls\s+([0-9]+)/) {
	    if ($in_section == 0) {
		# Before '<<<<<<<'
		die "$f:$i: Multiple __NR_syscalls definitions\n[s=$in_section inr=$i_nr nis=$nr_in_section]\n"
		    unless ($i_nr == -1);
		$nr = $1;
		$i_nr = $i;
		$nr_in_section = 4;
	    } elsif ($in_section == 1 && $nr_in_section == 0) {
		# After '<<<<<<<'
		$nr = $1;
		$i_nr = $i;
		$nr_in_section = 1;
	    } elsif ($in_section == 2 && $nr_in_section == 1) {
		# After '======='
		$i_nr = $i;
		$nr_in_section = 2;
	    } elsif ($in_section == 3 && $nr_in_section == 0) {
		# After '>>>>>>>'
		$nr = $1;
		$i_nr = $i;
		$nr_in_section = 3;
	    } else {
		die "$f:$i: Multiple __NR_syscalls definitions\n[s=$in_section inr=$i_nr nis=$nr_in_section]\n";
	    }
	    next;
	}
	next if ($in_section == 3);

	if ($l =~ /^<<<<<<</) {
	    $begin = $i;
	    $in_section = 1;
	    next;
	}
	if ($l =~ /^=======/) {
	    $mid = $i;
	    $in_section = 2;
	    next;
	}
	if ($l =~ /^>>>>>>>/) {
	    $end = $i;
	    $in_section = 3;
	    next;
	}
	next if ($in_section == 0);
    }

    die "$f: error: Can't find __NR_syscalls\n" if ($i_nr == -1);

    # Analyse the pre-merge syscalls.
    my $top = -1;
    my $stop = ($begin == -1) ? $#{$lines} : $begin;
    for ($i = 0; $i < $stop; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    my $name = $1;
	    my $num = $2;
	    next if ($name eq "syscalls");
	    die "$f:$i: Redefinition of $name\n" if (exists($conflict_list{$name}));
	    die "$f:$i: Number regression\n" if ($num < $top);
	    $top = $num;
	    $conflict_list{$name} = $num;
	    #print "Keep __NR_", $name, " as ", $num, "\n";
	}
    }

    if ($in_section == 0) {
	print "$f: Couldn't find section to be resolved\n";
	return \%conflict_list;
    }

    # Analyse what we're merging into.
    for ($i = $begin + 1; $i < $mid; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    my $name = $1;
	    my $num = $2;
	    next if ($name eq "syscalls");
	    die "$f:$i: Redefinition of $name\n" if (exists($conflict_list{$name}));
	    die "$f:$i: Number regression\n" if ($num < $top);
	    $top = $num;
	    $conflict_list{$name} = $num;
	    print "Keep __NR_", $name, " as ", $num, "\n";
	}
    }

    die "$f: Last number (", $top, ") different to limit-1 (", $nr - 1, ")\n"
	if ($top != -1 && $top != $nr - 1);

    # Renumber what we're merging in.
    for ($i = $mid + 1; $i < $end; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    my $name = $1;
	    my $num = $2;
	    next if ($name eq "syscalls");
	    if (exists($conflict_list{$name})) {
		warn "$f:$i: Definition of $name in both branches\n";
		# Remove the duplicate
		splice(@{$lines}, $i, 2, ());
		$end -= 2;
		$i--;
		next;
	    }
	    my $new = $nr;
	    $conflict_list{$name} = $new;
	    $l =~ s/(\s)$num/${1}$new/;
	    print "Reassign __NR_", $name, " to ", $new, "\n";
	    $lines->[$i] = $l;
	    $nr++;
	}
    }

    # Adjust __NR_syscalls
    if ($lines->[$i_nr] =~ /^#define\s+__NR_syscalls\s+([0-9]+)/) {
	my $num = $1;
	$lines->[$i_nr] =~ s/(\s)$num/${1}$nr/;
	print "__NR_syscalls set to $nr\n";
    }

    # Delete various bits, starting with the highest index and working towards
    # the lowest so as not to displace the higher indices.
    splice(@{$lines}, $end, 1, ());
    splice(@{$lines}, $mid, 1, ());
    for ($i = $mid - 1; $i > $begin; $i--) {
	my $l = $lines->[$i];

	splice(@{$lines}, $i, 1, ()) if ($l =~ /^#undef\s+__NR_syscalls\s*$/);
	splice(@{$lines}, $i, 1, ()) if ($l =~ /^#define\s+__NR_syscalls\s+([0-9]+)/);
	splice(@{$lines}, $i, 1, ()) if ($l =~ /^$/);
    }
    splice(@{$lines}, $begin, 1, ());

    write_file($f, $lines);

    return \%conflict_list;
}

###############################################################################
#
# Resolve git-conflicted syscalls in a syscall.tbl file.
#
###############################################################################
sub resolve_conflicts_in_table($$)
{
    my ($conflict_list, $table) = @_;
    my $f = $table->{file};
    my $lines = read_file($f);
    my $i;
    my $begin = -1;
    my $mid = -1;
    my $end = -1;
    my $in_section = 0;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];

	if ($l =~ /^<<<<<<</) {
	    $begin = $i;
	    $in_section = 1;
	    next;
	}
	if ($l =~ /^=======/) {
	    $mid = $i;
	    $in_section = 2;
	    next;
	}
	if ($l =~ /^>>>>>>>/) {
	    $end = $i;
	    $in_section = 3;
	    last;
	}
	next if ($in_section == 0);
    }

    if ($in_section == 0) {
	print "$f: Couldn't find section to be resolved\n";
	return;
    }

    my %used = ();
    for ($i = $begin + 1; $i < $mid; $i++) {
	my $l = $lines->[$i];
	next unless ($l =~ /^[0-9]/);
	my @bits = split(/\s+/, $l);
	my $num = $bits[0];
	my $name = $bits[2];
	next if ($#bits < 2);

	die "$f:$i: Undefined (my) syscall '", $name, "'\n"
	    unless (exists($conflict_list->{$name}));

	my $new = $conflict_list->{$name};
	$new += $table->{num_offset} ? $table->{num_offset} : 0;
	die "$f:$i: Redefined (my) syscall '", $name, "'\n" if (exists($used{$name}));
	$used{$name} = 1;
	next if ($num == $new);
    }

    for ($i = $mid + 1; $i < $end; $i++) {
	my $l = $lines->[$i];
	next unless ($l =~ /^[0-9]/);
	my @bits = split(/\s+/, $l);
	my $num = $bits[0];
	next if ($#bits < 2);
	my $name = $bits[2];

	die "$f:$i: Undefined (other) syscall '", $name, "'\n"
	    unless (exists($conflict_list->{$name}));

	my $new = $conflict_list->{$name};
	$new += $table->{num_offset} ? $table->{num_offset} : 0;
	if (exists($used{$name})) {
	    warn "$f:$i: Redefined (other) syscall '", $name, "'\n";
	    splice(@{$lines}, $i, 1, ());
	    $i--;
	    $end--;
	    next;
	}
	$used{$name} = 1;
	next if ($num == $new);
	$lines->[$i] =~ s/^$num/$new/;
    }

    # Delete the git markers, starting with the highest index and working
    # towards the lowest so as not to displace the higher indices.
    splice(@{$lines}, $end, 1, ());
    splice(@{$lines}, $mid, 1, ());
    splice(@{$lines}, $begin, 1, ());

    #print $_, "\n" foreach (@{$lines});
    #exit(88);
    
    write_file($f, $lines);
}

###############################################################################
#
# Renumber the syscall numbers in the master list that are between 424 and
# __NR_syscalls and reduce __NR_syscalls.
#
###############################################################################
sub renumber_master()
{
    my $f = $master;
    my $lines = read_file($f);
    my $nr = -1;
    my $next = $common_base;
    my $i;
    my $i_nr = -1;
    my %num_list = ();

    # Find the __NR_syscalls value.
    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	if ($l =~ /^#define\s+__NR_syscalls\s+([0-9]+)/) {
	    die "$f:$i: Redefinition of __NR_syscalls\n" if ($i_nr != -1);
	    $nr = $1;
	    $i_nr = $i;
	}
    }
    die "$f: error: Can't find __NR_syscalls\n" if ($i_nr == -1);

    # Renumber the definitions.
    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	if ($l =~ /^#define\s+__NR_([a-zA-Z0-9_]+)\s+([0-9]+)/) {
	    my $name = $1;
	    my $num = $2;

	    next if ($num < $common_base || $num >= $nr);
	    if ($num != $next) {
		print "Renumber ", $name, " from ", $num, " to ", $next, "\n";
		$lines->[$i] =~ s/(\s)$num/${1}$next/;
		$num_list{$name} = $next;
	    }

	    $next++;
	}
    }

    # Adjust __NR_syscalls
    $lines->[$i_nr] =~ s/(\s)$nr/${1}$next/;
    print "__NR_syscalls set to $next\n";

    write_file($f, $lines);
    return \%num_list;
}

###############################################################################
#
# Renumber the syscall numbers in a syscall.tbl file to match the master.
#
###############################################################################
sub renumber_table($$)
{
    my ($num_list, $table) = @_;
    my $f = $table->{file};
    my $lines = read_file($f);
    my $i;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	my @bits = split(/\s+/, $l);
	next if ($#bits < 2);
	my $num = $bits[0];
	my $name = $bits[2];

	next unless (exists($num_list->{$name}));
	my $new = $num_list->{$name};
	$new += $table->{num_offset} ? $table->{num_offset} : 0;
	next if ($num eq $new);

	$lines->[$i] =~ s/^$num/$new/;
    }

    write_file($f, $lines);
}

###############################################################################
#
# Change a syscall in a syscall.tbl file.
#
###############################################################################
sub change_in_table($$$)
{
    my ($name, $to_id, $table) = @_;
    my $f = $table->{file};
    my $pattern = $table->{pattern};
    my $widths = $table->{widths} ? $table->{widths} : [ 8, 8, 32, 32, 32 ];
    my $old_id = "";
    my $lines = read_file($f);
    my $i;
    my $j = -1;

    for ($i = 0; $i <= $#{$lines}; $i++) {
	my $l = $lines->[$i];
	my @bits = split(/\s+/, $l);
	next if ($#bits < 2);
	my $num = $bits[0];

	if ($bits[2] eq $name) {
	    print STDERR "$f:$i: Duplicate syscall ", $num, "\n" if ($j != -1);
	    $j = $i;
	    $old_id = $bits[0];
	}
    }

    if ($j == -1) {
	print STDERR "$f: error: Can't find syscall ", $name, "\n";
	return;
    }

    if ($old_id eq "") {
	print STDERR "$f: error: Can't find syscall number ", $name, "\n";
	return;
    }

    # Rename the syscall entry
    my $l = $lines->[$j];
    $l =~ s/^$old_id/$to_id/g;
    $lines->[$j] = $pattern = tabulate($l, $widths);

    write_file($f, $lines);
}

###############################################################################
#
# Decide what to do based on the script parameters
#
###############################################################################
sub format_error()
{
    print("Format: syscall-manage.pl --add <name> [--compat]\n");
    print("                          --change <name> <to-id>\n");
    print("                          --rm <name>\n");
    print("                          --rename <name>\n");
    print("                          --renumber\n");
    print("                          --resolve\n");
    exit(2);
}

format_error() if ($#ARGV < 0);

if ($ARGV[0] eq "--add") {
    format_error() if ($#ARGV < 1);

    my $name = $ARGV[1];
    my $compat = 0;
    $compat = 1 if ($#ARGV == 2 && $ARGV[2] eq "--compat");

    my $num = add_to_master($name, $compat);
    foreach my $table (@tables) {
	next if (exists($table->{compat}) && $compat != $table->{compat});
	add_to_table($name, $num, $table);
    }
} elsif ($ARGV[0] eq "--change") {
    format_error() if ($#ARGV < 2);

    my $name = $ARGV[1];
    my $to_id = $ARGV[2];
    foreach (@tables) {
	change_in_table($name, $to_id, $_);
    }
} elsif ($ARGV[0] eq "--rm") {
    format_error() if ($#ARGV < 1);

    my $name = $ARGV[1];
    remove_from_master($name);
    foreach (@tables) {
	remove_from_table($name, $_);
    }
    remove_from_sys_ni($name);
} elsif ($ARGV[0] eq "--rename") {
    format_error() if ($#ARGV < 2);

    my $name = $ARGV[1];
    my $name2 = $ARGV[2];
    rename_in_master($name, $name2);
    foreach (@tables) {
	rename_in_table($name, $name2, $_);
    }
    rename_in_sys_ni($name, $name2);
} elsif ($ARGV[0] eq "--resolve") {
    my $conflict_list = resolve_conflicts_in_master();
    foreach (@tables) {
	resolve_conflicts_in_table($conflict_list, $_);
    }
} elsif ($ARGV[0] eq "--renumber") {
    my $num_list = renumber_master();
    foreach (@tables) {
	renumber_table($num_list, $_);
    }
} else {
    format_error();
}