mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-02 17:28:30 +00:00
Prove that Makefile is fully defined
The whole repository is now buildable with GNU Make Landlock sandboxing. This proves that no Makefile targets exist which touch files other than their declared prerequisites. In order to do this, we had to: 1. Stop code morphing GCC output in package.com and instead run a newly introduced FIXUPOBJ.COM command after GCC invocations. 2. Disable all the crumby Python unit tests that do things like create files in the current directory, or rename() files between folders. This ended up being a lot of tests, but most of them are still ok. 3. Introduce an .UNSANDBOXED variable to GNU Make to disable Landlock. We currently only do this for things like `make tags`. 4. This change deletes some GNU Make code that was preventing the execve() optimization from working. This means it should no longer be necessary in most cases for command invocations to be indirected through the cocmd interpreter. 5. Missing dependencies had to be declared in certain places, in cases where they couldn't be automatically determined by MKDEPS.COM 6. The libcxx header situation has finally been tamed. One of the things that makes this difficult is MKDEPS.COM only wants to consider the first 64kb of a file, in order to go fast. But libcxx likes to have #include lines buried after huge documentation. 7. An .UNVEIL variable has been introduced to GNU Make just in case we ever wish to explicitly specify additional things that need to be whitelisted which aren't strictly prerequisites. This works in a manner similar to the recently introduced .EXTRA_PREREQS feature. There's now a new build/bootstrap/make.com prebuilt binary available. It should no longer be possible to write invalid Makefile code.
This commit is contained in:
parent
acdf591833
commit
cf93ecbbb2
181 changed files with 1902 additions and 1986 deletions
428
third_party/make/job.c
vendored
428
third_party/make/job.c
vendored
|
@ -28,8 +28,22 @@ this program. If not, see <http://www.gnu.org/licenses/>. */
|
|||
#include "libc/runtime/stack.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/x/x.h"
|
||||
#include "libc/bits/safemacros.internal.h"
|
||||
#include "libc/x/x.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/bits/safemacros.internal.h"
|
||||
#include "libc/elf/struct/ehdr.h"
|
||||
#include "libc/bits/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "third_party/make/dep.h"
|
||||
|
||||
#define GOTO_SLOW \
|
||||
do { \
|
||||
kprintf("%s:%d: goto slow\n", __FILE__, __LINE__); \
|
||||
goto slow; \
|
||||
} while (0)
|
||||
|
||||
#ifdef WINDOWS32
|
||||
const char *default_shell = "sh.exe";
|
||||
int no_default_sh_exe = 1;
|
||||
|
@ -217,6 +231,8 @@ is_bourne_compatible_shell (const char *path)
|
|||
{
|
||||
/* List of known POSIX (or POSIX-ish) shells. */
|
||||
static const char *unix_shells[] = {
|
||||
"build/bootstrap/cocmd.com",
|
||||
"dash",
|
||||
"sh",
|
||||
"bash",
|
||||
"ksh",
|
||||
|
@ -1569,12 +1585,76 @@ start_waiting_jobs (void)
|
|||
}
|
||||
|
||||
|
||||
void Unveil (const char *path, const char *perm)
|
||||
bool IsDynamicExecutable(const char *prog)
|
||||
{
|
||||
int fd;
|
||||
Elf64_Ehdr e;
|
||||
struct stat st;
|
||||
if ((fd = open(prog, O_RDONLY)) == -1)
|
||||
return false;
|
||||
if (read(fd, &e, sizeof(e)) != sizeof(e))
|
||||
return false;
|
||||
close(fd);
|
||||
return e.e_type == ET_DYN &&
|
||||
READ32LE(e.e_ident) == READ32LE(ELFMAG);
|
||||
}
|
||||
|
||||
bool GetPermPrefix (const char *path, char out_perm[5], const char **out_path)
|
||||
{
|
||||
int c, n;
|
||||
for (n = 0;;)
|
||||
switch ((c = *path++)) {
|
||||
case 'r':
|
||||
case 'w':
|
||||
case 'c':
|
||||
case 'x':
|
||||
out_perm[n++] = c;
|
||||
out_perm[n] = 0;
|
||||
break;
|
||||
case ':':
|
||||
if (n)
|
||||
{
|
||||
*out_path = path;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adds path to sandbox, returning true if found. */
|
||||
bool Unveil (const char *path, const char *perm)
|
||||
{
|
||||
int e;
|
||||
char permprefix[5];
|
||||
|
||||
/* if path is like `rwcx:o/tmp` then `rwcx` will override perm */
|
||||
if (path && GetPermPrefix (path, permprefix, &path))
|
||||
perm = permprefix;
|
||||
|
||||
e = errno;
|
||||
unveil(path, perm);
|
||||
errno = e;
|
||||
if (unveil (path, perm) != -1)
|
||||
return true;
|
||||
|
||||
/* if we're not on openbsd or linux 5.13+ we assume it worked */
|
||||
if (errno == ENOSYS)
|
||||
{
|
||||
errno = e;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* path not found isn't really much of an error */
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
errno = e;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* otherwise fail */
|
||||
OSS (error, NILF, "%s: %s", path, strerror (errno));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* POSIX:
|
||||
|
@ -1625,39 +1705,87 @@ child_execute_job (struct childbase *child, int good_stdin, char **argv)
|
|||
struct child *c;
|
||||
char pathbuf[PATH_MAX];
|
||||
char outpathbuf[PATH_MAX];
|
||||
const struct variable *var;
|
||||
c = (struct child *)child;
|
||||
if (c->file->deps) {
|
||||
argv[0] = commandv (argv[0], pathbuf, sizeof (pathbuf));
|
||||
Unveil (argv[0], "rx");
|
||||
Unveil ("o/tmp", "rwcx");
|
||||
Unveil ("/dev/zero", "r");
|
||||
Unveil ("/dev/null", "rw");
|
||||
Unveil ("/dev/full", "rw");
|
||||
Unveil ("/etc/hosts", "r");
|
||||
Unveil ("/dev/stdin", "rw");
|
||||
Unveil ("/dev/stdout", "rw");
|
||||
Unveil ("/dev/stderr", "rw");
|
||||
Unveil ("/usr/bin/ape", "rx");
|
||||
Unveil ("/etc/console", "rw");
|
||||
Unveil ("/etc/services", "r");
|
||||
Unveil ("libc/integral", "r");
|
||||
Unveil ("/etc/protocols", "r");
|
||||
Unveil ("build/bootstrap", "rx");
|
||||
Unveil ("/etc/resolv.conf", "r");
|
||||
Unveil ("o/third_party/gcc", "rx");
|
||||
Unveil ("libc/disclaimer.inc", "r");
|
||||
if (strlen(c->file->name) < PATH_MAX) {
|
||||
const char *dir;
|
||||
strcpy (outpathbuf, c->file->name);
|
||||
dir = dirname (outpathbuf);
|
||||
makedirs (dir, 0755);
|
||||
Unveil (dir, "rwc");
|
||||
if (!lookup_variable_in_set (STRING_SIZE_TUPLE(".UNSANDBOXED"),
|
||||
c->file->variables->set))
|
||||
{
|
||||
/* resolve command into executable path */
|
||||
argv[0] = commandv (argv[0], pathbuf, sizeof (pathbuf));
|
||||
|
||||
if (argv[0][0] == '/' && IsDynamicExecutable (argv[0]))
|
||||
{
|
||||
/* make it easier to run dynamic system executables */
|
||||
Unveil ("/lib", "rx");
|
||||
Unveil ("/lib64", "rx");
|
||||
Unveil ("/usr/lib", "rx");
|
||||
Unveil ("/usr/lib64", "rx");
|
||||
Unveil ("/usr/local/lib", "rx");
|
||||
Unveil ("/usr/local/lib64", "rx");
|
||||
Unveil ("/etc/ld-musl-x86_64.path", "r");
|
||||
Unveil ("/etc/ld.so.conf", "r");
|
||||
Unveil ("/etc/ld.so.cache", "r");
|
||||
Unveil ("/etc/ld.so.conf.d", "r");
|
||||
Unveil ("/etc/ld.so.preload", "r");
|
||||
}
|
||||
else
|
||||
/* permit launching actually portable executables */
|
||||
if (!Unveil ("/usr/bin/ape", "rx"))
|
||||
Unveil (xjoinpaths (firstnonnull (getenv ("TMPDIR"),
|
||||
firstnonnull (getenv ("HOME"),
|
||||
".")),
|
||||
".ape"),
|
||||
"rx");
|
||||
|
||||
/* unveil executable */
|
||||
Unveil (argv[0], "rx");
|
||||
|
||||
/* unveil essential paths */
|
||||
Unveil ("/dev/zero", "r");
|
||||
Unveil ("/dev/null", "rw");
|
||||
Unveil ("/dev/full", "rw");
|
||||
Unveil ("/dev/stdin", "rw");
|
||||
Unveil ("/dev/stdout", "rw");
|
||||
Unveil ("/dev/stderr", "rw");
|
||||
|
||||
/* unveil cosmopolitan specific */
|
||||
Unveil ("o/tmp", "rwcx");
|
||||
Unveil ("libc/integral", "r");
|
||||
Unveil ("libc/disclaimer.inc", "r");
|
||||
Unveil ("build/bootstrap", "rx");
|
||||
Unveil ("o/third_party/gcc", "rx");
|
||||
|
||||
/* unveil target output directory */
|
||||
if (strlen(c->file->name) < PATH_MAX)
|
||||
{
|
||||
const char *dir;
|
||||
strcpy (outpathbuf, c->file->name);
|
||||
dir = dirname (outpathbuf);
|
||||
makedirs (dir, 0755);
|
||||
Unveil (dir, "rwc");
|
||||
}
|
||||
|
||||
/* unveil target prerequisites */
|
||||
for (d = c->file->deps; d; d = d->next)
|
||||
Unveil (d->file->name, "rx");
|
||||
|
||||
/* unveil explicit .UNVEIL entries */
|
||||
if ((var = lookup_variable_in_set (STRING_SIZE_TUPLE(".UNVEIL"),
|
||||
c->file->variables->set)))
|
||||
{
|
||||
char *val, *tok, *state, *start;
|
||||
start = val = strdup (variable_expand (var->value));
|
||||
while (tok = strtok_r (start, " \t\r\n", &state))
|
||||
{
|
||||
Unveil (tok, "r");
|
||||
start = 0;
|
||||
}
|
||||
free(val);
|
||||
}
|
||||
|
||||
/* commit sandbox */
|
||||
Unveil (0, 0);
|
||||
}
|
||||
for (d = c->file->deps; d; d = d->next)
|
||||
/* TODO(jart): remove w (do code morphing outside package.com) */
|
||||
Unveil (d->file->name, "rwx");
|
||||
Unveil (0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the command. */
|
||||
|
@ -1743,36 +1871,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
const char *shellflags, const char *ifs,
|
||||
int flags, char **batch_filename UNUSED)
|
||||
{
|
||||
#if defined (WINDOWS32)
|
||||
/* We used to have a double quote (") in sh_chars_dos[] below, but
|
||||
that caused any command line with quoted file names be run
|
||||
through a temporary batch file, which introduces command-line
|
||||
limit of 4K charcaters imposed by cmd.exe. Since CreateProcess
|
||||
can handle quoted file names just fine, removing the quote lifts
|
||||
the limit from a very frequent use case, because using quoted
|
||||
file names is commonplace on MS-Windows. */
|
||||
static const char *sh_chars_dos = "|&<>";
|
||||
static const char *sh_cmds_dos[] =
|
||||
{ "assoc", "break", "call", "cd", "chcp", "chdir", "cls", "color", "copy",
|
||||
"ctty", "date", "del", "dir", "echo", "echo.", "endlocal", "erase",
|
||||
"exit", "for", "ftype", "goto", "if", "if", "md", "mkdir", "move",
|
||||
"path", "pause", "prompt", "rd", "rem", "ren", "rename", "rmdir",
|
||||
"set", "setlocal", "shift", "time", "title", "type", "ver", "verify",
|
||||
"vol", ":", 0 };
|
||||
|
||||
static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^";
|
||||
static const char *sh_cmds_sh[] =
|
||||
{ "cd", "eval", "exec", "exit", "login", "logout", "set", "umask", "wait",
|
||||
"while", "for", "case", "if", ":", ".", "break", "continue", "export",
|
||||
"read", "readonly", "shift", "times", "trap", "switch", "test", "command",
|
||||
#ifdef BATCH_MODE_ONLY_SHELL
|
||||
"echo",
|
||||
#endif
|
||||
0 };
|
||||
|
||||
const char *sh_chars;
|
||||
const char **sh_cmds;
|
||||
#else /* must be UNIX-ish */
|
||||
static const char *sh_chars = "#;\"*?[]&|<>(){}$`^~!";
|
||||
static const char *sh_cmds[] =
|
||||
{ ".", ":", "alias", "bg", "break", "case", "cd", "command", "continue",
|
||||
|
@ -1780,15 +1878,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
"if", "jobs", "login", "logout", "read", "readonly", "return", "set",
|
||||
"shift", "test", "times", "trap", "type", "ulimit", "umask", "unalias",
|
||||
"unset", "wait", "while", 0 };
|
||||
|
||||
# ifdef HAVE_DOS_PATHS
|
||||
/* This is required if the MSYS/Cygwin ports (which do not define
|
||||
WINDOWS32) are compiled with HAVE_DOS_PATHS defined, which uses
|
||||
sh_chars_sh directly (see below). The value must be identical
|
||||
to that of sh_chars immediately above. */
|
||||
static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^~!";
|
||||
# endif /* HAVE_DOS_PATHS */
|
||||
#endif
|
||||
size_t i;
|
||||
char *p;
|
||||
#ifndef NDEBUG
|
||||
|
@ -1800,20 +1889,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
int instring, word_has_equals, seen_nonequals, last_argument_was_empty;
|
||||
char **new_argv = 0;
|
||||
char *argstr = 0;
|
||||
#ifdef WINDOWS32
|
||||
int slow_flag = 0;
|
||||
|
||||
if (!unixy_shell)
|
||||
{
|
||||
sh_cmds = sh_cmds_dos;
|
||||
sh_chars = sh_chars_dos;
|
||||
}
|
||||
else
|
||||
{
|
||||
sh_cmds = sh_cmds_sh;
|
||||
sh_chars = sh_chars_sh;
|
||||
}
|
||||
#endif /* WINDOWS32 */
|
||||
|
||||
if (restp != NULL)
|
||||
*restp = NULL;
|
||||
|
@ -1830,47 +1905,8 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
/* See if it is safe to parse commands internally. */
|
||||
if (shell == 0)
|
||||
shell = default_shell;
|
||||
#ifdef WINDOWS32
|
||||
else if (strcmp (shell, default_shell))
|
||||
{
|
||||
char *s1 = _fullpath (NULL, shell, 0);
|
||||
char *s2 = _fullpath (NULL, default_shell, 0);
|
||||
|
||||
slow_flag = strcmp ((s1 ? s1 : ""), (s2 ? s2 : ""));
|
||||
|
||||
free (s1);
|
||||
free (s2);
|
||||
}
|
||||
if (slow_flag)
|
||||
goto slow;
|
||||
#else /* not WINDOWS32 */
|
||||
#if defined (__MSDOS__) || defined (__EMX__)
|
||||
else if (strcasecmp (shell, default_shell))
|
||||
{
|
||||
extern int _is_unixy_shell (const char *_path);
|
||||
|
||||
DB (DB_BASIC, (_("$SHELL changed (was '%s', now '%s')\n"),
|
||||
default_shell, shell));
|
||||
unixy_shell = _is_unixy_shell (shell);
|
||||
/* we must allocate a copy of shell: construct_command_argv() will free
|
||||
* shell after this function returns. */
|
||||
default_shell = xstrdup (shell);
|
||||
}
|
||||
if (unixy_shell)
|
||||
{
|
||||
sh_chars = sh_chars_sh;
|
||||
sh_cmds = sh_cmds_sh;
|
||||
}
|
||||
else
|
||||
{
|
||||
sh_chars = sh_chars_dos;
|
||||
sh_cmds = sh_cmds_dos;
|
||||
}
|
||||
#else /* !__MSDOS__ */
|
||||
else if (strcmp (shell, default_shell))
|
||||
goto slow;
|
||||
#endif /* !__MSDOS__ && !__EMX__ */
|
||||
#endif /* not WINDOWS32 */
|
||||
|
||||
/* [jart] remove code that forces slow path if not using /bin/sh */
|
||||
|
||||
if (ifs)
|
||||
for (cap = ifs; *cap != '\0'; ++cap)
|
||||
|
@ -1942,14 +1978,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
quotes have the same effect. */
|
||||
else if (instring == '"' && strchr ("\\$`", *p) != 0 && unixy_shell)
|
||||
goto slow;
|
||||
#ifdef WINDOWS32
|
||||
/* Quoted wildcard characters must be passed quoted to the
|
||||
command, so give up the fast route. */
|
||||
else if (instring == '"' && strchr ("*?", *p) != 0 && !unixy_shell)
|
||||
goto slow;
|
||||
else if (instring == '"' && strncmp (p, "\\\"", 2) == 0)
|
||||
*ap++ = *++p;
|
||||
#endif
|
||||
else
|
||||
*ap++ = *p;
|
||||
}
|
||||
|
@ -1988,30 +2016,8 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
while (ISBLANK (p[1]))
|
||||
++p;
|
||||
}
|
||||
#ifdef WINDOWS32
|
||||
/* Backslash before whitespace is not special if our shell
|
||||
is not Unixy. */
|
||||
else if (ISSPACE (p[1]) && !unixy_shell)
|
||||
{
|
||||
*ap++ = *p;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
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. */
|
||||
if (p[1] != '\\' && p[1] != '\''
|
||||
&& !ISSPACE (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;
|
||||
}
|
||||
|
@ -2061,12 +2067,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
{
|
||||
if (streq (sh_cmds[j], new_argv[0]))
|
||||
goto slow;
|
||||
#if defined(__EMX__) || defined(WINDOWS32)
|
||||
/* Non-Unix shells are case insensitive. */
|
||||
if (!unixy_shell
|
||||
&& strcasecmp (sh_cmds[j], new_argv[0]) == 0)
|
||||
goto slow;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2121,23 +2121,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
free (new_argv);
|
||||
}
|
||||
|
||||
#ifdef WINDOWS32
|
||||
/*
|
||||
* Not eating this whitespace caused things like
|
||||
*
|
||||
* sh -c "\n"
|
||||
*
|
||||
* which gave the shell fits. I think we have to eat
|
||||
* whitespace here, but this code should be considered
|
||||
* suspicious if things start failing....
|
||||
*/
|
||||
|
||||
/* Make sure not to bother processing an empty line. */
|
||||
NEXT_TOKEN (line);
|
||||
if (*line == '\0')
|
||||
return 0;
|
||||
#endif /* WINDOWS32 */
|
||||
|
||||
{
|
||||
/* SHELL may be a multi-word command. Construct a command line
|
||||
"$(SHELL) $(.SHELLFLAGS) LINE", with all special chars in LINE escaped.
|
||||
|
@ -2148,9 +2131,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
size_t shell_len = strlen (shell);
|
||||
size_t line_len = strlen (line);
|
||||
size_t sflags_len = shellflags ? strlen (shellflags) : 0;
|
||||
#ifdef WINDOWS32
|
||||
char *command_ptr = NULL; /* used for batch_mode_shell mode */
|
||||
#endif
|
||||
|
||||
/* In .ONESHELL mode we are allowed to throw the entire current
|
||||
recipe string at a single shell and trust that the user
|
||||
|
@ -2168,12 +2148,7 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
|
||||
/* Remove and ignore interior prefix chars [@+-] because they're
|
||||
meaningless given a single shell. */
|
||||
if (is_bourne_compatible_shell (shell)
|
||||
#ifdef WINDOWS32
|
||||
/* If we didn't find any sh.exe, don't behave is if we did! */
|
||||
&& !no_default_sh_exe
|
||||
#endif
|
||||
)
|
||||
if (is_bourne_compatible_shell (shell))
|
||||
{
|
||||
const char *f = line;
|
||||
char *t = line;
|
||||
|
@ -2208,79 +2183,6 @@ construct_command_argv_internal (char *line, char **restp, const char *shell,
|
|||
}
|
||||
*t = '\0';
|
||||
}
|
||||
#ifdef WINDOWS32
|
||||
else /* non-Posix shell (cmd.exe etc.) */
|
||||
{
|
||||
const char *f = line;
|
||||
char *t = line;
|
||||
char *tstart = t;
|
||||
int temp_fd;
|
||||
FILE* batch = NULL;
|
||||
int id = GetCurrentProcessId ();
|
||||
PATH_VAR(fbuf);
|
||||
|
||||
/* Generate a file name for the temporary batch file. */
|
||||
sprintf (fbuf, "make%d", id);
|
||||
*batch_filename = create_batch_file (fbuf, 0, &temp_fd);
|
||||
DB (DB_JOBS, (_("Creating temporary batch file %s\n"),
|
||||
*batch_filename));
|
||||
|
||||
/* Create a FILE object for the batch file, and write to it the
|
||||
commands to be executed. Put the batch file in TEXT mode. */
|
||||
_setmode (temp_fd, _O_TEXT);
|
||||
batch = _fdopen (temp_fd, "wt");
|
||||
fputs ("@echo off\n", batch);
|
||||
DB (DB_JOBS, (_("Batch file contents:\n\t@echo off\n")));
|
||||
|
||||
/* Copy the recipe, removing and ignoring interior prefix chars
|
||||
[@+-]: they're meaningless in .ONESHELL mode. */
|
||||
while (*f != '\0')
|
||||
{
|
||||
/* This is the start of a new recipe line. Skip whitespace
|
||||
and prefix characters but not newlines. */
|
||||
while (ISBLANK (*f) || *f == '-' || *f == '@' || *f == '+')
|
||||
++f;
|
||||
|
||||
/* Copy until we get to the next logical recipe line. */
|
||||
while (*f != '\0')
|
||||
{
|
||||
/* Remove the escaped newlines in the command, and the
|
||||
blanks that follow them. Windows shells cannot handle
|
||||
escaped newlines. */
|
||||
if (*f == '\\' && f[1] == '\n')
|
||||
{
|
||||
f += 2;
|
||||
while (ISBLANK (*f))
|
||||
++f;
|
||||
}
|
||||
*(t++) = *(f++);
|
||||
/* On an unescaped newline, we're done with this
|
||||
line. */
|
||||
if (f[-1] == '\n')
|
||||
break;
|
||||
}
|
||||
/* Write another line into the batch file. */
|
||||
if (t > tstart)
|
||||
{
|
||||
char c = *t;
|
||||
*t = '\0';
|
||||
fputs (tstart, batch);
|
||||
DB (DB_JOBS, ("\t%s", tstart));
|
||||
tstart = t;
|
||||
*t = c;
|
||||
}
|
||||
}
|
||||
DB (DB_JOBS, ("\n"));
|
||||
fclose (batch);
|
||||
|
||||
/* Create an argv list for the shell command line that
|
||||
will run the batch file. */
|
||||
new_argv = xmalloc (2 * sizeof (char *));
|
||||
new_argv[0] = xstrdup (*batch_filename);
|
||||
new_argv[1] = NULL;
|
||||
return new_argv;
|
||||
}
|
||||
#endif /* WINDOWS32 */
|
||||
/* Create an argv list for the shell command line. */
|
||||
{
|
||||
int n = 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue