Restore functionality on Android 2.2

* 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.
This commit is contained in:
Po Lu 2024-06-12 16:44:21 +08:00
parent f543ec18f4
commit 02e70821b3
5 changed files with 230 additions and 75 deletions

View file

@ -258,7 +258,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) \
@ -266,6 +267,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; \

View 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.
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", 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.
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;
/* 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);

View file

@ -19,6 +19,17 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <android/log.h>
/* 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 *);
/* 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)
{
(*(manager->env))->CallVoidMethod (manager->env,
desc,
manager->close);
goto fail;
}
/* Pop the local frame and return desc. */
desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
if (!desc)
goto fail;
if (desc)
{
/* Pop the local frame and return desc. */
desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
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

View 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;
}

View 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