Do not set LD_LIBRARY_PATH during Android initialization

* doc/emacs/android.texi (Android Environment): Adjust
documentation to match.

* java/org/gnu/emacs/EmacsNoninteractive.java (main1): New
function.  Remove initialization of EmacsNative hither.
(main): Acquire an ApplicationInfo or LoadedApk, as the case may
be on the host system, derive a ClassLoader from the result, and
load and call `main1' from within this class loader.

* src/android-emacs.c (main):

* src/android.c (setEmacsParams): Do not override
LD_LIBRARY_PATH or set EMACS_LD_LIBRARY_PATH.  This enables
Emacs to execute subprocesses in certain "fortified" Android
systems, amongst other things.
This commit is contained in:
Po Lu 2024-07-14 12:46:23 +08:00
parent 04bf3172f0
commit b00fc31dd1
4 changed files with 147 additions and 147 deletions

View file

@ -378,20 +378,18 @@ definition documents, so your mileage may vary.
@cindex EMACS_CLASS_PATH environment variable, Android
Even when the location of the @command{libandroid-emacs.so} command is
known in advance, special configuration is required to run Emacs from
known in advance, special preparation is required to run Emacs from
elsewhere than a subprocess of an existing Emacs session, as it must be
made to understand the location of resources and shared libraries in or
extracted from the installed application package. The OS command
@command{pm path org.gnu.emacs} will print the location of the
application package, and the adjacent @file{lib} directory will hold
shared libraries extracted from the same, though the said command must
be invoked in a peculiar manner to satisfy system restrictions on
communication between pseudoterminal devices created by user
applications and system services such as the package manager, which is
to say, with the standard IO streams redirected to a real file or a
pipe. Such values, once established, must be specified in the
environment variables @code{EMACS_CLASS_PATH} and
@code{EMACS_LD_LIBRARY_PATH}, so that this sample shell script may be
application package, though the said command must be invoked in a
peculiar manner to satisfy system restrictions on communication between
pseudoterminal devices created by user applications and system services
such as the package manager, which is to say, with the standard IO
streams redirected to a real file or a pipe. This value, once
established, must be specified in the environment variables
@code{EMACS_CLASS_PATH}, so that this sample shell script may be
installed as @code{emacs} in any location that is accessible:
@example
@ -400,7 +398,6 @@ installed as @code{emacs} in any location that is accessible:
package_name=`pm path org.gnu.emacs 2>/dev/null </dev/null \
| sed 's/^package://'`
emacs=
ld_path=
EMACS_CLASS_PATH=$package_name
for libdir in `dirname $package_name`/lib/*; do
@ -409,10 +406,7 @@ for libdir in `dirname $package_name`/lib/*; do
&& emacs="$libdir"/libandroid-emacs.so
done
EMACS_LD_LIBRARY_PATH=$ld_path
export EMACS_CLASS_PATH
export EMACS_LD_LIBRARY_PATH
test -x "$emacs" || exit 1
exec $emacs "$@@"
@end example

View file

@ -30,37 +30,69 @@
/* Noninteractive Emacs.
This is the class that libandroid-emacs.so starts.
libandroid-emacs.so figures out the system classpath, then starts
dalvikvm with the framework jars.
At that point, dalvikvm calls main, which sets up the main looper,
creates an ActivityThread and attaches it to the main thread.
Then, it obtains an application context for the LoadedApk in the
application thread.
Finally, it obtains the necessary context specific objects and
initializes Emacs. */
When started, libandroid-emacs.so invokes `app_process(64)' with a
command line placing Emacs's classes.dex file in the JVM class path,
which in turn transfers control to `main'. `main' creates a context,
which may be likened to a connection to the system server, and a
class loader derived from Emacs's application package, which it loads
beforehand. From this class loader, it loads another instance of
itself, and invokes `main1', to ensure the execution of
`EmacsNative''s static initializer within the application class
loader, where a proper library search path is in effect. */
@SuppressWarnings ("unchecked")
public final class EmacsNoninteractive
{
/* Prepare Emacs for startup and call `initEmacs'. This function is
called in an instance of `EmacsNoninteractive' loaded by the APK
ClassLoader acquired in `main', which guarantees that shared
libraries in the APK will be considered in resolving shared
libraries for `EmacsNative'. */
public static void
main1 (String[] args, Context context)
throws Exception
{
AssetManager assets;
String filesDir, libDir, cacheDir;
/* Don't actually start the looper or anything. Instead, obtain
an AssetManager. */
assets = context.getAssets ();
/* Now configure Emacs. The class path should already be set. */
filesDir = context.getFilesDir ().getCanonicalPath ();
libDir = EmacsService.getLibraryDirectory (context);
cacheDir = context.getCacheDir ().getCanonicalPath ();
EmacsNative.setEmacsParams (assets, filesDir,
libDir, cacheDir, 0.0f,
0.0f, 0.0f, null, null,
Build.VERSION.SDK_INT);
/* Now find the dump file that Emacs should use, if it has already
been dumped. */
EmacsApplication.findDumpFile (context);
/* Start Emacs. */
EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
}
public static void
main (String[] args)
{
Object activityThread, loadedApk;
Class activityThreadClass, loadedApkClass, contextImplClass;
Class compatibilityInfoClass;
Class compatibilityInfoClass, emacsNoninteractiveClass;
Method method;
Context context;
AssetManager assets;
String filesDir, libDir, cacheDir;
ClassLoader classLoader;
Looper.prepare ();
context = null;
assets = null;
filesDir = libDir = cacheDir = null;
loadedApkClass = null;
classLoader = null;
try
{
@ -72,7 +104,6 @@ public final class EmacsNoninteractive
/* Create and attach the activity thread. */
activityThread = method.invoke (null);
context = null;
/* Now get an LoadedApk. */
@ -82,99 +113,88 @@ public final class EmacsNoninteractive
}
catch (ClassNotFoundException exception)
{
/* Android 2.2 has no LoadedApk class, but fortunately it
does not need to be used, since contexts can be
directly created. */
/* Android 2.2 has no LoadedApk class; the several following
statements will load a context and an
ActivityThread.PackageInfo, as is appropriate on this
system. */
}
loadedApkClass = null;
contextImplClass = Class.forName ("android.app.ContextImpl");
/* Get a LoadedApk or ActivityThread.PackageInfo. How to do
this varies by Android version. On Android 2.3.3 and
earlier, there is no ``compatibilityInfo'' argument to
getPackageInfo. */
method = activityThreadClass.getDeclaredMethod ("getSystemContext");
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
{
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
(Context.CONTEXT_INCLUDE_CODE
| Context.CONTEXT_IGNORE_SECURITY));
}
else
{
compatibilityInfoClass
= Class.forName ("android.content.res.CompatibilityInfo");
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
compatibilityInfoClass,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
null, (Context.CONTEXT_INCLUDE_CODE
| Context.CONTEXT_IGNORE_SECURITY));
}
if (loadedApk == null)
throw new RuntimeException ("getPackageInfo returned NULL");
/* If loadedApkClass remains NULL, substitute the class of
the object returned by getPackageInfo. */
if (loadedApkClass == null)
loadedApkClass = loadedApk.getClass ();
/* Now, get a context. */
contextImplClass = Class.forName ("android.app.ContextImpl");
try
{
method
= contextImplClass.getDeclaredMethod ("createAppContext",
activityThreadClass,
loadedApkClass);
method.setAccessible (true);
context = (Context) method.invoke (null, activityThread,
loadedApk);
}
catch (NoSuchMethodException exception)
{
/* Older Android versions don't have createAppContext, but
instead require creating a ContextImpl, and then
calling createPackageContext. */
method
= activityThreadClass.getDeclaredMethod ("getSystemContext");
context = (Context) method.invoke (activityThread);
method = contextImplClass.getDeclaredMethod ("createPackageContext",
String.class,
int.class);
method
= contextImplClass.getDeclaredMethod ("createPackageContext",
String.class,
int.class);
method.setAccessible (true);
context = (Context) method.invoke (context, "org.gnu.emacs",
0);
}
/* If the context has not already been created, then do what
is appropriate for newer versions of Android. */
/* Retrieve the LoadedApk's class loader and execute the
remaining portion of the start-up process within its version
of EmacsNoninteractive, which will indicate to the system
that it must load shared libraries from the APK's library
search path. */
if (context == null)
{
/* Get a LoadedApk. How to do this varies by Android version.
On Android 2.3.3 and earlier, there is no
``compatibilityInfo'' argument to getPackageInfo. */
if (Build.VERSION.SDK_INT
<= Build.VERSION_CODES.GINGERBREAD_MR1)
{
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
0);
}
else
{
compatibilityInfoClass
= Class.forName ("android.content.res.CompatibilityInfo");
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
compatibilityInfoClass,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
null, 0);
}
if (loadedApk == null)
throw new RuntimeException ("getPackageInfo returned NULL");
/* Now, get a context. */
contextImplClass = Class.forName ("android.app.ContextImpl");
try
{
method
= contextImplClass.getDeclaredMethod ("createAppContext",
activityThreadClass,
loadedApkClass);
method.setAccessible (true);
context = (Context) method.invoke (null, activityThread,
loadedApk);
}
catch (NoSuchMethodException exception)
{
/* Older Android versions don't have createAppContext, but
instead require creating a ContextImpl, and then
calling createPackageContext. */
method
= activityThreadClass.getDeclaredMethod ("getSystemContext");
context = (Context) method.invoke (activityThread);
method
= contextImplClass.getDeclaredMethod ("createPackageContext",
String.class,
int.class);
method.setAccessible (true);
context = (Context) method.invoke (context, "org.gnu.emacs",
0);
}
}
/* Don't actually start the looper or anything. Instead, obtain
an AssetManager. */
assets = context.getAssets ();
/* Now configure Emacs. The class path should already be set. */
filesDir = context.getFilesDir ().getCanonicalPath ();
libDir = EmacsService.getLibraryDirectory (context);
cacheDir = context.getCacheDir ().getCanonicalPath ();
method = loadedApkClass.getDeclaredMethod ("getClassLoader");
classLoader = (ClassLoader) method.invoke (loadedApk);
}
catch (Exception e)
{
@ -188,16 +208,20 @@ public final class EmacsNoninteractive
System.exit (1);
}
EmacsNative.setEmacsParams (assets, filesDir,
libDir, cacheDir, 0.0f,
0.0f, 0.0f, null, null,
Build.VERSION.SDK_INT);
/* Now find the dump file that Emacs should use, if it has already
been dumped. */
EmacsApplication.findDumpFile (context);
/* Start Emacs. */
EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
try
{
emacsNoninteractiveClass
= classLoader.loadClass ("org.gnu.emacs.EmacsNoninteractive");
method = emacsNoninteractiveClass.getMethod ("main1", String[].class,
Context.class);
method.setAccessible (true);
method.invoke (null, args, context);
}
catch (Exception e)
{
System.err.println ("Internal error during startup: " + e);
e.printStackTrace ();
System.exit (1);
}
}
};

View file

@ -37,7 +37,7 @@ main (int argc, char **argv)
{
char **args;
int i;
char *bootclasspath, *emacs_class_path, *ld_library_path;
char *bootclasspath, *emacs_class_path;
/* Allocate enough to hold the arguments to app_process. */
args = alloca ((10 + argc) * sizeof *args);
@ -63,15 +63,6 @@ main (int argc, char **argv)
return 1;
}
/* Restore LD_LIBRARY_PATH to its original value, the app library
directory, to guarantee that it is possible for Java to find the
Emacs C code later. */
ld_library_path = getenv ("EMACS_LD_LIBRARY_PATH");
if (ld_library_path)
setenv ("LD_LIBRARY_PATH", ld_library_path, 1);
if (asprintf (&bootclasspath, "-Djava.class.path=%s",
emacs_class_path) < 0)
{

View file

@ -1338,7 +1338,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
int pipefd[2];
pthread_t thread;
const char *java_string;
const char *java_string, *tem;
struct stat statb;
#ifdef THREADS_ENABLED
@ -1491,15 +1491,6 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
EmacsNoninteractive can be found. */
setenv ("EMACS_CLASS_PATH", android_class_path, 1);
/* Set LD_LIBRARY_PATH to an appropriate value. */
setenv ("LD_LIBRARY_PATH", android_lib_dir, 1);
/* EMACS_LD_LIBRARY_PATH records the location of the app library
directory. android-emacs refers to this, since users have valid
reasons for changing LD_LIBRARY_PATH to a value that precludes
the possibility of Java locating libemacs later. */
setenv ("EMACS_LD_LIBRARY_PATH", android_lib_dir, 1);
/* If the system is Android 5.0 or later, set LANG to en_US.utf8,
which is understood by the C library. In other instances set it
to C, a meaningless value, for good measure. */