diff options
author | Luboš Luňák <l.lunak@suse.cz> | 2013-10-24 19:18:10 +0200 |
---|---|---|
committer | Thomas Arnhold <thomas@arnhold.org> | 2013-10-24 19:23:44 +0200 |
commit | 62c6d62e4b55fdc101f4d1edfbbb33f7961549b2 (patch) | |
tree | dcaa309c8c9cb6b6dbc589c25a080e059f7a5d03 | |
parent | b982fcc0c8ac46d7a38d648d04c76fadb2bcbbbf (diff) |
builtins for some common LO shell commands
Since fork() is rather slow on Cygwin, this makes Windows builds
somewhat faster (especially things like delivering).
-rw-r--r-- | make-4.0-gbuild/Makefile.am | 2 | ||||
-rw-r--r-- | make-4.0-gbuild/job.c | 11 | ||||
-rw-r--r-- | make-4.0-gbuild/runbuiltin.c | 595 |
3 files changed, 606 insertions, 2 deletions
diff --git a/make-4.0-gbuild/Makefile.am b/make-4.0-gbuild/Makefile.am index 5156f474..75c252cc 100644 --- a/make-4.0-gbuild/Makefile.am +++ b/make-4.0-gbuild/Makefile.am @@ -42,7 +42,7 @@ endif make_SOURCES = ar.c arscan.c commands.c default.c dir.c expand.c file.c \ function.c getopt.c getopt1.c implicit.c job.c load.c \ loadapi.c main.c misc.c output.c read.c remake.c rule.c \ - signame.c strcache.c variable.c version.c vpath.c hash.c \ + signame.c strcache.c variable.c version.c vpath.c hash.c runbuiltin.c \ $(remote) if HAVE_GUILE diff --git a/make-4.0-gbuild/job.c b/make-4.0-gbuild/job.c index febfac0c..bb80c6a2 100644 --- a/make-4.0-gbuild/job.c +++ b/make-4.0-gbuild/job.c @@ -1274,10 +1274,12 @@ start_job_command (struct child *child) output_dump (&child->output); #endif + int ran_as_builtin = try_run_as_builtin( argv ); + /* Print the command if appropriate. */ if (just_print_flag || trace_flag || (!(flags & COMMANDS_SILENT) && !silent_flag)) - message (0, "%s", p); + message (0, "%s%s", ran_as_builtin ? "(Built-in) " : "", p); /* Tell update_goal_chain that a command has been started on behalf of this target. It is important that this happens here and not in @@ -1288,6 +1290,13 @@ start_job_command (struct child *child) ++commands_started; + if( ran_as_builtin ) + { + free (argv[0]); + free (argv); + goto next_command; + } + /* Optimize an empty command. People use this for timestamp rules, so avoid forking a useless shell. Do this after we increment commands_started so make still treats this special case as if it diff --git a/make-4.0-gbuild/runbuiltin.c b/make-4.0-gbuild/runbuiltin.c new file mode 100644 index 00000000..4e7af077 --- /dev/null +++ b/make-4.0-gbuild/runbuiltin.c @@ -0,0 +1,595 @@ +/* Running trivial commands as builtin in GNU Make. +Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, +1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, +2010 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make 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 3 of the License, or (at your option) any later +version. + +GNU Make 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, see <http://www.gnu.org/licenses/>. */ + +#include "job.h" +#include "make.h" + +#ifdef HAVE_DOS_PATHS +/* TODO: For some reason WINDOWS32 is not set when building on Cygwin, + so enable the fast Windows builtins this way. +*/ +#define MYWIN +#else +#define DISABLE /* don't bother otherwise */ +#endif + +#ifdef MYWIN +#include <windows.h> +#include <unistd.h> +#endif + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +static int equals( const char* str1, const char* str2 ) +{ + if( str1 == NULL || str2 == NULL ) + return 0; + return strcmp( str1, str2 ) == 0; +} + +/* Split a shell command into arguments. + Based on construct_command_argv_internal(), with #ifdef ORIGINAL_CODE + for modifications. +*/ +static char** split_shell_args( char* line ) +{ +#ifndef ORIGINAL_CODE + /* Disable some code that's not needed here. */ + const int unixy_shell = 0; + const int oneshell = 0; + char** restp = NULL; +#endif + + static char sh_chars_sh[] = "#;\"*?[]&|<>(){}$`^"; + char** new_argv; + char* ap; + char* end; + char* argstr; + int i; + char *p; + int instring, word_has_equals, seen_nonequals, last_argument_was_empty; + + /* Make sure not to bother processing an empty line. */ + while (isblank ((unsigned char)*line)) + ++line; + if (*line == '\0') + return 0; + + i = strlen (line) + 1; + + /* More than 1 arg per character is impossible. */ + new_argv = xmalloc (i * sizeof (char *)); + +#ifdef ORIGINAL_CODE + /* All the args can fit in a buffer as big as LINE is. */ + ap = new_argv[0] = argstr = xmalloc (i); + end = ap + i; +#else + /* They actually need a bigger buffer, because we do variable expansion. */ + const int max_line_len = 64 * 1024; + ap = new_argv[0] = argstr = xmalloc( max_line_len ); + end = ap + max_line_len; +#endif + + /* I is how many complete arguments have been found. */ + i = 0; + instring = word_has_equals = seen_nonequals = last_argument_was_empty = 0; + for (p = line; *p != '\0'; ++p) + { + assert (ap <= end); + + if (instring) + { + /* Inside a string, just copy any char except a closing quote + or a backslash-newline combination. */ + if (*p == instring) + { + instring = 0; + if (ap == new_argv[0] || *(ap-1) == '\0') + last_argument_was_empty = 1; + } + else if (*p == '\\' && p[1] == '\n') + { + /* Backslash-newline is handled differently depending on what + kind of string we're in: inside single-quoted strings you + keep them; in double-quoted strings they disappear. + For DOS/Windows/OS2, if we don't have a POSIX shell, + we keep the pre-POSIX behavior of removing the + backslash-newline. */ + if (instring == '"' +#if defined (__MSDOS__) || defined (__EMX__) || defined (WINDOWS32) + || !unixy_shell +#endif + ) + ++p; + else + { + *(ap++) = *(p++); + *(ap++) = *p; + } + } + else if (*p == '\n' && restp != NULL) + { + /* End of the command line. */ + *restp = p; + goto end_of_line; + } +#ifdef ORIGINAL_CODE + /* Backslash, $, and ` are special inside double quotes. + If we see any of those, punt. + But on MSDOS, if we use COMMAND.COM, double and single + quotes have the same effect. */ + else if (instring == '"' && strchr ("\\$`", *p) != 0 && unixy_shell) + goto slow; +#endif + else + *ap++ = *p; + } +#ifdef ORIGINAL_CODE + else if (strchr (sh_chars, *p) != 0) + /* Not inside a string, but it's a special char. */ + goto slow; + else if (one_shell && *p == '\n') + /* In .ONESHELL mode \n is a separator like ; or && */ + goto slow; +#ifdef __MSDOS__ + else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.') + /* `...' is a wildcard in DJGPP. */ + goto slow; +#endif +#else /* ORIGINAL_CODE */ + /* Expand variables from gb_Helper_abbreviate_dirs. They are one char long and + are used for paths. */ + else if( *p == '$' && isalpha( p[ 1 ] ) && ( p[ 2 ] == '/' || p[ 2 ] == ' ' )) + { + int j; + for( j = 0; + j < i; + ++j ) + { + if( new_argv[ j ][ 0 ] == p[ 1 ] && new_argv[ j ][ 1 ] == '=' ) + { /* Found var definition, expand. */ + ++p; + const char* from = new_argv[ j ] + 2; + while( *from != '\0' ) + *ap++ = *from++; + break; + } + } + } +#endif + else + /* Not a special char. */ + switch (*p) + { + case '=': +#ifdef ORIGINAL_CODE + /* Equals is a special character in leading words before the + first word with no equals sign in it. This is not the case + with sh -k, but we never get here when using nonstandard + shell flags. */ + if (! seen_nonequals && unixy_shell) + goto slow; +#endif + word_has_equals = 1; + *ap++ = '='; + break; + case '\\': + /* Backslash-newline has special case handling, ref POSIX. + We're in the fastpath, so emulate what the shell would do. */ + if (p[1] == '\n') + { + /* Throw out the backslash and newline. */ + ++p; + + /* If there's nothing in this argument yet, skip any + whitespace before the start of the next word. */ + if (ap == new_argv[i]) + p = next_token (p + 1) - 1; + } + else if (p[1] != '\0') + { +#ifdef HAVE_DOS_PATHS + /* Only remove backslashes before characters special to Unixy + shells. All other backslashes are copied verbatim, since + they are probably DOS-style directory separators. This + still leaves a small window for problems, but at least it + should work for the vast majority of naive users. */ + +#ifdef __MSDOS__ + /* A dot is only special as part of the "..." + wildcard. */ + if (strneq (p + 1, ".\\.\\.", 5)) + { + *ap++ = '.'; + *ap++ = '.'; + p += 4; + } + else +#endif + if (p[1] != '\\' && p[1] != '\'' + && !isspace ((unsigned char)p[1]) + && strchr (sh_chars_sh, p[1]) == 0) + /* back up one notch, to copy the backslash */ + --p; +#endif /* HAVE_DOS_PATHS */ + + /* Copy and skip the following char. */ + *ap++ = *++p; + } + break; + + case '\'': + case '"': + instring = *p; + break; + + case '\n': + if (restp != NULL) + { + /* End of the command line. */ + *restp = p; + goto end_of_line; + } + else + /* Newlines are not special. */ + *ap++ = '\n'; + break; + + case ' ': + case '\t': + /* We have the end of an argument. + Terminate the text of the argument. */ + *ap++ = '\0'; + new_argv[++i] = ap; + last_argument_was_empty = 0; + +#ifdef ORIGINAL_CODE + /* Update SEEN_NONEQUALS, which tells us if every word + heretofore has contained an `='. */ + seen_nonequals |= ! word_has_equals; + if (word_has_equals && ! seen_nonequals) + /* An `=' in a word before the first + word without one is magical. */ + goto slow; +#endif + word_has_equals = 0; /* Prepare for the next word. */ + +#ifdef ORIGINAL_CODE + /* If this argument is the command name, + see if it is a built-in shell command. + If so, have the shell handle it. */ + if (i == 1) + { + register int j; + for (j = 0; sh_cmds[j] != 0; ++j) + { + if (streq (sh_cmds[j], new_argv[0])) + goto slow; +# ifdef __EMX__ + /* Non-Unix shells are case insensitive. */ + if (!unixy_shell + && strcasecmp (sh_cmds[j], new_argv[0]) == 0) + goto slow; +# endif + } + } +#endif + + /* Ignore multiple whitespace chars. */ + p = next_token (p) - 1; + break; + + default: + *ap++ = *p; + break; + } + } + end_of_line: + + if (instring) + /* Let the shell deal with an unterminated quote. */ +#ifdef ORIGINAL_CODE + goto slow; +#else + goto broken; +#endif + + /* Terminate the last argument and the argument list. */ + + *ap = '\0'; + if (new_argv[i][0] != '\0' || last_argument_was_empty) + ++i; + new_argv[i] = 0; + +#ifdef ORIGINAL_CODE + if (i == 1) + { + register int j; + for (j = 0; sh_cmds[j] != 0; ++j) + if (streq (sh_cmds[j], new_argv[0])) + goto slow; + } +#endif + +#ifndef ORIGINAL_CODE + /* Remove all variable definitions from the args, since they have been expanded, + and all commands that are builtin do not depend on them indirectly.*/ + { + char* writepos = argstr; + int readpos; + int skipnext = 0; + int new_i = 0; + for( readpos = 0; + readpos < i; + ++readpos ) + { + if( skipnext ) + { + skipnext = 0; + if( new_argv[ readpos ][ 0 ] == '&' && new_argv[ readpos ][ 1 ] == '&' + && new_argv[ readpos ][ 2 ] == '\0' ) + { + continue; /* skip the && following the variable definition */ + } + } + if( isalpha( new_argv[ readpos ][ 0 ] ) && new_argv[ readpos ][ 1 ] == '=' ) + skipnext = 1; /* skip this and possibly next */ + else + { + const char* from = new_argv[ readpos ]; + new_argv[ new_i ] = writepos; + while( *from != '\0' ) + *writepos++ = *from++; + *writepos++ = '\0'; + ++new_i; + } + } + i = new_i; + new_argv[ i ] = NULL; + } +#endif + if (new_argv[0] == 0) + { + /* Line was empty. */ + free (argstr); + free (new_argv); + return 0; + } + + return new_argv; + +#ifndef ORIGINAL_CODE +broken: + if (new_argv != 0) + { + /* Free the old argument list we were working on. */ + free (argstr); + free (new_argv); + } + return NULL; +#endif +} + +/* Is normal cases ignore any backticks or expansions that we cannot handle + and let the real shell take care of it. */ +static int is_normal_argument( const char* item ) +{ + return item != NULL && strchr( item, '`' ) == NULL && strchr( item, '$' ) == NULL; +} + +static int touch_file( const char* file ) +{ +#ifdef MYWIN + int ok = 0; + HANDLE handle = CreateFile( file, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL ); + if( handle != INVALID_HANDLE_VALUE ) + { + FILETIME time; + GetSystemTimeAsFileTime( &time ); + if( SetFileTime( handle, NULL, NULL, &time )) + ok = 1; + CloseHandle( handle ); + } + return ok; +#else + return 1; +#endif +} + +static int mkdir_p( const char* dir ) +{ +#ifdef MYWIN + return CreateDirectory( dir, NULL ) || GetLastError() == ERROR_ALREADY_EXISTS; +#else + return 1; +#endif +} + +static int echo_to_file( const char* file, const char* txt, int append_nl ) +{ +#ifdef MYWIN + int ok = 0; + HANDLE handle = CreateFile( file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL ); + if( handle != INVALID_HANDLE_VALUE ) + { + DWORD written; + if( WriteFile( handle, txt, strlen( txt ), &written, NULL ) + && written == strlen( txt ) + && ( !append_nl + || ( WriteFile( handle, "\n", 1, &written, NULL ) + && written == 1 ))) + { + ok = 1; + } + CloseHandle( handle ); + return ok; + } +#else + return 1; +#endif +} + +int try_run_as_builtin( char** orig_argv ) +{ +#ifdef DISABLE + return 0; +#endif + char** argv = orig_argv; + if( argv == NULL || argv[ 0 ] == NULL ) + return 0; + int builtin = 0; + +/* fprintf( stderr, "C: \'%s\' \'%s\' \'%s\' \'%s\'\n", argv[ 0 ], argv[ 0 ] ? argv[ 1 ] : NULL, + argv[ 0 ] && argv[ 1 ] ? argv[ 2 ] : NULL, argv[ 0 ] && argv[ 1 ] && argv[ 2 ] ? argv[ 3 ] : NULL ); +*/ + /* touch <file> */ + if( equals( argv[ 0 ], "touch" ) + && argv[ 1 ] != NULL + && argv[ 2 ] == NULL ) + { + if( touch_file( argv[ 1 ] )) + builtin = 1; + } + /* mkdir -p <dir> */ + else if( equals( argv[ 0 ], "mkdir" ) + && equals( argv[ 1 ], "-p" ) + && argv[ 2 ] != NULL + && argv[ 3 ] == NULL ) + { + if( mkdir_p( argv[ 2 ] )) + builtin = 1; + } + /* cp [--remove-destination] --no-dereference --force --preserve=timestamps <srcfile> <destfile> */ + else if( equals( argv[ 0 ], "/usr/bin/cp" ) || equals( argv[ 0 ], "/bin/cp" ) || equals( argv[ 0 ], "cp" )) + { + int remove = 0; + if( equals( argv[ 1 ], "--remove-destination" )) // may not be present + remove = 1; + if( equals( argv[ 1 + remove ], "--no-dereference" ) + && equals( argv[ 2 + remove ], "--force" ) + && equals( argv[ 3 + remove ], "--preserve=timestamps" ) + && argv[ 4 + remove ] != NULL + && argv[ 5 + remove ] != NULL + && argv[ 6 + remove ] == NULL ) + { +#ifdef MYWIN + const char* srcfile = argv[ 4 + remove ]; + const char* destfile = argv[ 5 + remove ]; + struct stat st; + if( remove ) + DeleteFile( destfile ); + /* Do we ever actually copy symlinks this way? Handle --no-dereference. + Not sure if Windows can handle them, so use POSIX. */ + if( lstat( srcfile, &st ) == 0 && S_ISLNK( st.st_mode )) + { + DeleteFile( destfile ); + if( symlink( srcfile, destfile ) == 0 ) + builtin = 1; + } + else + { + if( CopyFile( srcfile, destfile, 0 )) + builtin = 1; + } +#else + builtin = 1; +#endif + } + } + /* make has decided to run the command using shell */ + else if( is_bourne_compatible_shell( argv[ 0 ] ) + && equals( argv[ 1 ], "-c" ) + && argv[ 2 ] != NULL && argv[ 2 ][ 0 ] != ' ' + && argv[ 3 ] == NULL ) + { + argv = split_shell_args( orig_argv[ 2 ] ); +/* fprintf( stderr, "SH \'%s\' \'%s\' \'%s\' \'%s\' \'%s\'\n", argv ? argv[ 0 ] : NULL, + argv && argv[ 0 ] ? argv[ 1 ] : NULL, + argv && argv[ 0 ] && argv[ 1 ] ? argv[ 2 ] : NULL, + argv && argv[ 0 ] && argv[ 1 ] && argv[ 2 ] ? argv[ 3 ] : NULL, + argv && argv[ 0 ] && argv[ 1 ] && argv[ 2 ] && argv[ 3 ] ? argv[ 4 ] : NULL ); +*/ + if( argv == NULL ) + ; /* possibly sh given empty command given, but let it process anyway, just in case */ + /* mkdir -p <dir> && echo <txt> > <file> */ + else if( equals( argv[ 0 ], "mkdir" ) + && equals( argv[ 1 ], "-p" ) + && is_normal_argument( argv[ 2 ] ) + && equals( argv[ 3 ], "&&" ) + && equals( argv[ 4 ], "echo" ) + && argv[ 5 ] != NULL + && equals( argv[ 6 ], ">" ) + && is_normal_argument( argv[ 7 ] ) + && argv[ 8 ] == NULL ) + { + if( mkdir_p( argv[ 2 ] ) && echo_to_file( argv[ 7 ], argv[ 5 ], 1 )) + builtin = 1; + } + /* mkdir -p <dir> && touch <file> */ + else if( equals( argv[ 0 ], "mkdir" ) + && equals( argv[ 1 ], "-p" ) + && is_normal_argument( argv[ 2 ] ) + && equals( argv[ 3 ], "&&" ) + && equals( argv[ 4 ], "touch" ) + && is_normal_argument( argv[ 5 ] ) + && argv[ 6 ] == NULL ) + { + if( mkdir_p( argv[ 2 ] ) && touch_file( argv[ 5 ] )) + builtin = 1; + } + /* mkdir -p `dirname <file>` && touch <file> */ + else if( equals( argv[ 0 ], "mkdir" ) + && equals( argv[ 1 ], "-p" ) + && equals( argv[ 2 ], "`dirname" ) + && argv[ 3 ][ strlen( argv[ 3 ] ) - 1 ] == '`' + && equals( argv[ 4 ], "&&" ) + && equals( argv[ 5 ], "touch" ) + && is_normal_argument( argv[ 6 ] ) + && argv[ 7 ] == NULL ) + { + // do the dirname first + char* dir = strdup( argv[ 3 ] ); + dir[ strlen( dir ) - 1 ] = '\0'; // remove ` + if( is_normal_argument( dir ) && *dir != '\0' ) + { + char* pos = dir + strlen( dir ) - 1; + while( *pos != '/' && *pos != '\\' && pos > dir ) + --pos; + if( pos > dir ) + { + *pos = '\0'; + if( mkdir_p( dir ) && touch_file( argv[ 6 ] )) + builtin = 1; + } + } + free( dir ); + } + if( argv != NULL ) + { + free( argv[ 0 ] ); + free( argv ); + } + argv = orig_argv; + } + return builtin; +} |