]> git.eshelyaron.com Git - emacs.git/commitdiff
Restore functionality on Android 2.2
authorPo Lu <luangruo@yahoo.com>
Wed, 12 Jun 2024 08:44:21 +0000 (16:44 +0800)
committerEshel Yaron <me@eshelyaron.com>
Wed, 12 Jun 2024 09:29:35 +0000 (11:29 +0200)
* java/Makefile.in (install_temp): Do not compress
directory-tree and generate such files in a special format that
stores file sizes.

* lib-src/asset-directory-tool.c (struct directory_tree): New
field st_size.
(need_file_size): New variable.
(main_1, main_2, main): Write file sizes before sibling offsets
if `--api-8' is specified.

* src/android-asset.h (struct android_asset_manager): New field
open.
(struct android_asset): New field name.
(AAssetManager_fromJava): Load AssetManager#open.
(AAssetManager_open): If a directory tree has already been
loaded, search for a matching asset and load its size thence, to
avoid the requirement of an AssetFileDescriptor.
(AAsset_close): Don't assume asset->fd exists.  Release
asset->name.
(AAsset_getLength): Likewise.
(android_asset_create_stream): If asset->name exists, call
AssetManager#open, in order to open compressed files.

* src/androidvfs.c (OLD_ANDROID_ASSETS): Define to 1 on API 8.
(android_extract_long, android_scan_directory_tree): Mark
arguments as const.  Adjust offsets when OLD_ANDROID_ASSETS.
(android_is_directory, android_init_assets, android_afs_readdir):
Likewise.

* src/lread.c (lread_fstat): Define to sys_fstat, not fstat.

(cherry picked from commit 02e70821b3800a082aec215a9ab8adbfafe9ad76)

java/Makefile.in
lib-src/asset-directory-tool.c
src/android-asset.h
src/androidvfs.c
src/lread.c

index b14fc3c2fffe788ed1c69ba3995b86e944287bf9..3b3788576506fe72245a52937a46c65c2751ef80 100644 (file)
@@ -257,7 +257,8 @@ install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(RESOURCE_FILES)
        { hostname; date +%s; } > install_temp/assets/build_info
 # Produce the file index.
        $(AM_V_SILENT) $(libsrc)/asset-directory-tool           \
-         install_temp/assets install_temp/assets/directory-tree
+         install_temp/assets install_temp/assets/directory-tree\
+         $(if $(ANDROID_SDK_8_OR_EARLIER),--api-8)
 # If the package targets Android 2.2, move compressable and
 # non-compressable assets to separate directories.
        $(AM_V_SILENT)                                          \
@@ -265,6 +266,7 @@ install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(RESOURCE_FILES)
          echo "Moving large and gzipped files to separate directories...">&2;\
          mkdir -p install_temp/assets_raw;                     \
          cd install_temp/assets;                               \
+         mv directory-tree ../assets_raw;                      \
          find . \( -size +1536 -o -size 1536 \)                \
            \( \! -name '*.gz' \) -type f > files.txt;          \
          tar cf ../assets_raw/largefiles.tar -T files.txt;     \
index 31735586193d3f8cb6c3c7f04adeb975c85b4d4b..23f4655448c73f7ccab9abb297ee919e92916302 100644 (file)
@@ -20,6 +20,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <config.h>
 
 #include <stdio.h>
+#include <verify.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <byteswap.h>
@@ -35,17 +36,19 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    application package.
 
    Such a file records the layout of the `assets' directory in the
-   package.  Emacs records this information itself and uses it in the
-   Android emulation of readdir, because the system asset manager APIs
-   are routinely buggy, and are often unable to locate directories or
-   files.
-
-   The file is packed, with no data alignment guarantees made.  The
-   file starts with the bytes "EMACS", following which is the name of
-   the first file or directory, a NULL byte and an unsigned int
-   indicating the offset from the start of the file to the start of
-   the next sibling.  Following that is a list of subdirectories or
-   files in the same format.  The long is stored LSB first.
+   package, and, in packages targeting Android 2.2, the size of each of
+   its members.  Emacs records this information itself and uses it in
+   the Android emulation of readdir, because the system asset manager
+   APIs are often unable to locate directories or files, or provide
+   corresponding metadata.
+
+   The file is packed, with no data alignment guarantees made.  The file
+   starts with the bytes "EMACS", or EMACS____ on Android 2.2, following
+   which is the name of the first file or directory, a NULL byte, an
+   unsigned int holding its size (on Android 2.2), and an unsigned int
+   indicating the offset from the start of the file to the start of the
+   next sibling.  Following that is a list of subdirectories or files in
+   the same format.  The long is stored LSB first.
 
    Directories can be distinguished from ordinary files through the
    last bytes of their file names (immediately previous to their
@@ -62,10 +65,19 @@ struct directory_tree
   /* The name of this directory or file.  */
   char *name;
 
+  /* st_size of this entry.  */
+  off_t st_size;
+
   /* Subdirectories and files inside this directory.  */
   struct directory_tree *children, *next;
 };
 
+/* Whether the size of each entry should be prepended to the start
+   pointer.  */
+static bool need_file_size;
+
+\f
+
 /* Exit with EXIT_FAILURE, after printing a description of a failing
    function WHAT along with the details of the error.  */
 
@@ -138,11 +150,14 @@ main_1 (DIR *dir, struct directory_tree *parent)
          last = &this->next;
          this->name = xmalloc (length + 2);
          strcpy (this->name, dirent->d_name);
+         this->st_size = 0;
 
          /* Now record the offset to the end of this directory.  This
-            is length + 1, for the file name, and 5 more bytes for
-            the trailing NULL and long.  */
-         this->offset = parent->offset + length + 6;
+            is length + 1, for the file name, 5 more bytes for the
+            trailing NULL and long, and 4 further bytes if a file size
+            is required.  */
+         this->offset = (parent->offset
+                         + length + 6 + (need_file_size ? 4 : 0));
 
          /* Terminate that with a slash and trailing NULL byte.  */
          this->name[length] = '/';
@@ -175,11 +190,22 @@ main_1 (DIR *dir, struct directory_tree *parent)
          *last = this;
          last = &this->next;
          this->name = xmalloc (length + 1);
+         this->st_size = statb.st_size;
          strcpy (this->name, dirent->d_name);
 
-         /* This is one byte shorter because there is no trailing
+         if (this->st_size >= 0x1ffffff)
+           {
+             fprintf (stderr,
+                      "asset-directory-tool: file size exceeds maximum"
+                      " representable in a directory-tree: %s\n",
+                      dirent->d_name);
+             exit (EXIT_FAILURE);
+           }
+
+         /* This is one byte the shorter because there is no trailing
             slash.  */
-         this->offset = parent->offset + length + 5;
+         this->offset = (parent->offset + length + 5
+                         + (need_file_size ? 4 : 0));
          parent->offset = this->offset;
        }
     }
@@ -194,7 +220,7 @@ main_2 (int fd, struct directory_tree *tree, size_t *offset)
 {
   ssize_t size;
   struct directory_tree *child;
-  unsigned int output;
+  unsigned int output[2];
 
   /* Write tree->name with the trailing NULL byte.  */
   size = strlen (tree->name) + 1;
@@ -203,13 +229,26 @@ main_2 (int fd, struct directory_tree *tree, size_t *offset)
 
   /* Write the offset.  */
 #ifdef WORDS_BIGENDIAN
-  output = bswap_32 (tree->offset);
-#else
-  output = tree->offset;
-#endif
-  if (write (fd, &output, 4) < 1)
-    croak ("write");
-  size += 4;
+  output[1] = bswap_32 (tree->offset);
+  output[0] = bswap_32 ((unsigned int) tree->st_size);
+#else /* !WORDS_BIGENDIAN */
+  output[1] = tree->offset;
+  output[0] = (unsigned int) tree->st_size;
+#endif /* !WORDS_BIGENDIAN */
+
+  verify (sizeof output == 8 && sizeof output[0] == 4);
+  if (!need_file_size)
+    {
+      if (write (fd, output + 1, 4) < 1)
+       croak ("write");
+      size += 4;
+    }
+  else
+    {
+      if (write (fd, output, 8) < 1)
+       croak ("write");
+      size += 8;
+    }
 
   /* Now update offset.  */
   *offset += size;
@@ -240,13 +279,16 @@ main (int argc, char **argv)
   struct directory_tree tree;
   size_t offset;
 
-  if (argc != 3)
+  if (argc != 3 && argc != 4)
     {
-      fprintf (stderr, "usage: %s directory output-file\n",
-              argv[0]);
+      fprintf (stderr, "usage: %s directory output-file "
+              "[--api-8]\n", argv[0]);
       return EXIT_FAILURE;
     }
 
+  if (argc == 4 && !strcmp (argv[3], "--api-8"))
+    need_file_size = true;
+
   fd = open (argv[2], O_CREAT | O_TRUNC | O_RDWR,
             S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
 
@@ -272,16 +314,23 @@ main (int argc, char **argv)
       return EXIT_FAILURE;
     }
 
+  /* And a further 4 bytes of padding if need_file_size.  */
+  if (need_file_size && write (fd, "____", 4) < 4)
+    {
+      perror ("write");
+      return EXIT_FAILURE;
+    }
+
   /* Now iterate through children of INDIR, building the directory
      tree.  */
-  tree.offset = 5;
+  tree.offset = 5 + (need_file_size ? 4 : 0);
   tree.children = NULL;
 
   main_1 (indir, &tree);
   closedir (indir);
 
   /* Finally, write the directory tree to the output file.  */
-  offset = 5;
+  offset = 5 + (need_file_size ? 4 : 0);
   for (; tree.children; tree.children = tree.children->next)
     main_2 (fd, tree.children, &offset);
 
index a6b5aa3366c75cc391774e9d09649aef65f6d871..273ab1fa7344a3f386c2a73d406cef3878ee353e 100644 (file)
@@ -19,6 +19,17 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <android/log.h>
 
+\f
+
+/* Forward declarations.  */
+
+static const char *directory_tree;
+
+static const char *android_scan_directory_tree (const char *, size_t *);
+static unsigned int android_extract_long (const char *);
+
+\f
+
 /* This file contains an emulation of the Android asset manager API
    used on builds for Android 2.2.  It is included by android.c
    whenever appropriate.
@@ -34,6 +45,7 @@ struct android_asset_manager
   /* Asset manager class and functions.  */
   jclass class;
   jmethodID open_fd;
+  jmethodID open;
 
   /* Asset file descriptor class and functions.  */
   jclass fd_class;
@@ -63,6 +75,9 @@ struct android_asset
   /* The asset file descriptor and input stream.  */
   jobject fd, stream;
 
+  /* Alternatively, the name of the file.  */
+  jstring name;
+
   /* The mode.  */
   int mode;
 };
@@ -98,6 +113,12 @@ AAssetManager_fromJava (JNIEnv *env, jobject java_manager)
     = (*env)->GetMethodID (env, manager->class, "openFd",
                           "(Ljava/lang/String;)"
                           "Landroid/content/res/AssetFileDescriptor;");
+  assert (manager->open_fd);
+
+  manager->open
+    = (*env)->GetMethodID (env, manager->class, "open",
+                          "(Ljava/lang/String;)"
+                          "Ljava/io/InputStream;");
   assert (manager->open);
 
   manager->fd_class
@@ -168,6 +189,8 @@ AAssetManager_open (AAssetManager *manager, const char *c_name,
   jobject desc;
   jstring name;
   AAsset *asset;
+  const char *asset_dir;
+  jlong st_size = -1;
 
   /* Push a local frame.  */
   asset = NULL;
@@ -177,53 +200,86 @@ AAssetManager_open (AAssetManager *manager, const char *c_name,
   if ((*(manager->env))->ExceptionCheck (manager->env))
     goto fail;
 
-  /* Encoding issues can be ignored for now as there are only ASCII
-     file names in Emacs.  */
+  /* If the directory tree has been initialized, it is possible to avoid
+     opening an AssetFileDescriptor and thereby access compressed
+     assets, without sacrificing the possibility of reading the file
+     size.  */
+  if (directory_tree)
+    {
+      /* Search for a matching asset.  */
+      asset_dir = android_scan_directory_tree (c_name, NULL);
+      if (!asset_dir)
+       goto fail;
+
+      /* Extract the size of the asset from this directory.  */
+      st_size = android_extract_long (asset_dir - 8);
+    }
+
+  /* Encoding issues can be ignored for the time being as there are only
+     ASCII file names in Emacs.  */
   name = (*(manager->env))->NewStringUTF (manager->env, c_name);
 
   if (!name)
     goto fail;
 
-  /* Now try to open an ``AssetFileDescriptor''.  */
-  desc = (*(manager->env))->CallObjectMethod (manager->env,
-                                             manager->asset_manager,
-                                             manager->open_fd,
-                                             name);
+  /* If st_size has been set, it ought to be possible to open an input
+     stream directly upon the first attempt to read from the asset,
+     sidestepping the intermediate AssetFileDescriptor.  */
 
-  if (!desc)
-    goto fail;
+  desc = NULL;
+
+  if (st_size < 0)
+    /* Otherwise attempt to open an ``AssetFileDescriptor''.  */
+    desc = (*(manager->env))->CallObjectMethod (manager->env,
+                                               manager->asset_manager,
+                                               manager->open_fd,
+                                               name);
 
   /* Allocate the asset.  */
   asset = calloc (1, sizeof *asset);
 
   if (!asset)
+    goto fail;
+
+  if (desc)
     {
-      (*(manager->env))->CallVoidMethod (manager->env,
-                                        desc,
-                                        manager->close);
-      goto fail;
-    }
+      /* Pop the local frame and return desc.  */
+      desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
 
-  /* Pop the local frame and return desc.  */
-  desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
+      if (!desc)
+       goto fail;
 
-  if (!desc)
-    goto fail;
+      /* Will be released by PopLocalFrame.  */
+      name = NULL;
+    }
+  else /* if (name) */
+    {
+      /* Pop the local frame and return name.  */
+      name = (*(manager->env))->NewGlobalRef (manager->env, name);
+
+      if (!name)
+       goto fail;
+    }
 
   (*(manager->env))->PopLocalFrame (manager->env, NULL);
 
   asset->manager = manager;
-  asset->length = -1;
+  asset->length = st_size;
   asset->fd = desc;
+  asset->name = name;
   asset->mode = mode;
 
   return asset;
 
  fail:
+  if (desc)
+    (*(manager->env))->CallVoidMethod (manager->env,
+                                      desc,
+                                      manager->close);
+
   (*(manager->env))->ExceptionClear (manager->env);
   (*(manager->env))->PopLocalFrame (manager->env, NULL);
   free (asset);
-
   return NULL;
 }
 
@@ -234,11 +290,14 @@ AAsset_close (AAsset *asset)
 
   env = asset->manager->env;
 
-  (*env)->CallVoidMethod (asset->manager->env,
-                         asset->fd,
-                         asset->manager->close);
-  (*env)->DeleteGlobalRef (asset->manager->env,
-                          asset->fd);
+  if (asset->fd)
+    {
+      (*env)->CallVoidMethod (asset->manager->env,
+                             asset->fd,
+                             asset->manager->close);
+      (*env)->DeleteGlobalRef (asset->manager->env,
+                              asset->fd);
+    }
 
   if (asset->stream)
     {
@@ -249,6 +308,10 @@ AAsset_close (AAsset *asset)
                               asset->stream);
     }
 
+  if (asset->name)
+    (*env)->DeleteGlobalRef (asset->manager->env,
+                            asset->name);
+
   free (asset);
 }
 
@@ -264,10 +327,17 @@ android_asset_create_stream (AAsset *asset)
   jobject stream;
   JNIEnv *env;
 
+  assert (asset->fd || asset->name);
+
   env = asset->manager->env;
-  stream
-    = (*env)->CallObjectMethod (env, asset->fd,
-                               asset->manager->create_input_stream);
+
+  if (asset->name)
+    stream = (*env)->CallObjectMethod (env, asset->manager->asset_manager,
+                                      asset->manager->open, asset->name);
+  else
+    stream
+      = (*env)->CallObjectMethod (env, asset->fd,
+                                 asset->manager->create_input_stream);
 
   if (!stream)
     {
@@ -380,6 +450,8 @@ AAsset_getLength (AAsset *asset)
 
   if (asset->length != -1)
     return asset->length;
+  if (!asset->fd)
+    return 0;
 
   env = asset->manager->env;
   asset->length
index c0bd86e54b8d1207ae34f1f9db6fb866eb58d857..d28a74918f6fb68ddb84b22b19c147f9abf2f6a0 100644 (file)
@@ -46,8 +46,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #if __ANDROID_API__ >= 9
 #include <android/asset_manager.h>
 #include <android/asset_manager_jni.h>
+#define OLD_ANDROID_ASSETS 0
 #else /* __ANDROID_API__ < 9 */
 #include "android-asset.h"
+#define OLD_ANDROID_ASSETS 1
 #endif /* __ANDROID_API__ >= 9 */
 
 #include <android/log.h>
@@ -1001,7 +1003,7 @@ static AAssetManager *asset_manager;
 /* Read an unaligned (32-bit) long from the address POINTER.  */
 
 static unsigned int
-android_extract_long (char *pointer)
+android_extract_long (const char *pointer)
 {
   unsigned int number;
 
@@ -1022,16 +1024,20 @@ android_extract_long (char *pointer)
    directory.  */
 
 static const char *
-android_scan_directory_tree (char *file, size_t *limit_return)
+android_scan_directory_tree (const char *file, size_t *limit_return)
 {
   char *token, *saveptr, *copy, *start, *max, *limit;
   size_t token_length, ntokens, i, len;
-  char *tokens[10];
+  char *tokens[20];
 
   USE_SAFE_ALLOCA;
 
-  /* Skip past the 5 byte header.  */
+  /* Skip past the 5 or 9 byte header.  */
+#if !OLD_ANDROID_ASSETS
   start = (char *) directory_tree + 5;
+#else /* OLD_ANDROID_ASSETS */
+  start = (char *) directory_tree + 9;
+#endif /* OLD_ANDROID_ASSETS */
 
   /* Figure out the current limit.  */
   limit = (char *) directory_tree + directory_tree_size;
@@ -1098,9 +1104,9 @@ android_scan_directory_tree (char *file, size_t *limit_return)
        {
          /* They probably match.  Find the NULL byte.  It must be
             either one byte past start + token_length, with the last
-            byte a trailing slash (indicating that it is a
-            directory), or just start + token_length.  Return 4 bytes
-            past the next NULL byte.  */
+            byte a trailing slash (indicating that it is a directory),
+            or just start + token_length.  Return 4 or 8 bytes past the
+            next NULL byte.  */
 
          max = memchr (start, 0, limit - start);
 
@@ -1113,13 +1119,14 @@ android_scan_directory_tree (char *file, size_t *limit_return)
             last token.  Otherwise, set it as start and the limit as
             start + the offset and continue the loop.  */
 
-         if (max && max + 5 <= limit)
+         if (max && max + (OLD_ANDROID_ASSETS ? 9 : 5) <= limit)
            {
              if (i < ntokens - 1)
                {
-                 start = max + 5;
+                 start = max + (OLD_ANDROID_ASSETS ? 9 : 5);
                  limit = ((char *) directory_tree
-                          + android_extract_long (max + 1));
+                          + android_extract_long (max + (OLD_ANDROID_ASSETS
+                                                         ? 5 : 1)));
 
                  /* Make sure limit is still in range.  */
                  if (limit > directory_tree + directory_tree_size
@@ -1137,10 +1144,12 @@ android_scan_directory_tree (char *file, size_t *limit_return)
                {
                  /* Figure out the limit.  */
                  if (limit_return)
-                   *limit_return = android_extract_long (max + 1);
+                   *limit_return
+                     = android_extract_long (max + (OLD_ANDROID_ASSETS
+                                                    ? 5 : 1));
 
                  /* Go to the end of this file.  */
-                 max += 5;
+                 max += (OLD_ANDROID_ASSETS ? 9 : 5);
                }
 
              SAFE_FREE ();
@@ -1161,11 +1170,12 @@ android_scan_directory_tree (char *file, size_t *limit_return)
 
       start = memchr (start, 0, limit - start);
 
-      if (!start || start + 5 > limit)
+      if (!start || start + (OLD_ANDROID_ASSETS ? 9 : 5) > limit)
        goto fail;
 
       start = ((char *) directory_tree
-              + android_extract_long (start + 1));
+              + android_extract_long (start
+                                      + (OLD_ANDROID_ASSETS ? 5 : 1)));
 
       /* Make sure start is still in bounds.  */
 
@@ -1192,13 +1202,20 @@ android_is_directory (const char *dir)
 {
   /* If the directory is the directory tree, then it is a
      directory.  */
-  if (dir == directory_tree + 5)
+  if (dir == directory_tree + (OLD_ANDROID_ASSETS ? 9 : 5))
     return true;
 
+#if !OLD_ANDROID_ASSETS
   /* Otherwise, look 5 bytes behind.  If it is `/', then it is a
      directory.  */
   return (dir - 6 >= directory_tree
          && *(dir - 6) == '/');
+#else /* OLD_ANDROID_ASSETS */
+  /* Otherwise, look 9 bytes behind.  If it is `/', then it is a
+     directory.  */
+  return (dir - 10 >= directory_tree
+         && *(dir - 10) == '/');
+#endif /* OLD_ANDROID_ASSETS */
 }
 
 /* Initialize asset retrieval.  ENV should be a JNI environment for
@@ -1232,6 +1249,7 @@ android_init_assets (JNIEnv *env, jobject manager)
   /* Now figure out how big the directory tree is, and compare the
      first few bytes.  */
   directory_tree_size = AAsset_getLength (asset);
+#if !OLD_ANDROID_ASSETS
   if (directory_tree_size < 5
       || memcmp (directory_tree, "EMACS", 5))
     {
@@ -1239,6 +1257,15 @@ android_init_assets (JNIEnv *env, jobject manager)
                           "Directory tree has bad magic");
       emacs_abort ();
     }
+#else /* OLD_ANDROID_ASSETS */
+  if (directory_tree_size < 9
+      || memcmp (directory_tree, "EMACS____", 9))
+    {
+      __android_log_print (ANDROID_LOG_FATAL, __func__,
+                          "Directory tree has bad magic");
+      emacs_abort ();
+    }
+#endif /* OLD_ANDROID_ASSETS */
 
   /* Hold a VM reference to the asset manager to prevent the native
      object from being deleted.  */
@@ -2287,8 +2314,13 @@ android_afs_readdir (struct android_vdir *vdir)
     dirent.d_type = DT_REG;
 
   /* Forward dir->asset_dir to the file past last.  */
+#if !OLD_ANDROID_ASSETS
   dir->asset_dir = ((char *) directory_tree
                    + android_extract_long ((char *) last));
+#else /* OLD_ANDROID_ASSETS */
+  dir->asset_dir = ((char *) directory_tree
+                   + android_extract_long ((char *) last + 4));
+#endif /* OLD_ANDROID_ASSETS */
 
   return &dirent;
 }
index f5c79a8c0ea1c60e0a6b2a6637bfb00975c7b76e..1bc5b0c993db70bb4b38bc881dd921f402c9f2ff 100644 (file)
@@ -69,7 +69,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #define lread_fd_cmp(n) (fd == (n))
 #define lread_fd_p     (fd >= 0)
 #define lread_close    emacs_close
-#define lread_fstat    fstat
+#define lread_fstat    sys_fstat
 #define lread_read_quit        emacs_read_quit
 #define lread_lseek    lseek