Files
RedBear-OS/local/recipes/dev/libtool/source/gnulib/lib/csharpexec.c
T

684 lines
23 KiB
C

/* Execute a C# program.
Copyright (C) 2003-2024 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2003.
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 3 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, see <https://www.gnu.org/licenses/>. */
#include <config.h>
#include <alloca.h>
/* Specification. */
#include "csharpexec.h"
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <error.h>
#include "dirname.h"
#include "concat-filename.h"
#include "canonicalize.h"
#include "cygpath.h"
#include "execute.h"
#include "spawn-pipe.h"
#include "wait-process.h"
#include "sh-quote.h"
#include "xalloc.h"
#include "xmalloca.h"
#include "copy-file.h"
#include "clean-temp-simple.h"
#include "clean-temp.h"
#include "gettext.h"
/* Handling of MONO_PATH is just like Java CLASSPATH. */
#define CLASSPATHVAR "MONO_PATH"
#define new_classpath new_monopath
#define set_classpath set_monopath
#define reset_classpath reset_monopath
#include "classpath.h"
#include "classpath.c"
#undef reset_classpath
#undef set_classpath
#undef new_classpath
#undef CLASSPATHVAR
/* Handling of clix' PATH variable is just like Java CLASSPATH. */
#if defined _WIN32 || defined __CYGWIN__
/* Native Windows, Cygwin */
#define CLASSPATHVAR "PATH"
#elif defined __APPLE__ && defined __MACH__
/* Mac OS X */
#define CLASSPATHVAR "DYLD_LIBRARY_PATH"
#else
/* Normal Unix */
#define CLASSPATHVAR "LD_LIBRARY_PATH"
#endif
#define new_classpath new_clixpath
#define set_classpath set_clixpath
#define reset_classpath reset_clixpath
#include "classpath.h"
#include "classpath.c"
#undef reset_classpath
#undef set_classpath
#undef new_classpath
#undef CLASSPATHVAR
#define _(str) gettext (str)
/* Survey of CIL interpreters.
Program from
mono mono
dotnet dotnet or MSVC
clix sscli (non-free)
With Mono, the MONO_PATH is a colon separated list of pathnames. (On
Windows: semicolon separated list of pathnames.)
We try the CIL interpreters in the following order:
1. "mono", because it is nowadays the "traditional" C# system on Unix.
2. "dotnet", because it is another (newer) free C# system on Unix.
3. "clix", although it is not free, because it is a kind of "reference
implementation" of C#.
But the order can be changed through the --enable-csharp configuration
option.
*/
static int
name_is_dll (const struct dirent *d)
{
if (d->d_name[0] != '.')
{
size_t d_name_len = strlen (d->d_name);
if (d_name_len > 4 && memcmp (d->d_name + d_name_len - 4, ".dll", 4) == 0)
return 1;
}
return 0;
}
static int
execute_csharp_using_mono (const char *assembly_path,
const char * const *libdirs,
unsigned int libdirs_count,
const char * const *args, unsigned int nargs,
bool verbose, bool quiet,
execute_fn *executer, void *private_data)
{
static bool mono_tested;
static bool mono_present;
if (!mono_tested)
{
/* Test for presence of mono:
"mono --version >/dev/null 2>/dev/null" */
const char *argv[3];
int exitstatus;
argv[0] = "mono";
argv[1] = "--version";
argv[2] = NULL;
exitstatus = execute ("mono", "mono", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
mono_present = (exitstatus == 0);
mono_tested = true;
}
if (mono_present)
{
char *old_monopath;
const char **argv =
(const char **) xmalloca ((2 + nargs + 1) * sizeof (const char *));
unsigned int i;
bool err;
/* Set MONO_PATH. */
old_monopath = set_monopath (libdirs, libdirs_count, false, verbose);
argv[0] = "mono";
argv[1] = assembly_path;
for (i = 0; i <= nargs; i++)
argv[2 + i] = args[i];
if (verbose)
{
char *command = shell_quote_argv (argv);
printf ("%s\n", command);
free (command);
}
err = executer ("mono", "mono", argv, private_data);
/* Reset MONO_PATH. */
reset_monopath (old_monopath);
freea (argv);
return err;
}
else
return -1;
}
static int
execute_csharp_using_dotnet (const char *assembly_path,
const char * const *libdirs,
unsigned int libdirs_count,
const char * const *args, unsigned int nargs,
bool verbose, bool quiet,
execute_fn *executer, void *private_data)
{
static bool dotnet_tested;
static bool dotnet_present;
if (!dotnet_tested)
{
/* Test for presence of dotnet:
"dotnet --list-runtimes >/dev/null 2>/dev/null" */
const char *argv[3];
int exitstatus;
argv[0] = "dotnet";
argv[1] = "--list-runtimes";
argv[2] = NULL;
exitstatus = execute ("dotnet", "dotnet", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
dotnet_present = (exitstatus == 0);
dotnet_tested = true;
}
if (dotnet_present)
{
bool err = false;
char *assembly_path_converted = cygpath_w (assembly_path);
/* Handle the -L options.
The way this works is that we have to copy (or symlink) the DLLs into
the directory where FOO.exe resides.
Maybe there is another way to do this, but I haven't found it, trying
- the --additionalprobingpath command-line option,
- the additionalProbingPaths property in runtimeconfig.json,
- adding a --deps deps.json option,
cf. <https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-running-an-application>
and <https://github.com/dotnet/runtime/blob/main/docs/design/features/host-probing.md>. */
char **tmpfiles = NULL;
size_t tmpfiles_alloc = 0;
size_t tmpfiles_count = 0;
if (libdirs_count > 0)
{
/* Copy the DLLs. */
char *assembly_dir = dir_name (assembly_path);
unsigned int l;
for (l = 0; l < libdirs_count; l++)
{
const char *libdir = libdirs[l];
struct dirent **dlls;
int num_dlls;
/* Get a list of all *.dll files in libdir. */
num_dlls = scandir (libdir, &dlls, name_is_dll, alphasort);
if (num_dlls < 0 && errno == ENOMEM)
xalloc_die ();
if (num_dlls <= 0)
{
dlls = NULL;
num_dlls = 0;
}
int i;
for (i = 0; i < num_dlls; i++)
{
char *target_dll =
xconcatenated_filename (assembly_dir, dlls[i]->d_name,
NULL);
struct stat statbuf;
if (stat (target_dll, &statbuf) == 0 || errno == EOVERFLOW)
{
/* A DLL of this name is already at the expected
location. */
}
else
{
char *absolute_target_dll =
canonicalize_filename_mode (target_dll, CAN_ALL_BUT_LAST);
if (absolute_target_dll == NULL)
{
if (errno == ENOMEM)
xalloc_die ();
}
else
{
char *libdir_dll =
xconcatenated_filename (libdir, dlls[i]->d_name,
NULL);
/* Create absolute_target_dll as a temporary file. */
if (register_temporary_file (absolute_target_dll) < 0)
xalloc_die ();
if (tmpfiles_count >= tmpfiles_alloc)
{
tmpfiles_alloc = 2 * tmpfiles_alloc + 1;
tmpfiles =
(char **)
xrealloc (tmpfiles, tmpfiles_alloc * sizeof (char *));
}
tmpfiles[tmpfiles_count++] = absolute_target_dll;
if (copy_file_to (libdir_dll, absolute_target_dll) != 0)
{
remove (absolute_target_dll);
unregister_temporary_file (absolute_target_dll);
error (0, 0, _("failed to copy '%s' to '%s'"),
libdir_dll, absolute_target_dll);
free (libdir_dll);
free (target_dll);
for (i = 0; i < num_dlls; i++)
free (dlls[i]);
free (dlls);
free (assembly_dir);
while (tmpfiles_count > 0)
{
char *tmpfile = tmpfiles[--tmpfiles_count];
remove (tmpfile);
unregister_temporary_file (tmpfile);
free (tmpfile);
}
free (tmpfiles);
return 1;
}
free (libdir_dll);
}
}
free (target_dll);
}
for (i = 0; i < num_dlls; i++)
free (dlls[i]);
free (dlls);
}
free (assembly_dir);
}
/* Test whether alongside FOO.exe, a file FOO.runtimeconfig.json already
exists. */
char *runtimeconfig_filename =
(char *) xmalloca (strlen (assembly_path) + 19 + 1);
{
size_t assembly_path_len = strlen (assembly_path);
if (assembly_path_len > 4
&& memcmp (assembly_path + (assembly_path_len - 4), ".exe", 4) == 0)
assembly_path_len -= 4;
char *p = runtimeconfig_filename;
memcpy (p, assembly_path, assembly_path_len);
p += assembly_path_len;
strcpy (p, ".runtimeconfig.json");
}
struct stat runtimeconfig_statbuf;
if (stat (runtimeconfig_filename, &runtimeconfig_statbuf) == 0
|| errno == EOVERFLOW)
{
const char **argv =
(const char **) xmalloca ((3 + nargs + 1) * sizeof (const char *));
unsigned int i;
argv[0] = "dotnet";
argv[1] = "exec";
argv[2] = assembly_path_converted;
for (i = 0; i <= nargs; i++)
argv[3 + i] = args[i];
if (verbose)
{
char *command = shell_quote_argv (argv);
printf ("%s\n", command);
free (command);
}
err = executer ("dotnet", "dotnet", argv, private_data);
freea (argv);
}
else
{
/* dotnet needs a FOO.runtimeconfig.json alongside FOO.exe in order to
execute FOO.exe. Create a dummy one in a temporary directory
(because the directory where FOO.exe sits is not necessarily
writable).
Documentation of this file format:
<https://learn.microsoft.com/en-us/dotnet/core/runtime-config/> */
char *netcore_version;
/* Invoke 'dotnet --list-runtimes' and extract the .NET Core version
from its output. */
{
netcore_version = NULL;
const char *argv[3];
argv[0] = "dotnet";
argv[1] = "--list-runtimes";
argv[2] = NULL;
/* Open a pipe to the program. */
int fd[1];
pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
{
error (0, 0, _("%s subprocess I/O error"), "dotnet");
err = true;
}
else
{
/* Retrieve its result. */
FILE *fp = fdopen (fd[0], "r");
if (fp == NULL)
error (EXIT_FAILURE, errno, _("fdopen() failed"));
char *line = NULL;
size_t linesize = 0;
for (;;)
{
size_t linelen = getline (&line, &linesize, fp);
if (linelen == (size_t)(-1))
{
error (0, 0, _("%s subprocess I/O error"), "dotnet");
err = true;
break;
}
if (linelen > 0 && line[linelen - 1] == '\n')
{
line[linelen - 1] = '\0';
linelen--;
if (linelen > 0 && line[linelen - 1] == '\r')
{
line[linelen - 1] = '\0';
linelen--;
}
}
/* The line has the structure
Microsoft.SUBSYSTEM VERSION [DIRECTORY] */
if (linelen > 22
&& memcmp (line, "Microsoft.NETCore.App ", 22) == 0)
{
char *version = line + 22;
char *version_end = strchr (version, ' ');
if (version_end != NULL)
{
*version_end = '\0';
netcore_version = xstrdup (version);
break;
}
}
}
free (line);
/* Read until EOF (otherwise the child process may get a
SIGPIPE signal). */
while (getc (fp) != EOF)
;
fclose (fp);
/* Remove zombie process from process list, and retrieve
exit status. */
int exitstatus =
wait_subprocess (child, "dotnet", true, false, true, false,
NULL);
if (exitstatus != 0)
{
error (0, 0, _("%s subprocess failed"), "dotnet");
err = true;
}
}
}
if (!err)
{
if (netcore_version == NULL)
{
error (0, 0, _("could not determine %s version"), "dotnet");
err = true;
}
}
if (!err)
{
/* Create the runtimeconfig.json file. */
struct temp_dir *tmpdir = create_temp_dir ("csharp", NULL, false);
if (tmpdir == NULL)
err = true;
else
{
char *runtimeconfig =
xconcatenated_filename (tmpdir->dir_name,
"runtimeconfig.json", NULL);
register_temp_file (tmpdir, runtimeconfig);
FILE *fp = fopen_temp (runtimeconfig, "w", false);
if (fp == NULL)
{
error (0, errno, _("failed to create \"%s\""), runtimeconfig);
unregister_temp_file (tmpdir, runtimeconfig);
err = true;
}
else
{
fprintf (fp,
"{ \"runtimeOptions\":\n"
" { \"framework\":\n"
" { \"name\": \"Microsoft.NETCore.App\", \"version\": \"%s\" }\n"
" }\n"
"}",
netcore_version);
if (fwriteerror_temp (fp))
{
error (0, errno, _("error while writing \"%s\" file"),
runtimeconfig);
err = true;
}
else
{
char *runtimeconfig_converted =
cygpath_w (runtimeconfig);
const char **argv =
(const char **)
xmalloca ((5 + nargs + 1) * sizeof (const char *));
unsigned int i;
argv[0] = "dotnet";
argv[1] = "exec";
argv[2] = "--runtimeconfig";
argv[3] = runtimeconfig_converted;
argv[4] = assembly_path_converted;
for (i = 0; i <= nargs; i++)
argv[5 + i] = args[i];
if (verbose)
{
char *command = shell_quote_argv (argv);
printf ("%s\n", command);
free (command);
}
err = executer ("dotnet", "dotnet", argv,
private_data);
freea (argv);
free (runtimeconfig_converted);
}
}
free (runtimeconfig);
cleanup_temp_dir (tmpdir);
}
}
}
freea (runtimeconfig_filename);
while (tmpfiles_count > 0)
{
char *tmpfile = tmpfiles[--tmpfiles_count];
remove (tmpfile);
unregister_temporary_file (tmpfile);
free (tmpfile);
}
free (tmpfiles);
free (assembly_path_converted);
return err;
}
else
return -1;
}
static int
execute_csharp_using_sscli (const char *assembly_path,
const char * const *libdirs,
unsigned int libdirs_count,
const char * const *args, unsigned int nargs,
bool verbose, bool quiet,
execute_fn *executer, void *private_data)
{
static bool clix_tested;
static bool clix_present;
if (!clix_tested)
{
/* Test for presence of clix:
"clix >/dev/null 2>/dev/null ; test $? = 1" */
const char *argv[2];
int exitstatus;
argv[0] = "clix";
argv[1] = NULL;
exitstatus = execute ("clix", "clix", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
clix_present = (exitstatus == 0 || exitstatus == 1);
clix_tested = true;
}
if (clix_present)
{
/* Here, we assume that 'clix' is a native Windows program, therefore
we need to use cygpath_w. */
char *assembly_path_converted = cygpath_w (assembly_path);
char *old_clixpath;
const char **argv =
(const char **) xmalloca ((2 + nargs + 1) * sizeof (const char *));
unsigned int i;
bool err;
/* Set clix' PATH variable. */
old_clixpath = set_clixpath (libdirs, libdirs_count, false, verbose);
argv[0] = "clix";
argv[1] = assembly_path_converted;
for (i = 0; i <= nargs; i++)
argv[2 + i] = args[i];
if (verbose)
{
char *command = shell_quote_argv (argv);
printf ("%s\n", command);
free (command);
}
err = executer ("clix", "clix", argv, private_data);
/* Reset clix' PATH variable. */
reset_clixpath (old_clixpath);
freea (argv);
free (assembly_path_converted);
return err;
}
else
return -1;
}
bool
execute_csharp_program (const char *assembly_path,
const char * const *libdirs,
unsigned int libdirs_count,
const char * const *args,
bool verbose, bool quiet,
execute_fn *executer, void *private_data)
{
unsigned int nargs;
int result;
/* Count args. */
{
const char * const *arg;
for (nargs = 0, arg = args; *arg != NULL; nargs++, arg++)
;
}
/* First try the C# implementation specified through --enable-csharp. */
#if CSHARP_CHOICE_MONO
result = execute_csharp_using_mono (assembly_path, libdirs, libdirs_count,
args, nargs, verbose, quiet,
executer, private_data);
if (result >= 0)
return (bool) result;
#endif
#if CSHARP_CHOICE_DOTNET
result = execute_csharp_using_dotnet (assembly_path, libdirs, libdirs_count,
args, nargs, verbose, quiet,
executer, private_data);
if (result >= 0)
return (bool) result;
#endif
/* Then try the remaining C# implementations in our standard order. */
#if !CSHARP_CHOICE_MONO
result = execute_csharp_using_mono (assembly_path, libdirs, libdirs_count,
args, nargs, verbose, quiet,
executer, private_data);
if (result >= 0)
return (bool) result;
#endif
#if !CSHARP_CHOICE_DOTNET
result = execute_csharp_using_dotnet (assembly_path, libdirs, libdirs_count,
args, nargs, verbose, quiet,
executer, private_data);
if (result >= 0)
return (bool) result;
#endif
result = execute_csharp_using_sscli (assembly_path, libdirs, libdirs_count,
args, nargs, verbose, quiet,
executer, private_data);
if (result >= 0)
return (bool) result;
if (!quiet)
error (0, 0, _("C# virtual machine not found, try installing mono or dotnet"));
return true;
}