diff options
Diffstat (limited to 'release/src/btools')
-rw-r--r-- | release/src/btools/Makefile | 17 | ||||
-rw-r--r-- | release/src/btools/fpkg.c | 330 | ||||
-rwxr-xr-x | release/src/btools/libfoo.pl | 408 | ||||
-rwxr-xr-x | release/src/btools/sizehistory.pl | 50 | ||||
-rwxr-xr-x | release/src/btools/uversion.pl | 75 |
5 files changed, 880 insertions, 0 deletions
diff --git a/release/src/btools/Makefile b/release/src/btools/Makefile new file mode 100644 index 00000000..06ec47e3 --- /dev/null +++ b/release/src/btools/Makefile @@ -0,0 +1,17 @@ + +CC = gcc +CFLAGS = -O3 -Wall + +all: fpkg + +fpkg: fpkg.c + $(CC) $(CFLAGS) -o $@ $^ + +dcfg: dcfg.c + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f *.o fpkg dcfg + +%.o: %.c + $(CC) $(CFLAGS) -c $< diff --git a/release/src/btools/fpkg.c b/release/src/btools/fpkg.c new file mode 100644 index 00000000..8e0244e1 --- /dev/null +++ b/release/src/btools/fpkg.c @@ -0,0 +1,330 @@ +/* + + fpkg - Package a firmware + Copyright (C) 2007 Jonathan Zarate + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#include <time.h> +#include <stdint.h> +#include <sys/stat.h> +#include <arpa/inet.h> + + +#define ROUNDUP(n, a) ((n + (a - 1)) & ~(a - 1)) + +#define TRX_MAGIC 0x30524448 +#define TRX_VERSION 1 +#define TRX_MAX_OFFSET 3 +#define TRX_MAX_LEN ((8 * 1024 * 1024) - ((256 + 128) * 1024)) // 8MB - (256K cfe + 128K cfg) + +typedef struct { + uint32_t magic; + uint32_t length; + uint32_t crc32; + uint32_t flag_version; + uint32_t offsets[TRX_MAX_OFFSET]; +} trx_t; + +typedef struct { + uint32_t crc32; + uint32_t magic; +} moto_t; + +typedef struct { + uint8_t magic[4]; + uint8_t extra1[4]; + uint8_t date[3]; + uint8_t version[3]; + uint8_t u2nd[4]; + uint8_t hardware; + uint8_t serial; + uint16_t flags; + uint8_t extra2[10]; +} cytan_t; + +uint32_t *crc_table = NULL; +trx_t *trx = NULL; +int trx_count = 0; +int trx_final = 0; +int trx_padding; +time_t max_time = 0; + +int crc_init(void) +{ + uint32_t c; + int i, j; + + if (crc_table == NULL) { + if ((crc_table = malloc(sizeof(uint32_t) * 256)) == NULL) return 0; + for (i = 255; i >= 0; --i) { + c = i; + for (j = 8; j > 0; --j) { + if (c & 1) c = (c >> 1) ^ 0xEDB88320L; + else c >>= 1; + } + crc_table[i] = c; + } + } + return 1; +} + +void crc_done(void) +{ + free(crc_table); + crc_table = NULL; +} + +uint32_t crc_calc(uint32_t crc, uint8_t *buf, int len) +{ + while (len-- > 0) { + crc = crc_table[(crc ^ *buf) & 0xFF] ^ (crc >> 8); + ++buf; + } + return crc; +} + +void help(void) +{ + fprintf(stderr, + "fpkg - Package a firmware\n" + "Copyright (C) 2007 Jonathan Zarate\n" + "\n" + "Usage: -i <input> [-i <input>] {output}\n" + "Output:\n" + " TRX: -t <output file>\n" + " Linksys: -l <id>,<output file>\n" + " W54G WRT54G / WRT54GL\n" + " W54U WRTSL54GS\n" + " W54S WRTS54GS\n" + " W54s WRT54GS v4\n" + " Motorola: -m <id>,<output file>\n" + " 0x10577000 WE800\n" + " 0x10577040 WA840\n" + " 0x10577050 WR850\n" + "\n" + ); + exit(1); +} + +void load_image(const char *fname) +{ + struct stat st; + FILE *f; + long rsize; + char *p; + + if (trx_final) { + fprintf(stderr, "Cannot load another image if an output has already been written.\n"); + exit(1); + } + if (trx_count >= TRX_MAX_OFFSET) { + fprintf(stderr, "Too many input files.\n"); + exit(1); + } + + if (stat(fname, &st) != 0) { + perror(fname); + exit(1); + } + if (st.st_ctime > max_time) max_time = st.st_ctime; + + rsize = ROUNDUP(st.st_size, 4); + if ((trx->length + rsize) > TRX_MAX_LEN) { + fprintf(stderr, "Total size is too big.\n"); + exit(1); + } + + p = (char *)trx + trx->length; + if ((f = fopen(fname, "r")) == NULL) { + perror(fname); + exit(1); + } + if (fread((char *)trx + trx->length, st.st_size, 1, f) != 1) { + perror(fname); + exit(1); + } + fclose(f); + + trx->offsets[trx_count++] = trx->length; + trx->length += rsize; +} + +void finalize_trx(void) +{ + uint32_t len; + + if (trx_count == 0) { + fprintf(stderr, "No image was loaded.\n"); + exit(1); + } + + if (trx_final) return; + trx_final = 1; + + len = trx->length; + + trx->length = ROUNDUP(len, 4096); + trx->magic = TRX_MAGIC; + trx->flag_version = TRX_VERSION << 16; + trx->crc32 = crc_calc(0xFFFFFFFF, (void *)&trx->flag_version, + trx->length - (sizeof(*trx) - (sizeof(trx->flag_version) + sizeof(trx->offsets)))); + + trx_padding = trx->length - len; +} + +void create_trx(const char *fname) +{ + FILE *f; + + finalize_trx(); + + printf("Creating TRX: %s\n", fname); + + if (((f = fopen(fname, "w")) == NULL) || + (fwrite(trx, trx->length, 1, f) != 1)) { + perror(fname); + exit(1); + } + fclose(f); +} + +void create_cytan(const char *fname, const char *pattern) +{ + FILE *f; + cytan_t h; + char padding[1024 - sizeof(h)]; + struct tm *tm; + + if (strlen(pattern) != 4) { + fprintf(stderr, "Linksys signature must be 4 characters. \"%s\" is invalid.\n", pattern); + exit(1); + } + + finalize_trx(); + + printf("Creating Linksys %s: %s\n", pattern, fname); + + memset(&h, 0, sizeof(h)); + memcpy(h.magic, pattern, 4); + memcpy(h.u2nd, "U2ND", 4); + h.version[0] = 4; // 4.0.0 should be >= *_VERSION_FROM defined in code_pattern.h + h.flags = 0xFF; + tm = localtime(&max_time); + h.date[0] = tm->tm_year - 100; + h.date[1] = tm->tm_mon + 1; + h.date[2] = tm->tm_mday; + + memset(padding, 0, sizeof(padding)); + + if (((f = fopen(fname, "w")) == NULL) || + (fwrite(&h, sizeof(h), 1, f) != 1) || + (fwrite(trx, trx->length, 1, f) != 1) || + (fwrite(padding, sizeof(padding), 1, f) != 1)) { + perror(fname); + exit(1); + } + fclose(f); +} + +void create_moto(const char *fname, const char *signature) +{ + FILE *f; + moto_t h; + char *p; + + h.magic = strtoul(signature, &p, 0); + if (*p != 0) help(); + + finalize_trx(); + + printf("Creating Motorola 0x%08X: %s\n", h.magic, fname); + + h.magic = htonl(h.magic); + h.crc32 = crc_calc(0xFFFFFFFF, (void *)&h.magic, sizeof(h.magic)); + h.crc32 = htonl(crc_calc(h.crc32, (void *)trx, trx->length)); + + if (((f = fopen(fname, "w")) == NULL) || + (fwrite(&h, sizeof(h), 1, f) != 1) || + (fwrite(trx, trx->length, 1, f) != 1)) { + perror(fname); + exit(1); + } + fclose(f); +} + +int main(int argc, char **argv) +{ + char s[256]; + char *p; + int o; + + printf("\n"); + + if ((!crc_init()) || ((trx = calloc(1, TRX_MAX_LEN)) == NULL)) { + fprintf(stderr, "Not enough memory\n"); + exit(1); + } + trx->length = sizeof(*trx); + + while ((o = getopt(argc, argv, "i:t:l:m:")) != -1) { + switch (o) { + case 'i': + load_image(optarg); + break; + case 't': + create_trx(optarg); + break; + case 'l': + case 'm': + if (strlen(optarg) >= sizeof(s)) help(); + strcpy(s, optarg); + if ((p = strchr(s, ',')) == NULL) help(); + *p = 0; + ++p; + if (o == 'l') create_cytan(p, s); + else create_moto(p, s); + break; + default: + help(); + return 1; + } + } + + if (trx_count == 0) { + help(); + } + else { + finalize_trx(); + printf("\nTRX Image:\n"); + printf(" Total Size .... : %u (%.2fK)\n", trx->length, trx->length / 1024.0); + printf(" Images ...... : %u\n", trx->length - trx_padding); + printf(" Padding ..... : %d\n", trx_padding); + printf(" CRC-32 ........ : %8X\n", trx->crc32); + printf(" 128K Blocks ... : %u\n", (ROUNDUP(trx->length, (128 * 1024)) / (128 * 1024))); + printf(" Offsets:\n"); + for (o = 0; o < TRX_MAX_OFFSET; ++o) { + printf(" %d: 0x%08X\n", o, trx->offsets[o]); + } + } + printf("\n"); + return 0; +} diff --git a/release/src/btools/libfoo.pl b/release/src/btools/libfoo.pl new file mode 100755 index 00000000..f7025acf --- /dev/null +++ b/release/src/btools/libfoo.pl @@ -0,0 +1,408 @@ +#!/usr/bin/perl +# +# libfoo.pl +# Copyright (C) 2006-2008 Jonathan Zarate +# +# - strip un-needed objects +# - create xref of symbols used +# + +sub error +{ + print STDERR "\n*** ERROR: " . (shift) . "\n\n"; + exit 1; +} + +sub basename +{ + my $fn = shift; + if ($fn =~ /([^\/]+)$/) { + return $1; + } + return $fn; +} + +sub load +{ + my $fname = shift; + + if ((-l $fname) || + ($fname =~ /\/lib\/modules\/\d+\.\d+\.\d+/) || + ($fname =~ /\.(asp|gif|png|svg|js|jsx|css|txt|pat|sh)$/)) { + return; + } + + if (-d $fname) { + my $d; + if (opendir($d, $fname)) { + foreach (readdir($d)) { + if ($_ !~ /^\./) { + load($fname . "/" . $_); + } + } + closedir($d); + } + return; + } + + + my $f; + my $base; + my $ok; + my $s; + + $base = basename($fname); + print LOG "\n\nreadelf $base:\n"; + + open($f, "mipsel-linux-readelf -WhsdD ${fname} 2>&1 |") || error("readelf - $!\n"); + + while (<$f>) { + print LOG; + + if (/\s+Type:\s+(\w+)/) { + $elf_type{$base} = $1; + $ok = 1; + last; + } + } + + if (!$ok) { + close($f); + return; + } + + print "$elf_type{$base} $base", " " x 30, "\r"; + + push(@elfs, $base); + + while (<$f>) { + print LOG; + + if (/\(NEEDED\)\s+Shared library: \[(.+)\]/) { + push(@{$elf_lib{$base}}, $1); + } + elsif (/Symbol table for image:/) { + last; + } + } + + while (<$f>) { + print LOG; + + if (/\s+(WEAK|GLOBAL)\s+(?:DEFAULT|VISIBLE)\s+(\w+)\s+(\w+)/) { + $s = $3; + if ($2 eq 'UND') { + if ($1 eq 'GLOBAL') { + $elf_ext{$base}{$s} = 1; + } + else { + print LOG "*** not GLOBAL\n"; + } + } + elsif ($2 eq 'ABS') { + } + elsif ($2 =~ /^\d+$/) { + $elf_exp{$base}{$s} = 1; + } + else { + print LOG "*** unknown type\n"; + } + } + elsif (!/Num Buc:/) { + print LOG "*** strange line\n"; + } + } + + close($f); +} + +sub fixDynDep +{ + my ($user, $dep) = @_; + + if (!defined $elf_dyn{$user}{$dep}) { + push(@{$elf_lib{$user}}, $dep); + $elf_dyn{$user}{$dep} = 1; + + print LOG "FixDynDep: $user = $dep\n"; + } +} + +sub fixDyn +{ + my $s; + + foreach (@elfs) { + if (/^libipt_.+\.so$/) { + fixDynDep("iptables", $_); + } + elsif (/^CP\d+\.so$/) { + fixDynDep("smbd", $_); + } + } + + fixDynDep("l2tpd", "cmd.so"); + fixDynDep("l2tpd", "sync-pppd.so"); + +# fixDynDep("libbcm.so", "libshared.so"); +# fixDynDep("libbcm.so", "libc.so.0"); +} + +sub usersOf +{ + my $name = shift; + my $sym = shift; + my @x; + my $e; + my $l; + + @x = (); + foreach $e (@elfs) { + foreach $l (@{$elf_lib{$e}}) { + if ($l eq $name) { + if ((!defined $sym) || (defined $elf_ext{$e}{$sym})) { + push(@x, $e); + } + last; + } + } + } + return @x; +} + +sub resolve +{ + my $name = shift; + my $sym = shift; + my $l; + + foreach $l (@{$elf_lib{$name}}) { +# print "\n$l $sym ", $elf_exp{$l}{$sym}, "\n"; + return $l if (defined $elf_exp{$l}{$sym}); + } + return "*** unresolved ***"; +} + +sub fillGaps +{ + my $name; + my $sym; + my @users; + my $u; + my $t; + my $found; + +# print "Resolving implicit links...\n"; + + foreach $name (@elfs) { + foreach $sym (keys %{$elf_ext{$name}}) { + $found = 0; + if (resolve($name, $sym) eq "*** unresolved ***") { + @users = usersOf($name); + foreach $u (@users) { + # if exported by $u + if (defined $elf_exp{$u}{$sym}) { + fixDynDep($name, $u); + $found = 1; + } + # if exported by shared libs of $u + if (($t = resolve($u, $sym)) ne "*** unresolved ***") { + fixDynDep($name, $t); + $found = 1; + } + } + + if ($found == 0) { + print "Unable to resolve $sym used by $name\n", @users; + exit 1; + } + } + } + } +} + +sub tab +{ + my $current = shift; + my $target = shift; + my $s = ""; + my $n; + + while (1) { + $n = $current + (4 - ($current % 4)); + last if ($n > $target); + $s = $s . "\t"; + $current = $n; + } + while ($current < $target) { + $s = $s . " "; + $current++; + } + return $s; +} + +sub genXref +{ + my $f; + my $fname; + my $s; + my @u; + +# print "Generating Xref Report...\n"; + + open($f, ">libfoo_xref.txt"); + foreach $fname (sort keys %elf_type) { + print $f "$fname:\n"; + + if (scalar(@{$elf_lib{$fname}}) > 0) { + print $f "Dependency:\n"; + foreach $s (sort @{$elf_lib{$fname}}) { + print $f "\t$s", defined $elf_dyn{$fname}{$s} ? " (dyn)\n" : "\n"; + } + } + + if (scalar(keys %{$elf_exp{$fname}}) > 0) { + print $f "Export:\n"; + foreach $s (sort keys %{$elf_exp{$fname}}) { + @u = usersOf($fname, $s); + if (scalar(@u) > 0) { + print $f "\t$s", tab(length($s) + 4, 40), " > ", join(",", @u), "\n"; + } + else { + print $f "\t$s\n"; + } + } + } + + if (scalar(keys %{$elf_ext{$fname}}) > 0) { + print $f "External:\n"; + foreach $s (sort keys %{$elf_ext{$fname}}) { + print $f "\t$s", tab(length($s) + 4, 40), " < ", resolve($fname, $s), "\n"; + } + } + + print $f "\n"; + } + close($f); +} + + +sub genSO +{ + my ($so, $arc, $opt) = @_; + my $name = basename($so); + my $sym; + my $fn; + my $inuse; + my @used; + my @unused; + my $cmd; + my $before, $after; + + if (!-f $so) { + print "$name: not found, skipping...\n"; + return 0; + } + + foreach $sym (sort keys %{$elf_exp{$name}}) { + if (scalar(usersOf($name, $sym)) > 0) { + push(@used, $sym); + } + else { + push(@unused, $sym); + } + } + +# print "\n$name: Attempting to link ", scalar(@used), " and remove ", scalar(@unused), " objects...\n"; + + print LOG "\n\n${base}\n"; + + $cmd = "mipsel-uclibc-ld -shared -s -z combreloc --warn-common --fatal-warnings ${opt} -soname ${name} -o ${so}"; + foreach (@{$elf_lib{$name}}) { + if ((!$elf_dyn{$name}{$_}) && (/^lib(.+)\.so/)) { + $cmd .= " -l$1"; + } + else { +# print LOG "Not marking for linkage: $_\n"; + } + } +# print "$cmd -u... ${arc}\n"; + if (scalar(@used) == 0) { + print "\n\n\n$name: WARNING: Library is not used by anything.\n\n\n"; + <>; +# return 0; + } + $cmd .= " -u " . join(" -u ", @used) . " ". $arc; + + print LOG "Command: $cmd\n"; + print LOG "Used: ", join(",", @used), "\n"; + print LOG "Unused: ", join(",", @unused), "\n"; + + $before = -s $so; + + system($cmd); + if ($? != 0) { + error("ld returned $?"); + } + + $after = -s $so; + + print "$name: Attempted to remove ", scalar(@unused), "/", scalar(@unused) + scalar(@used), " symbols. "; + printf "%.2fK - %.2fK = %.2fK\n", $before / 1024, $after / 1024, ($before - $after) / 1024; + +# print "\n$name: Attempting to link ", scalar(@used), " and remove ", scalar(@unused), " objects...\n"; +# printf "Before: %.2fK / After: %.2fK / Removed: %.2fK\n\n", $before / 1024, $after / 1024, ($before - $after) / 1024; + + return ($before > $after) +} + + +## +## +## + +# print "\nlibfoo.pl - fooify shared libraries\n"; +# print "Copyright (C) 2006-2007 Jonathan Zarate\n\n"; + +$root = $ENV{"TARGETDIR"}; +$uclibc = $ENV{"TOOLCHAIN"}; +$router = $ENV{"SRCBASE"} . "/router"; + +if ((!-d $root) || (!-d $uclibc) || (!-d $router)) { + print "Missing or invalid environment variables\n"; + exit(1); +} + +#open(LOG, ">libfoo.debug"); +open(LOG, ">/dev/null"); + +print "Loading...\r"; +load($root); +print "Finished loading files.", " " x 30, "\r"; + +fixDyn(); +fillGaps(); + +genXref(); + + +genSO("${root}/lib/libc.so.0", "${uclibc}/lib/libc.a", "-init __uClibc_init ${uclibc}/lib/optinfo/interp.o"); +genSO("${root}/lib/libresolv.so.0", "${uclibc}/lib/libresolv.a"); +genSO("${root}/lib/libcrypt.so.0", "${uclibc}/lib/libcrypt.a"); +genSO("${root}/lib/libm.so.0", "${uclibc}/lib/libm.a"); +genSO("${root}/lib/libpthread.so.0", "${uclibc}/lib/libpthread.a"); +genSO("${root}/lib/libutil.so.0", "${uclibc}/lib/libutil.a"); +# genSO("${root}/lib/libdl.so.0", "${uclibc}/lib/libdl.a"); +# genSO("${root}/lib/libnsl.so.0", "${uclibc}/lib/libnsl.a"); + +genSO("${root}/usr/lib/libcrypto.so", "${router}/openssl/libcrypto.a"); +genSO("${root}/usr/lib/libzebra.so", "${router}/zebra/lib/libzebra.a"); +# genSO("${root}/usr/lib/libtamba.so", "${router}/samba3/source/bin/libtamba.a"); +# genSO("${root}/usr/lib/libiptc.so", "${router}/iptables/libiptc/libiptc.a"); +# genSO("${root}/usr/lib/libshared.so", "${router}/shared/libshared.a"); +# genSO("${root}/usr/lib/libnvram.so", "${router}/nvram/libnvram.a"); + +print "\n"; + +close(LOG); +exit(0); diff --git a/release/src/btools/sizehistory.pl b/release/src/btools/sizehistory.pl new file mode 100755 index 00000000..fe9e9525 --- /dev/null +++ b/release/src/btools/sizehistory.pl @@ -0,0 +1,50 @@ +#!/usr/bin/perl +# +# sizehistory +# Copyright (C) 2006 Jonathan Zarate +# + +if (($#ARGV < 0) || ($#ARGV > 1)) { + print "Usage: sizehistory <filename> [datafile]\n"; + exit 1; +} + +$fname = $ARGV[0]; +$dname = ($ARGV[1] || $fname) . ".size"; + +print "\nSize history for $fname\n\n"; +@size = `mipsel-linux-size $fname`; +foreach (@size) { + if (($text, $data, $bss, $total) = $_ =~ /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+/) { + $line = "$text\t$data\t$bss\t$total\t" . scalar localtime((stat($fname))[10]); + print "text\tdata\tbss\ttotal\ttime\n"; + + if (open(F, "<$dname")) { + @hist = <F>; + close(F); + if ($#hist >= 0) { + if ($#hist > 20) { + splice(@hist, 0, $#hist - 19); + } + print @hist; + if ($hist[$#hist] =~ /^(\d+)\t(\d+)\t(\d+)\t(\d+)\t/) { + if (($1 == $text) && ($2 == $data) && ($3 == $bss)) { + print "--- same size as last ---\n$line <= current\n"; + exit 0; + } + printf "%d\t%d\t%d\t%d\t--- changes since above ---\n", ($text - $1), ($data - $2), ($bss - $3), ($total - $4); + } + } + } + + print $line, "\n"; + + if ((localtime((stat($dname))[9]))[7] != (localtime())[7]) { + open(F, ">>$dname") || die "$dname: $!"; + print F $line, "\n"; + close(F); + } + exit 0; + } +} +exit 1; diff --git a/release/src/btools/uversion.pl b/release/src/btools/uversion.pl new file mode 100755 index 00000000..1e22b5d3 --- /dev/null +++ b/release/src/btools/uversion.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl +# +# uversion.pl +# Copyright (C) 2006 Jonathan Zarate +# +# - update the build number for Tomato +# + +use POSIX qw(strftime); + +sub error +{ + print "\nuversion error: $@\n"; + exit(1); +} + +sub help +{ + print "Usage: uversion --bump|--gen\n"; + exit(1); +} + +# +# + +if ($#ARGV != 0) { + help(); +} + +$path = "router/shared"; +$major = 0; +$minor = 0; +$build = 0; + +open(F, "$path/tomato_version") || error("opening tomato_version: $!"); +$_ = <F>; +if (!(($major, $minor, $build) = /^(\d+)\.(\d+)\.(\d+)$/)) { + error("Invalid version: '$_'"); +} +close(F); + + +if ($ARGV[0] eq "--bump") { + ++$build; + open(F, ">$path/tomato_version.~") || error("creating temp file: $!"); + printf F "%d.%02d.%04d", $major, $minor, $build; + close(F); + rename("$path/tomato_version.~", "$path/tomato_version") || error("renaming: $!"); + exit(0); +} + +if ($ARGV[0] ne "--gen") { + help(); +} + +$time = strftime("%a, %d %b %Y %H:%M:%S %z", localtime()); +$minor = sprintf("%02d", $minor); +$build = sprintf("%04d", $build); + +open(F, ">$path/tomato_version.h~") || error("creating temp file: $!"); +print F <<"END"; +#ifndef __TOMATO_VERSION_H__
+#define __TOMATO_VERSION_H__
+#define TOMATO_MAJOR "$major"
+#define TOMATO_MINOR "$minor"
+#define TOMATO_BUILD "$build"
+#define TOMATO_BUILDTIME "$time"
+#define TOMATO_VERSION "$major.$minor.$build"
+#endif
+END
+close(F); +rename("$path/tomato_version.h~", "$path/tomato_version.h") || error("renaming: $!"); + +print "Version: $major.$minor.$build ($time)\n"; +exit(0); |