7864 lines
203 KiB
C
7864 lines
203 KiB
C
/* Android virtual file-system support for GNU Emacs.
|
||
|
||
Copyright (C) 2023-2024 Free Software Foundation, Inc.
|
||
|
||
This file is part of GNU Emacs.
|
||
|
||
GNU Emacs 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.
|
||
|
||
GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
#include <config.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <assert.h>
|
||
#include <dlfcn.h>
|
||
#include <dirent.h>
|
||
#include <errno.h>
|
||
#include <minmax.h>
|
||
#include <string.h>
|
||
#include <systime.h>
|
||
#include <semaphore.h>
|
||
|
||
#include <sys/stat.h>
|
||
#include <sys/mman.h>
|
||
|
||
#include <stat-time.h>
|
||
#include <md5.h>
|
||
|
||
#include <linux/ashmem.h>
|
||
|
||
#include "android.h"
|
||
#include "androidterm.h"
|
||
#include "systime.h"
|
||
#include "blockinput.h"
|
||
#include "coding.h"
|
||
|
||
#if __ANDROID_API__ >= 9
|
||
#include <android/asset_manager.h>
|
||
#include <android/asset_manager_jni.h>
|
||
#else /* __ANDROID_API__ < 9 */
|
||
#include "android-asset.h"
|
||
#endif /* __ANDROID_API__ >= 9 */
|
||
|
||
#include <android/log.h>
|
||
|
||
/* This file implements support for the various special-purpose
|
||
directories found on Android systems through a series of functions
|
||
that substitute for Unix system call wrappers. Such directories
|
||
are not mounted in the Unix virtual file-system, but instead
|
||
require the use of special system APIs to access; Emacs pretends
|
||
they are mounted at specific folders within the root directory.
|
||
|
||
There are presently two directories: /assets, granting access to
|
||
asset files stored within the APK, and /content, providing direct
|
||
access to content URIs (in Android 4.4 and later) and content
|
||
directory trees (in Android 5.0 and later.)
|
||
|
||
Substitutes for the C library `open', `fstat', `close', `fclose',
|
||
`unlink', `symlink', `rmdir', `rename', `stat' system call wrappers
|
||
are implemented, which delegate their actions to function tables
|
||
contained inside ``VFS nodes''.
|
||
|
||
The functions of a VFS node are to provide the implementations of
|
||
the Unix file system operations that can be carried out on the file
|
||
designated by its name and to connect useful information (such as
|
||
internal file handles or identifiers) with those file names. To
|
||
those ends, there exist several different types of vnodes, each
|
||
with a different set of functions and supplementary attributes.
|
||
|
||
The key to locating the correct vnode for any given file name is an
|
||
additional file system operation, defined by each node, which
|
||
``names'' children. This operation takes a relative file name and
|
||
returns a second node designating a constituent sub-file.
|
||
|
||
When a file system function is called, it invokes the `name'
|
||
operation of a special root vnode conceptually located at the top
|
||
of the Unix file system hierarchy, handing it the complete file
|
||
name given to it. This vnode's name operation examines the first
|
||
component of the relative file name it receives and creates either
|
||
an asset, content, or Unix vnode, and calls the new vnode's `name'
|
||
operation with the remainder of the file name.
|
||
|
||
The vnode(s) created by each `name' operation may in turn create
|
||
different vnodes based on the components of the names they have
|
||
been provided that are used to repeat this process until no
|
||
components remain. The vnode created for the last component of the
|
||
file name will provide its file system operations or be passed as
|
||
an argument to other file system operations to which the file has
|
||
been passed as an argument.
|
||
|
||
The substitute functions defined have two caveats, which however
|
||
don't prove problematic in an Emacs context: the first is that the
|
||
treatment of `..' is inconsistent with Unix, and has not really
|
||
been tested, while the second is that errno values do not always
|
||
conform to what the corresponding Unix system calls may return.
|
||
These caveats are described in more detail inside the last few
|
||
pages of this file. */
|
||
|
||
/* Structure describing an array of VFS operations. */
|
||
|
||
struct android_vnode;
|
||
|
||
struct android_vdir
|
||
{
|
||
/* Return a `struct dirent' describing the next file in this
|
||
directory stream, or NULL if the stream has reached its end. */
|
||
struct dirent *(*readdir) (struct android_vdir *);
|
||
|
||
/* Close and release all resources allocated for this directory
|
||
stream. */
|
||
void (*closedir) (struct android_vdir *);
|
||
|
||
/* Return a ``file descriptor'' tied to this directory stream. */
|
||
int (*dirfd) (struct android_vdir *);
|
||
};
|
||
|
||
struct android_vops
|
||
{
|
||
/* Name a child of the given VFS node, which should be a
|
||
directory.
|
||
|
||
LENGTH should be the length of NAME, excluding that of any
|
||
trailing NULL byte.
|
||
|
||
NAME should be a normalized and NULL-terminated relative file
|
||
name; it may contain a leading separator characters, but no
|
||
consecutive ones.
|
||
|
||
If NAME is empty, create another VFS node designating the same
|
||
file instead.
|
||
|
||
NAME should also be located within writable storage; it may be
|
||
overwritten as the vnode sees fit.
|
||
|
||
Value is a VFS node corresponding to the child, or NULL upon
|
||
failure.
|
||
|
||
A VFS node may be returned even if NAME does not exist, the
|
||
expectation being that either a later filesystem operation will
|
||
fail, or will create the file. */
|
||
struct android_vnode *(*name) (struct android_vnode *, char *, size_t);
|
||
|
||
/* Open the specified VNODE, returning either a file descriptor or
|
||
an asset file descriptor.
|
||
|
||
FLAGS and MODE mean the same as they do to the Unix `open' system
|
||
call.
|
||
|
||
ASSET_P stipulates if an asset file descriptor may be returned;
|
||
if true, *ASSET may be set to an asset file descriptor.
|
||
|
||
If an asset file descriptor is unavailable or ASSET_P is false,
|
||
*FD will be set to a file descriptor.
|
||
|
||
If the vnode cannot be opened, value is -1 with errno set
|
||
accordingly. Otherwise, value is 0 if a file descriptor was
|
||
returned, and 1 if an asset file descriptor was returned. */
|
||
int (*open) (struct android_vnode *, int, mode_t, bool,
|
||
int *, AAsset **);
|
||
|
||
/* Close the specified VNODE, releasing all of its resources.
|
||
Save errno before making system calls that may set it, and
|
||
restore it to its original value before returning.
|
||
|
||
This is unrelated to `android_close', which primarily releases on
|
||
stat buffers linked to file or asset file descriptors. */
|
||
void (*close) (struct android_vnode *);
|
||
|
||
/* Unlink the file and the specified VNODE. Value and errno are the
|
||
same as Unix `unlink'. */
|
||
int (*unlink) (struct android_vnode *);
|
||
|
||
/* Create a symlink from the specified VNODE to the target TARGET.
|
||
Value and errno are the same as `symlink' on Linux (which notably
|
||
means that errno is set to EPERM if VNODE doesn't support
|
||
symlinks.) */
|
||
int (*symlink) (const char *, struct android_vnode *);
|
||
|
||
/* Remove VNODE from its parent directory. VNODE must be an empty
|
||
directory. Value and errno are the same as Unix `rmdir'. */
|
||
int (*rmdir) (struct android_vnode *);
|
||
|
||
/* Move the file designated by SRC to DST, overwriting DST if
|
||
KEEP_EXISTING is false.
|
||
|
||
If KEEP_EXISTING is true and DST already exists, value is -1 with
|
||
errno set to EEXIST.
|
||
|
||
If VNODE does not natively support checking for a preexisting DST
|
||
and KEEP_EXISTING is true, value is -1 with errno set to ENOSYS.
|
||
|
||
Value is otherwise the same as `rename'. */
|
||
int (*rename) (struct android_vnode *, struct android_vnode *, bool);
|
||
|
||
/* Return statistics for the specified VNODE.
|
||
Value and errno are the same as with Unix `stat'. */
|
||
int (*stat) (struct android_vnode *, struct stat *);
|
||
|
||
/* Return whether or not VNODE is accessible.
|
||
Value, errno and MODE are the same as with Unix `access'. */
|
||
int (*access) (struct android_vnode *, int);
|
||
|
||
/* Make a directory designated by VNODE, like Unix `mkdir'. */
|
||
int (*mkdir) (struct android_vnode *, mode_t);
|
||
|
||
/* Change the access mode of the provided VNODE to MODE. Value is
|
||
the same as with `chmod'. FLAGS is passed verbatim from the call
|
||
to the delegating at-func, and is probably
|
||
AT_SYMLINK_NOFOLLOW. */
|
||
int (*chmod) (struct android_vnode *, mode_t, int);
|
||
|
||
/* Return the target of VNODE if it is a symbolic link, or -1.
|
||
Value and errno are the same as with `readlink'. */
|
||
ssize_t (*readlink) (struct android_vnode *, char *, size_t);
|
||
|
||
/* Open the specified VNODE as a directory.
|
||
Value is a ``directory handle'', or NULL upon failure. */
|
||
struct android_vdir *(*opendir) (struct android_vnode *);
|
||
};
|
||
|
||
struct android_vnode
|
||
{
|
||
/* Operations associated with this vnode. */
|
||
struct android_vops *ops;
|
||
|
||
/* Type of this vnode and its flags. */
|
||
short type, flags;
|
||
};
|
||
|
||
/* Structure describing a special named vnode relative to the root
|
||
vnode, or another directory vnode. */
|
||
|
||
struct android_special_vnode
|
||
{
|
||
/* The name of the special file. */
|
||
const char *name;
|
||
|
||
/* The length of that name. */
|
||
size_t length;
|
||
|
||
/* Function called to create the initial vnode from the rest of the
|
||
component. */
|
||
struct android_vnode *(*initial) (char *, size_t);
|
||
|
||
/* If non-nil, an encoding system into which file name buffers are to
|
||
be re-encoded before being handed to VFS functions. */
|
||
Lisp_Object special_coding_system;
|
||
};
|
||
|
||
verify (NIL_IS_ZERO); /* special_coding_system above. */
|
||
|
||
enum android_vnode_type
|
||
{
|
||
ANDROID_VNODE_UNIX,
|
||
ANDROID_VNODE_AFS,
|
||
ANDROID_VNODE_CONTENT,
|
||
ANDROID_VNODE_CONTENT_AUTHORITY,
|
||
ANDROID_VNODE_CONTENT_AUTHORITY_NAMED,
|
||
ANDROID_VNODE_SAF_ROOT,
|
||
ANDROID_VNODE_SAF_TREE,
|
||
ANDROID_VNODE_SAF_FILE,
|
||
ANDROID_VNODE_SAF_NEW,
|
||
};
|
||
|
||
|
||
|
||
/* Structure describing the android.database.Cursor class. */
|
||
|
||
struct android_cursor_class
|
||
{
|
||
jclass class;
|
||
jmethodID close;
|
||
};
|
||
|
||
/* Structure describing the EmacsDirectoryEntry class. */
|
||
|
||
struct emacs_directory_entry_class
|
||
{
|
||
jclass class;
|
||
jfieldID d_type;
|
||
jfieldID d_name;
|
||
};
|
||
|
||
/* The java.lang.String class. */
|
||
jclass java_string_class;
|
||
|
||
/* Fields and methods associated with the Cursor class. */
|
||
static struct android_cursor_class cursor_class;
|
||
|
||
/* Fields and methods associated with the EmacsDirectoryEntry
|
||
class. */
|
||
static struct emacs_directory_entry_class entry_class;
|
||
|
||
/* Fields and methods associated with the ParcelFileDescriptor
|
||
class. */
|
||
struct android_parcel_file_descriptor_class fd_class;
|
||
|
||
/* Global references to several exception classes. */
|
||
static jclass file_not_found_exception, security_exception;
|
||
static jclass operation_canceled_exception;
|
||
static jclass unsupported_operation_exception, out_of_memory_error;
|
||
|
||
/* Initialize `cursor_class' using the given JNI environment ENV.
|
||
Calling this function is not necessary on Android 4.4 and
|
||
earlier. */
|
||
|
||
static void
|
||
android_init_cursor_class (JNIEnv *env)
|
||
{
|
||
jclass old;
|
||
|
||
cursor_class.class
|
||
= (*env)->FindClass (env, "android/database/Cursor");
|
||
eassert (cursor_class.class);
|
||
|
||
old = cursor_class.class;
|
||
cursor_class.class
|
||
= (jclass) (*env)->NewGlobalRef (env, (jobject) old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
|
||
if (!cursor_class.class)
|
||
emacs_abort ();
|
||
|
||
#define FIND_METHOD(c_name, name, signature) \
|
||
cursor_class.c_name \
|
||
= (*env)->GetMethodID (env, cursor_class.class, \
|
||
name, signature); \
|
||
assert (cursor_class.c_name);
|
||
FIND_METHOD (close, "close", "()V");
|
||
#undef FIND_METHOD
|
||
}
|
||
|
||
/* Initialize `entry_class' using the given JNI environment ENV.
|
||
Calling this function is not necessary on Android 4.4 and
|
||
earlier. */
|
||
|
||
static void
|
||
android_init_entry_class (JNIEnv *env)
|
||
{
|
||
jclass old;
|
||
|
||
entry_class.class
|
||
= (*env)->FindClass (env, "org/gnu/emacs/EmacsDirectoryEntry");
|
||
eassert (entry_class.class);
|
||
|
||
old = entry_class.class;
|
||
entry_class.class
|
||
= (jclass) (*env)->NewGlobalRef (env, (jobject) old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
|
||
if (!entry_class.class)
|
||
emacs_abort ();
|
||
|
||
entry_class.d_type = (*env)->GetFieldID (env, entry_class.class,
|
||
"d_type", "I");
|
||
entry_class.d_name = (*env)->GetFieldID (env, entry_class.class,
|
||
"d_name",
|
||
"Ljava/lang/String;");
|
||
assert (entry_class.d_type && entry_class.d_name);
|
||
}
|
||
|
||
|
||
/* Initialize `fd_class' using the given JNI environment ENV. Called on
|
||
API 12 (Android 3.1) and later by androidselect.c and on 5.0 and
|
||
later in this file. */
|
||
|
||
void
|
||
android_init_fd_class (JNIEnv *env)
|
||
{
|
||
jclass old;
|
||
static bool fd_class_initialized;
|
||
|
||
if (fd_class_initialized)
|
||
return;
|
||
|
||
fd_class.class
|
||
= (*env)->FindClass (env, "android/os/ParcelFileDescriptor");
|
||
eassert (fd_class.class);
|
||
|
||
old = fd_class.class;
|
||
fd_class.class
|
||
= (jclass) (*env)->NewGlobalRef (env, (jobject) old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
|
||
if (!fd_class.class)
|
||
emacs_abort ();
|
||
|
||
#define FIND_METHOD(c_name, name, signature) \
|
||
fd_class.c_name \
|
||
= (*env)->GetMethodID (env, fd_class.class, \
|
||
name, signature); \
|
||
assert (fd_class.c_name);
|
||
FIND_METHOD (close, "close", "()V");
|
||
FIND_METHOD (get_fd, "getFd", "()I");
|
||
FIND_METHOD (detach_fd, "detachFd", "()I");
|
||
#undef FIND_METHOD
|
||
|
||
fd_class_initialized = true;
|
||
}
|
||
|
||
|
||
|
||
/* Account for SAF file names two times as large as PATH_MAX; larger
|
||
values are prohibitively slow, but smaller values can't face up to
|
||
some long file names within several nested layers of directories.
|
||
|
||
Buffers holding components or other similar file name constituents
|
||
which don't represent SAF files must continue to use PATH_MAX, for
|
||
that is the restriction imposed by the Unix file system. */
|
||
|
||
#define EMACS_PATH_MAX (PATH_MAX * 2)
|
||
|
||
/* Delete redundant instances of `.' and `..' from NAME in-place.
|
||
NAME must be *LENGTH long, excluding a mandatory trailing NULL
|
||
byte.
|
||
|
||
Transform each directory component in NAME to avoid instances
|
||
of the `.' and `..' directories. For example, turn:
|
||
|
||
a/../b/c/.
|
||
|
||
into
|
||
|
||
b/c/
|
||
|
||
and return NULL, writing the new length of NAME into *LENGTH.
|
||
|
||
If there are more `..' components in NAME than there are normal
|
||
file name components, return NAME incremented to the position after
|
||
the first `..' component that cannot be transformed. For example,
|
||
if NAME is
|
||
|
||
a/../../a
|
||
|
||
value will be
|
||
|
||
a
|
||
|
||
If NAME is a directory separator and LENGTH is 1, return without
|
||
modifying NAME. In any other case, omit any leading directory
|
||
separator when writing to NAME. This is useful when a vnode that
|
||
can only be opened as a directory is desired, as this status is
|
||
made clear by suffixing the file name with a trailing
|
||
directory separator. */
|
||
|
||
static char *
|
||
android_vfs_canonicalize_name (char *name, size_t *length)
|
||
{
|
||
size_t nellipsis, i;
|
||
char *last_component, *prev_component, *fill, *orig_name;
|
||
size_t size;
|
||
|
||
/* Special case described in the last paragraph of the comment
|
||
above. */
|
||
|
||
size = *length;
|
||
orig_name = name;
|
||
|
||
if (*name == '/' && size == 1)
|
||
return NULL;
|
||
else if (*name == '/')
|
||
size -= 1;
|
||
|
||
nellipsis = 0; /* Number of ellipsis encountered within the current
|
||
file name component, or -1. */
|
||
prev_component = NULL; /* Pointer to the separator character of
|
||
the component immediately before the
|
||
component currently being written. */
|
||
last_component = name; /* Pointer to the separator character of
|
||
the component currently being read. */
|
||
fill = name; /* Pointer to the next character that will be written
|
||
within NAME. */
|
||
|
||
/* Adjust name to skip the leading directory separator. But only
|
||
after fill is set. */
|
||
if (*name == '/')
|
||
name++;
|
||
|
||
for (i = 0; i < size; ++i)
|
||
{
|
||
switch (name[i])
|
||
{
|
||
case '/':
|
||
/* See if the previous component was `..' or `.'.
|
||
|
||
If it is .., and if no previous directory separator was
|
||
encountered, return or look up a vnode representing the
|
||
parent. */
|
||
|
||
if (nellipsis == 2)
|
||
{
|
||
/* .. */
|
||
|
||
if (!prev_component)
|
||
goto parent_vnode;
|
||
|
||
/* Return to the last component. */
|
||
fill = prev_component;
|
||
|
||
/* Restore last_component to prev_component, and
|
||
prev_component back to the component before that. */
|
||
last_component = prev_component;
|
||
|
||
if (last_component != name)
|
||
prev_component = memrchr (name, '/',
|
||
last_component - name - 1);
|
||
else
|
||
prev_component = NULL;
|
||
|
||
/* prev_component may now be NULL. If last_component is
|
||
the same as NAME, then fill has really been returned
|
||
to the beginning of the string, so leave it be. But
|
||
if it's something else, then it must be the first
|
||
separator character in the string, so set
|
||
prev_component to NAME itself. */
|
||
|
||
if (!prev_component && last_component != name)
|
||
prev_component = name;
|
||
}
|
||
else if (nellipsis == 1)
|
||
/* If it's ., return to this component. */
|
||
fill = last_component;
|
||
else
|
||
{
|
||
/* Record the position of the last directory separator,
|
||
so NAME can be overwritten from there onwards if `..'
|
||
or `.' are encountered. */
|
||
prev_component = last_component;
|
||
last_component = fill;
|
||
}
|
||
|
||
/* Allow tracking ellipses again. */
|
||
nellipsis = 0;
|
||
break;
|
||
|
||
case '.':
|
||
if (nellipsis != -1)
|
||
nellipsis++;
|
||
break;
|
||
|
||
default:
|
||
nellipsis = -1;
|
||
break;
|
||
}
|
||
|
||
/* Now copy this character over from NAME. */
|
||
*fill++ = name[i];
|
||
}
|
||
|
||
/* See if the previous component was `..' or `.'.
|
||
|
||
If it is .., and if no previous directory separator was
|
||
encountered, return or look up a vnode representing the
|
||
parent. */
|
||
|
||
if (nellipsis == 2)
|
||
{
|
||
/* .. */
|
||
|
||
if (!prev_component)
|
||
/* Look up the rest of the vnode in its parent. */
|
||
goto parent_vnode;
|
||
|
||
/* Return to the last component. */
|
||
fill = prev_component;
|
||
nellipsis = -2;
|
||
}
|
||
else if (nellipsis == 1)
|
||
{
|
||
/* If it's ., return to this component. */
|
||
fill = last_component;
|
||
nellipsis = -2;
|
||
}
|
||
|
||
/* Now, if there's enough room and an ellipsis file name was the
|
||
last component of END, append a trailing `/' before NULL
|
||
terminating it, indicating that the file name must be a
|
||
directory. */
|
||
|
||
if (fill + 1 < name + size && nellipsis == -2)
|
||
*fill++ = '/';
|
||
|
||
/* NULL terminate fill. */
|
||
*fill = '\0';
|
||
*length = fill - orig_name;
|
||
return NULL;
|
||
|
||
parent_vnode:
|
||
/* .. was encountered and the parent couldn't be found through
|
||
stripping off preceding components.
|
||
|
||
Find the parent vnode and name the rest of NAME starting from
|
||
there. */
|
||
return name + i;
|
||
}
|
||
|
||
|
||
|
||
/* Unix vnode implementation. These VFS nodes directly wrap around
|
||
the Unix filesystem, with the exception of the root vnode. */
|
||
|
||
struct android_unix_vnode
|
||
{
|
||
/* The vnode data itself. */
|
||
struct android_vnode vnode;
|
||
|
||
/* Length of the name without a trailing null byte. */
|
||
size_t name_length;
|
||
|
||
/* Name of the vnode. */
|
||
char *name;
|
||
};
|
||
|
||
struct android_unix_vdir
|
||
{
|
||
/* The directory function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The directory stream. */
|
||
DIR *directory;
|
||
};
|
||
|
||
/* The vnode representing the root filesystem. */
|
||
static struct android_unix_vnode root_vnode;
|
||
|
||
static struct android_vnode *android_unix_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_unix_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_unix_close (struct android_vnode *);
|
||
static int android_unix_unlink (struct android_vnode *);
|
||
static int android_unix_symlink (const char *, struct android_vnode *);
|
||
static int android_unix_rmdir (struct android_vnode *);
|
||
static int android_unix_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_unix_stat (struct android_vnode *, struct stat *);
|
||
static int android_unix_access (struct android_vnode *, int);
|
||
static int android_unix_mkdir (struct android_vnode *, mode_t);
|
||
static int android_unix_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_unix_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_unix_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with Unix filesystem VFS
|
||
nodes. */
|
||
|
||
static struct android_vops unix_vfs_ops =
|
||
{
|
||
android_unix_name,
|
||
android_unix_open,
|
||
android_unix_close,
|
||
android_unix_unlink,
|
||
android_unix_symlink,
|
||
android_unix_rmdir,
|
||
android_unix_rename,
|
||
android_unix_stat,
|
||
android_unix_access,
|
||
android_unix_mkdir,
|
||
android_unix_chmod,
|
||
android_unix_readlink,
|
||
android_unix_opendir,
|
||
};
|
||
|
||
static struct android_vnode *
|
||
android_unix_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
struct android_unix_vnode *vp, *input, temp;
|
||
char *fill, *remainder;
|
||
size_t j;
|
||
|
||
/* Canonicalize NAME. */
|
||
input = (struct android_unix_vnode *) vnode;
|
||
remainder = android_vfs_canonicalize_name (name, &length);
|
||
|
||
/* If remainder is set, it's a name relative to the parent
|
||
vnode. */
|
||
if (remainder)
|
||
goto parent_vnode;
|
||
|
||
/* Create a new unix vnode. */
|
||
vp = xmalloc (sizeof *vp);
|
||
|
||
/* If name is empty, duplicate the current vnode. */
|
||
|
||
if (length < 1)
|
||
{
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
vp->name = xstrdup (vp->name);
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* Otherwise, fill in the vnode. */
|
||
|
||
vp->vnode.ops = &unix_vfs_ops;
|
||
vp->vnode.type = ANDROID_VNODE_UNIX;
|
||
vp->vnode.flags = 0;
|
||
|
||
/* Generate the new name of the vnode. Remove any trailing slash
|
||
from vp->name. */
|
||
|
||
vp->name_length = input->name_length + length;
|
||
vp->name = xmalloc (vp->name_length + 2);
|
||
|
||
/* Copy the parent name over. */
|
||
fill = mempcpy (vp->name, input->name, input->name_length);
|
||
|
||
/* Check if it contains a trailing slash. input->name cannot be
|
||
empty, as the root vnode's name is `/'. */
|
||
|
||
if (fill[-1] != '/' && *name != '/')
|
||
/* If not, append a trailing slash and adjust vp->name_length
|
||
correspondingly. */
|
||
*fill++ = '/', vp->name_length++;
|
||
else if (fill[-1] == '/' && *name == '/')
|
||
/* If name has a leading slash and fill does too, move fill
|
||
backwards so that name's slash will override that of fill. */
|
||
fill--, vp->name_length--;
|
||
|
||
/* Now copy NAME. */
|
||
fill = mempcpy (fill, name, length);
|
||
|
||
/* And NULL terminate fill. */
|
||
*fill = '\0';
|
||
return &vp->vnode;
|
||
|
||
parent_vnode:
|
||
/* .. was encountered and the parent couldn't be found through
|
||
stripping off preceding components.
|
||
|
||
Find the parent vnode and name the rest of NAME starting from
|
||
there. */
|
||
|
||
if (input->name_length == 1)
|
||
/* This is the vnode representing the root directory; just look
|
||
within itself... */
|
||
vnode = &root_vnode.vnode;
|
||
else
|
||
{
|
||
/* Create a temporary asset vnode within the parent and use it
|
||
instead. First, establish the length of vp->name before its
|
||
last component. */
|
||
|
||
for (j = input->name_length - 1; j; --j)
|
||
{
|
||
if (input->name[j - 1] == '/')
|
||
break;
|
||
}
|
||
|
||
/* There must be at least one leading directory separator in an
|
||
asset vnode's `name' field. */
|
||
|
||
if (!j)
|
||
abort ();
|
||
|
||
/* j is now the length of the string minus the size of its last
|
||
component. Create a temporary vnode with that as its
|
||
name. */
|
||
|
||
temp.vnode.ops = &unix_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_UNIX;
|
||
temp.vnode.flags = 0;
|
||
temp.name_length = j;
|
||
temp.name = xmalloc (j + 1);
|
||
fill = mempcpy (temp.name, input->name, j);
|
||
*fill = '\0';
|
||
|
||
/* Search for the remainder of NAME relative to its parent. */
|
||
vnode = android_unix_name (&temp.vnode, remainder,
|
||
strlen (remainder));
|
||
xfree (temp.name);
|
||
return vnode;
|
||
}
|
||
|
||
return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
|
||
}
|
||
|
||
/* Create a Unix vnode representing the given file NAME. Use this
|
||
function to create vnodes that aren't rooted in the root VFS
|
||
node. */
|
||
|
||
static struct android_vnode *
|
||
android_unix_vnode (const char *name)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = xmalloc (sizeof *vp);
|
||
vp->vnode.ops = &unix_vfs_ops;
|
||
vp->vnode.type = ANDROID_VNODE_UNIX;
|
||
vp->vnode.flags = 0;
|
||
vp->name_length = strlen (name);
|
||
vp->name = xstrdup (name);
|
||
return &vp->vnode;
|
||
}
|
||
|
||
static int
|
||
android_unix_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd,
|
||
AAsset **asset)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
int fds;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
fds = open (vp->name, flags, mode);
|
||
|
||
if (fds < 0)
|
||
return -1;
|
||
|
||
*fd = fds;
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
android_unix_close (struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
int save_errno;
|
||
|
||
save_errno = errno;
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
xfree (vp->name);
|
||
xfree (vp);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_unix_unlink (struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return unlink (vp->name);
|
||
}
|
||
|
||
static int
|
||
android_unix_symlink (const char *target, struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return symlink (target, vp->name);
|
||
}
|
||
|
||
static int
|
||
android_unix_rmdir (struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return rmdir (vp->name);
|
||
}
|
||
|
||
static int
|
||
android_unix_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
struct android_unix_vnode *vp, *dest;
|
||
|
||
if (src->type != dst->type)
|
||
{
|
||
/* If the types of both vnodes differ, complain that they're on
|
||
two different filesystems (which is correct from a abstract
|
||
viewpoint.) */
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
vp = (struct android_unix_vnode *) src;
|
||
dest = (struct android_unix_vnode *) dst;
|
||
|
||
return (keep_existing
|
||
? renameat_noreplace (AT_FDCWD, vp->name,
|
||
AT_FDCWD, dest->name)
|
||
: rename (vp->name, dest->name));
|
||
}
|
||
|
||
static int
|
||
android_unix_stat (struct android_vnode *vnode, struct stat *statb)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return stat (vp->name, statb);
|
||
}
|
||
|
||
static int
|
||
android_unix_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return access (vp->name, mode);
|
||
}
|
||
|
||
static int
|
||
android_unix_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return mkdir (vp->name, mode);
|
||
}
|
||
|
||
static int
|
||
android_unix_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return fchmodat (AT_FDCWD, vp->name, mode, flags);
|
||
}
|
||
|
||
static ssize_t
|
||
android_unix_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
return readlink (vp->name, buffer, size);
|
||
}
|
||
|
||
static struct dirent *
|
||
android_unix_readdir (struct android_vdir *vdir)
|
||
{
|
||
struct android_unix_vdir *dir;
|
||
|
||
dir = (struct android_unix_vdir *) vdir;
|
||
return readdir (dir->directory);
|
||
}
|
||
|
||
static void
|
||
android_unix_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_unix_vdir *dir;
|
||
|
||
dir = (struct android_unix_vdir *) vdir;
|
||
closedir (dir->directory);
|
||
xfree (vdir);
|
||
}
|
||
|
||
static int
|
||
android_unix_dirfd (struct android_vdir *vdir)
|
||
{
|
||
struct android_unix_vdir *dir;
|
||
|
||
dir = (struct android_unix_vdir *) vdir;
|
||
return dirfd (dir->directory);
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_unix_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
struct android_unix_vdir *dir;
|
||
DIR *directory;
|
||
|
||
/* Try to opendir the vnode. */
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
directory = opendir (vp->name);
|
||
|
||
if (!directory)
|
||
return NULL;
|
||
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_unix_readdir;
|
||
dir->vdir.closedir = android_unix_closedir;
|
||
dir->vdir.dirfd = android_unix_dirfd;
|
||
dir->directory = directory;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
|
||
|
||
/* Asset directory handling functions. ``directory-tree'' is a file in
|
||
the root of the assets directory describing its contents.
|
||
|
||
See lib-src/asset-directory-tool for more details. */
|
||
|
||
/* The Android directory tree. */
|
||
static const char *directory_tree;
|
||
|
||
/* The size of the directory tree. */
|
||
static size_t directory_tree_size;
|
||
|
||
/* The asset manager being used. */
|
||
static AAssetManager *asset_manager;
|
||
|
||
/* Read an unaligned (32-bit) long from the address POINTER. */
|
||
|
||
static unsigned int
|
||
android_extract_long (char *pointer)
|
||
{
|
||
unsigned int number;
|
||
|
||
memcpy (&number, pointer, sizeof number);
|
||
return number;
|
||
}
|
||
|
||
/* Scan to the file FILE in the asset directory tree. Return a
|
||
pointer to the end of that file (immediately before any children)
|
||
in the directory tree, or NULL if that file does not exist.
|
||
|
||
If returning non-NULL, also return the offset to the end of the
|
||
last subdirectory or file in *LIMIT_RETURN. LIMIT_RETURN may be
|
||
NULL.
|
||
|
||
FILE must have less than 11 levels of nesting. If it ends with a
|
||
trailing slash, then NULL will be returned if it is not actually a
|
||
directory. */
|
||
|
||
static const char *
|
||
android_scan_directory_tree (char *file, size_t *limit_return)
|
||
{
|
||
char *token, *saveptr, *copy, *start, *max, *limit;
|
||
size_t token_length, ntokens, i, len;
|
||
char *tokens[10];
|
||
|
||
USE_SAFE_ALLOCA;
|
||
|
||
/* Skip past the 5 byte header. */
|
||
start = (char *) directory_tree + 5;
|
||
|
||
/* Figure out the current limit. */
|
||
limit = (char *) directory_tree + directory_tree_size;
|
||
|
||
/* Now, split `file' into tokens, with the delimiter being the file
|
||
name separator. Look for the file and seek past it. Create a copy
|
||
of FILE for the enjoyment of `strtok_r'. */
|
||
|
||
ntokens = 0;
|
||
saveptr = NULL;
|
||
len = strlen (file) + 1;
|
||
copy = SAFE_ALLOCA (len);
|
||
memcpy (copy, file, len);
|
||
memset (tokens, 0, sizeof tokens);
|
||
|
||
while ((token = strtok_r (copy, "/", &saveptr)))
|
||
{
|
||
copy = NULL;
|
||
|
||
/* Make sure ntokens is within bounds. */
|
||
if (ntokens == ARRAYELTS (tokens))
|
||
goto fail;
|
||
|
||
len = strlen (token) + 1;
|
||
tokens[ntokens] = SAFE_ALLOCA (len);
|
||
memcpy (tokens[ntokens], token, len);
|
||
ntokens++;
|
||
}
|
||
|
||
/* If there are no tokens, just return the start of the directory
|
||
tree. */
|
||
|
||
if (!ntokens)
|
||
{
|
||
SAFE_FREE ();
|
||
|
||
/* Return the size of the directory tree as the limit.
|
||
Do not subtract the initial header bytes, as the limit
|
||
is an offset from the start of the file. */
|
||
|
||
if (limit_return)
|
||
*limit_return = directory_tree_size;
|
||
|
||
return start;
|
||
}
|
||
|
||
/* Loop through tokens, indexing the directory tree each time. */
|
||
|
||
for (i = 0; i < ntokens; ++i)
|
||
{
|
||
token = tokens[i];
|
||
|
||
/* Figure out how many bytes to compare. */
|
||
token_length = strlen (token);
|
||
|
||
again:
|
||
|
||
/* If this would be past the directory, return NULL. */
|
||
if (start + token_length > limit)
|
||
goto fail;
|
||
|
||
/* Now compare the file name. */
|
||
if (!memcmp (start, token, token_length))
|
||
{
|
||
/* 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. */
|
||
|
||
max = memchr (start, 0, limit - start);
|
||
|
||
if (max != start + token_length
|
||
&& !(max == start + token_length + 1
|
||
&& *(max - 1) == '/'))
|
||
goto false_positive;
|
||
|
||
/* Return it if it exists and is in range, and this is the
|
||
last token. Otherwise, set it as start and the limit as
|
||
start + the offset and continue the loop. */
|
||
|
||
if (max && max + 5 <= limit)
|
||
{
|
||
if (i < ntokens - 1)
|
||
{
|
||
start = max + 5;
|
||
limit = ((char *) directory_tree
|
||
+ android_extract_long (max + 1));
|
||
|
||
/* Make sure limit is still in range. */
|
||
if (limit > directory_tree + directory_tree_size
|
||
|| start > directory_tree + directory_tree_size)
|
||
goto fail;
|
||
|
||
continue;
|
||
}
|
||
|
||
/* Now see if max is not a directory and file is. If
|
||
file is a directory, then return NULL. */
|
||
if (*(max - 1) != '/' && file[strlen (file) - 1] == '/')
|
||
max = NULL;
|
||
else
|
||
{
|
||
/* Figure out the limit. */
|
||
if (limit_return)
|
||
*limit_return = android_extract_long (max + 1);
|
||
|
||
/* Go to the end of this file. */
|
||
max += 5;
|
||
}
|
||
|
||
SAFE_FREE ();
|
||
return max;
|
||
}
|
||
|
||
/* Return NULL otherwise. */
|
||
__android_log_print (ANDROID_LOG_WARN, __func__,
|
||
"could not scan to end of directory tree"
|
||
": %s", file);
|
||
goto fail;
|
||
}
|
||
|
||
false_positive:
|
||
|
||
/* No match was found. Set start to the next sibling and try
|
||
again. */
|
||
|
||
start = memchr (start, 0, limit - start);
|
||
|
||
if (!start || start + 5 > limit)
|
||
goto fail;
|
||
|
||
start = ((char *) directory_tree
|
||
+ android_extract_long (start + 1));
|
||
|
||
/* Make sure start is still in bounds. */
|
||
|
||
if (start > limit)
|
||
goto fail;
|
||
|
||
/* Continue the loop. */
|
||
goto again;
|
||
}
|
||
|
||
fail:
|
||
SAFE_FREE ();
|
||
return NULL;
|
||
}
|
||
|
||
/* Return whether or not the directory tree entry DIR is a
|
||
directory.
|
||
|
||
DIR should be a value returned by
|
||
`android_scan_directory_tree'. */
|
||
|
||
static bool
|
||
android_is_directory (const char *dir)
|
||
{
|
||
/* If the directory is the directory tree, then it is a
|
||
directory. */
|
||
if (dir == directory_tree + 5)
|
||
return true;
|
||
|
||
/* Otherwise, look 5 bytes behind. If it is `/', then it is a
|
||
directory. */
|
||
return (dir - 6 >= directory_tree
|
||
&& *(dir - 6) == '/');
|
||
}
|
||
|
||
/* Initialize asset retrieval. ENV should be a JNI environment for
|
||
the Emacs thread, and MANAGER should be a local reference to a Java
|
||
asset manager object created for the Emacs service context. */
|
||
|
||
static void
|
||
android_init_assets (JNIEnv *env, jobject manager)
|
||
{
|
||
AAsset *asset;
|
||
|
||
/* Set the asset manager. */
|
||
asset_manager = AAssetManager_fromJava (env, manager);
|
||
|
||
/* Initialize the directory tree. */
|
||
asset = AAssetManager_open (asset_manager, "directory-tree",
|
||
AASSET_MODE_BUFFER);
|
||
|
||
if (!asset)
|
||
{
|
||
__android_log_print (ANDROID_LOG_FATAL, __func__,
|
||
"Failed to open directory tree");
|
||
emacs_abort ();
|
||
}
|
||
|
||
directory_tree = AAsset_getBuffer (asset);
|
||
|
||
if (!directory_tree)
|
||
emacs_abort ();
|
||
|
||
/* Now figure out how big the directory tree is, and compare the
|
||
first few bytes. */
|
||
directory_tree_size = AAsset_getLength (asset);
|
||
if (directory_tree_size < 5
|
||
|| memcmp (directory_tree, "EMACS", 5))
|
||
{
|
||
__android_log_print (ANDROID_LOG_FATAL, __func__,
|
||
"Directory tree has bad magic");
|
||
emacs_abort ();
|
||
}
|
||
|
||
/* Hold a VM reference to the asset manager to prevent the native
|
||
object from being deleted. */
|
||
(*env)->NewGlobalRef (env, manager);
|
||
|
||
/* Abort if there's no more memory for the global reference. */
|
||
if ((*env)->ExceptionCheck (env))
|
||
abort ();
|
||
}
|
||
|
||
|
||
|
||
/* Asset-to-file descriptor conversion. */
|
||
|
||
/* Pointer to the `ASharedMemory_create' function which is loaded
|
||
dynamically. */
|
||
static int (*asharedmemory_create) (const char *, size_t);
|
||
|
||
/* Do the same as android_hack_asset_fd, but use an unlinked temporary
|
||
file to cater to old Android kernels where ashmem files are not
|
||
readable. */
|
||
|
||
static int
|
||
android_hack_asset_fd_fallback (AAsset *asset)
|
||
{
|
||
int fd;
|
||
char filename[PATH_MAX];
|
||
size_t size;
|
||
void *mem;
|
||
|
||
/* Assets must be small enough to fit in size_t, if off_t is
|
||
larger. */
|
||
size = AAsset_getLength (asset);
|
||
|
||
/* Get an unlinked file descriptor from a file in the cache
|
||
directory, which is guaranteed to only be written to by Emacs.
|
||
Creating an ashmem file descriptor and reading from it doesn't
|
||
work on these old Android versions. */
|
||
|
||
snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d",
|
||
android_cache_dir, getpid ());
|
||
fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
|
||
S_IRUSR | S_IWUSR);
|
||
|
||
if (fd < 0)
|
||
return -1;
|
||
|
||
if (unlink (filename))
|
||
goto fail;
|
||
|
||
if (ftruncate (fd, size))
|
||
goto fail;
|
||
|
||
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
|
||
if (mem == MAP_FAILED)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"mmap: %s", strerror (errno));
|
||
goto fail;
|
||
}
|
||
|
||
if (AAsset_read (asset, mem, size) != size)
|
||
{
|
||
/* Too little was read. Close the file descriptor and
|
||
report an error. */
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"AAsset_read: %s", strerror (errno));
|
||
goto fail;
|
||
}
|
||
|
||
munmap (mem, size);
|
||
return fd;
|
||
|
||
fail:
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
/* Return whether or not shared memory file descriptors can also be
|
||
read from, and are thus suitable for creating asset files.
|
||
|
||
This does not work on some ancient Android systems running old
|
||
versions of the kernel. */
|
||
|
||
static bool
|
||
android_detect_ashmem (void)
|
||
{
|
||
int fd, rc;
|
||
void *mem;
|
||
char test_buffer[10];
|
||
|
||
memcpy (test_buffer, "abcdefghi", 10);
|
||
|
||
/* Create the file descriptor to be used for the test. */
|
||
|
||
/* Android 28 and earlier let Emacs access /dev/ashmem directly, so
|
||
prefer that over using ASharedMemory. */
|
||
|
||
if (android_get_current_api_level () <= 28)
|
||
{
|
||
fd = open ("/dev/ashmem", O_RDWR);
|
||
|
||
if (fd < 0)
|
||
return false;
|
||
|
||
/* An empty name means the memory area will exist until the file
|
||
descriptor is closed, because no other process can
|
||
attach. */
|
||
rc = ioctl (fd, ASHMEM_SET_NAME, "");
|
||
|
||
if (rc < 0)
|
||
{
|
||
close (fd);
|
||
return false;
|
||
}
|
||
|
||
rc = ioctl (fd, ASHMEM_SET_SIZE, sizeof test_buffer);
|
||
|
||
if (rc < 0)
|
||
{
|
||
close (fd);
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* On the other hand, SELinux restrictions on Android 29 and
|
||
later require that Emacs use a system service to obtain
|
||
shared memory. Load this dynamically, as this service is not
|
||
available on all versions of the NDK. */
|
||
|
||
if (!asharedmemory_create)
|
||
{
|
||
*(void **) (&asharedmemory_create)
|
||
= dlsym (RTLD_DEFAULT, "ASharedMemory_create");
|
||
|
||
if (!asharedmemory_create)
|
||
{
|
||
__android_log_print (ANDROID_LOG_FATAL, __func__,
|
||
"dlsym: %s\n",
|
||
strerror (errno));
|
||
emacs_abort ();
|
||
}
|
||
}
|
||
|
||
fd = (*asharedmemory_create) ("", sizeof test_buffer);
|
||
|
||
if (fd < 0)
|
||
return false;
|
||
}
|
||
|
||
/* Now map the resource and write the test contents. */
|
||
|
||
mem = mmap (NULL, sizeof test_buffer, PROT_WRITE,
|
||
MAP_SHARED, fd, 0);
|
||
if (mem == MAP_FAILED)
|
||
{
|
||
close (fd);
|
||
return false;
|
||
}
|
||
|
||
/* Copy over the test contents. */
|
||
memcpy (mem, test_buffer, sizeof test_buffer);
|
||
|
||
/* Return anyway even if munmap fails. */
|
||
munmap (mem, sizeof test_buffer);
|
||
|
||
/* Try to read the content back into test_buffer. If this does not
|
||
compare equal to the original string, or the read fails, then
|
||
ashmem descriptors are not readable on this system. */
|
||
|
||
if ((read (fd, test_buffer, sizeof test_buffer)
|
||
!= sizeof test_buffer)
|
||
|| memcmp (test_buffer, "abcdefghi", sizeof test_buffer))
|
||
{
|
||
__android_log_print (ANDROID_LOG_WARN, __func__,
|
||
"/dev/ashmem does not produce real"
|
||
" temporary files on this system, so"
|
||
" Emacs will fall back to creating"
|
||
" unlinked temporary files.");
|
||
close (fd);
|
||
return false;
|
||
}
|
||
|
||
close (fd);
|
||
return true;
|
||
}
|
||
|
||
/* Get a file descriptor backed by a temporary in-memory file for the
|
||
given asset. */
|
||
|
||
static int
|
||
android_hack_asset_fd (AAsset *asset)
|
||
{
|
||
static bool ashmem_readable_p;
|
||
static bool ashmem_initialized;
|
||
int fd, rc;
|
||
unsigned char *mem;
|
||
size_t size;
|
||
|
||
/* The first time this function is called, try to determine whether
|
||
or not ashmem file descriptors can be read from. */
|
||
|
||
if (!ashmem_initialized)
|
||
ashmem_readable_p
|
||
= android_detect_ashmem ();
|
||
ashmem_initialized = true;
|
||
|
||
/* If it isn't, fall back. */
|
||
|
||
if (!ashmem_readable_p)
|
||
return android_hack_asset_fd_fallback (asset);
|
||
|
||
/* Assets must be small enough to fit in size_t, if off_t is
|
||
larger. */
|
||
size = AAsset_getLength (asset);
|
||
|
||
/* Android 28 and earlier let Emacs access /dev/ashmem directly, so
|
||
prefer that over using ASharedMemory. */
|
||
|
||
if (android_get_current_api_level () <= 28)
|
||
{
|
||
fd = open ("/dev/ashmem", O_RDWR);
|
||
|
||
if (fd < 0)
|
||
return -1;
|
||
|
||
/* An empty name means the memory area will exist until the file
|
||
descriptor is closed, because no other process can
|
||
attach. */
|
||
rc = ioctl (fd, ASHMEM_SET_NAME, "");
|
||
|
||
if (rc < 0)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"ioctl ASHMEM_SET_NAME: %s",
|
||
strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
rc = ioctl (fd, ASHMEM_SET_SIZE, size);
|
||
|
||
if (rc < 0)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"ioctl ASHMEM_SET_SIZE: %s",
|
||
strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
if (!size)
|
||
return fd;
|
||
|
||
/* Now map the resource. */
|
||
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
|
||
if (mem == MAP_FAILED)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"mmap: %s", strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
if (AAsset_read (asset, mem, size) != size)
|
||
{
|
||
/* Too little was read. Close the file descriptor and
|
||
report an error. */
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"AAsset_read: %s", strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
/* Return anyway even if munmap fails. */
|
||
munmap (mem, size);
|
||
return fd;
|
||
}
|
||
|
||
/* On the other hand, SELinux restrictions on Android 29 and later
|
||
require that Emacs use a system service to obtain shared memory.
|
||
Load this dynamically, as this service is not available on all
|
||
versions of the NDK. */
|
||
|
||
if (!asharedmemory_create)
|
||
{
|
||
*(void **) (&asharedmemory_create)
|
||
= dlsym (RTLD_DEFAULT, "ASharedMemory_create");
|
||
|
||
if (!asharedmemory_create)
|
||
{
|
||
__android_log_print (ANDROID_LOG_FATAL, __func__,
|
||
"dlsym: %s\n",
|
||
strerror (errno));
|
||
emacs_abort ();
|
||
}
|
||
}
|
||
|
||
fd = (*asharedmemory_create) ("", size);
|
||
|
||
if (fd < 0)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"ASharedMemory_create: %s",
|
||
strerror (errno));
|
||
return -1;
|
||
}
|
||
|
||
/* Now map the resource. */
|
||
mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
|
||
if (mem == MAP_FAILED)
|
||
{
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"mmap: %s", strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
if (AAsset_read (asset, mem, size) != size)
|
||
{
|
||
/* Too little was read. Close the file descriptor and
|
||
report an error. */
|
||
__android_log_print (ANDROID_LOG_ERROR, __func__,
|
||
"AAsset_read: %s", strerror (errno));
|
||
close (fd);
|
||
return -1;
|
||
}
|
||
|
||
/* Return anyway even if munmap fails. */
|
||
munmap (mem, size);
|
||
return fd;
|
||
}
|
||
|
||
|
||
|
||
/* ``Asset file system'' vnode implementation. These vnodes map to
|
||
asset files within the application package, provided by the Android
|
||
``asset manager''. */
|
||
|
||
struct android_afs_vnode
|
||
{
|
||
/* The vnode data itself. */
|
||
struct android_vnode vnode;
|
||
|
||
/* Length of the name without a trailing null byte. */
|
||
size_t name_length;
|
||
|
||
/* Name of the vnode. */
|
||
char *name;
|
||
};
|
||
|
||
struct android_afs_vdir
|
||
{
|
||
/* The directory function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The next directory stream in `all_afs_vdirs'. */
|
||
struct android_afs_vdir *next;
|
||
|
||
/* Pointer to the directory in directory_tree. */
|
||
char *asset_dir;
|
||
|
||
/* And the end of the files in asset_dir. */
|
||
char *asset_limit;
|
||
|
||
/* Path to the directory relative to /. */
|
||
char *asset_file;
|
||
|
||
/* File descriptor representing this directory stream, or NULL. */
|
||
int fd;
|
||
};
|
||
|
||
struct android_afs_open_fd
|
||
{
|
||
/* The next table entry. */
|
||
struct android_afs_open_fd *next;
|
||
|
||
/* The open file descriptor. */
|
||
int fd;
|
||
|
||
/* The stat buffer associated with this entry. */
|
||
struct stat statb;
|
||
};
|
||
|
||
static struct android_vnode *android_afs_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_afs_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_afs_close (struct android_vnode *);
|
||
static int android_afs_unlink (struct android_vnode *);
|
||
static int android_afs_symlink (const char *, struct android_vnode *);
|
||
static int android_afs_rmdir (struct android_vnode *);
|
||
static int android_afs_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_afs_stat (struct android_vnode *, struct stat *);
|
||
static int android_afs_access (struct android_vnode *, int);
|
||
static int android_afs_mkdir (struct android_vnode *, mode_t);
|
||
static int android_afs_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_afs_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_afs_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with asset VFS nodes. */
|
||
|
||
static struct android_vops afs_vfs_ops =
|
||
{
|
||
android_afs_name,
|
||
android_afs_open,
|
||
android_afs_close,
|
||
android_afs_unlink,
|
||
android_afs_symlink,
|
||
android_afs_rmdir,
|
||
android_afs_rename,
|
||
android_afs_stat,
|
||
android_afs_access,
|
||
android_afs_mkdir,
|
||
android_afs_chmod,
|
||
android_afs_readlink,
|
||
android_afs_opendir,
|
||
};
|
||
|
||
/* Chain consisting of all open asset directory streams. */
|
||
static struct android_afs_vdir *all_afs_vdirs;
|
||
|
||
/* List linking open file descriptors to asset information. This
|
||
assumes Emacs does not use dup on regular files. */
|
||
static struct android_afs_open_fd *afs_file_descriptors;
|
||
|
||
static struct android_vnode *
|
||
android_afs_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
size_t j;
|
||
char *remainder, *fill;
|
||
struct android_afs_vnode *vp, *input;
|
||
struct android_afs_vnode temp;
|
||
|
||
input = (struct android_afs_vnode *) vnode;
|
||
|
||
/* Canonicalize NAME. */
|
||
remainder = android_vfs_canonicalize_name (name, &length);
|
||
|
||
/* If remainder is set, it's a name relative to the parent
|
||
vnode. */
|
||
if (remainder)
|
||
goto parent_vnode;
|
||
|
||
/* Allocate a new vnode. */
|
||
vp = xmalloc (sizeof *vp);
|
||
|
||
/* See the specified name is empty. */
|
||
|
||
if (length < 1)
|
||
{
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
vp->name = xstrdup (vp->name);
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* Recompute length. */
|
||
vp->vnode.ops = &afs_vfs_ops;
|
||
vp->vnode.type = ANDROID_VNODE_AFS;
|
||
vp->vnode.flags = 0;
|
||
|
||
/* Generate the new name of the vnode. Remove any trailing slash
|
||
from vp->name. */
|
||
|
||
vp->name_length = input->name_length + length;
|
||
vp->name = xmalloc (vp->name_length + 2);
|
||
|
||
/* Copy the parent name over. */
|
||
fill = mempcpy (vp->name, input->name, input->name_length);
|
||
|
||
/* Check if it contains a trailing slash. input->name cannot be
|
||
empty, as the root vnode's name is `/'. */
|
||
|
||
if (fill[-1] != '/' && *name != '/')
|
||
/* If not, append a trailing slash and adjust vp->name_length
|
||
correspondingly. */
|
||
*fill++ = '/', vp->name_length++;
|
||
else if (fill[-1] == '/' && *name == '/')
|
||
/* If name has a leading slash and fill does too, move fill
|
||
backwards so that name's slash will override that of fill. */
|
||
fill--, vp->name_length--;
|
||
|
||
/* Now copy NAME. */
|
||
fill = mempcpy (fill, name, length);
|
||
|
||
/* And NULL terminate fill. */
|
||
*fill = '\0';
|
||
return &vp->vnode;
|
||
|
||
parent_vnode:
|
||
/* .. was encountered and the parent couldn't be found through
|
||
stripping off preceding components.
|
||
|
||
Find the parent vnode and name the rest of NAME starting from
|
||
there. */
|
||
|
||
if (input->name_length == 1)
|
||
/* This is the vnode representing the /assets directory... */
|
||
vnode = &root_vnode.vnode;
|
||
else
|
||
{
|
||
/* Create a temporary asset vnode within the parent and use it
|
||
instead. First, establish the length of vp->name before its
|
||
last component. */
|
||
|
||
for (j = input->name_length - 1; j; --j)
|
||
{
|
||
if (input->name[j - 1] == '/')
|
||
break;
|
||
}
|
||
|
||
/* There must be at least one leading directory separator in an
|
||
asset vnode's `name' field. */
|
||
|
||
if (!j)
|
||
abort ();
|
||
|
||
/* j is now the length of the string minus the size of its last
|
||
component. Create a temporary vnode with that as its
|
||
name. */
|
||
|
||
temp.vnode.ops = &afs_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_AFS;
|
||
temp.vnode.flags = 0;
|
||
temp.name_length = j;
|
||
temp.name = xmalloc (j + 1);
|
||
fill = mempcpy (temp.name, input->name, j);
|
||
*fill = '\0';
|
||
|
||
/* Search for the remainder of NAME relative to its parent. */
|
||
vnode = android_afs_name (&temp.vnode, remainder,
|
||
strlen (remainder));
|
||
xfree (temp.name);
|
||
return vnode;
|
||
}
|
||
|
||
return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
|
||
}
|
||
|
||
/* Find the vnode designated by the normalized NAME relative to the
|
||
root of the asset file system. NAME may be modified, and must be
|
||
LENGTH bytes long, excluding its terminating NULL byte. */
|
||
|
||
static struct android_vnode *
|
||
android_afs_initial (char *name, size_t length)
|
||
{
|
||
struct android_afs_vnode temp;
|
||
|
||
/* Create a temporary vnode at the root of the asset file
|
||
system. */
|
||
|
||
temp.vnode.ops = &afs_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_AFS;
|
||
temp.vnode.flags = 0;
|
||
temp.name_length = 1;
|
||
temp.name = (char *) "/";
|
||
|
||
/* Try to name this vnode. If NAME is empty, it will be duplicated
|
||
instead. */
|
||
return android_afs_name (&temp.vnode, name, length);
|
||
}
|
||
|
||
/* Make FD close-on-exec. If any system call fails, do not abort, but
|
||
log a warning to the system log. */
|
||
|
||
static void
|
||
android_close_on_exec (int fd)
|
||
{
|
||
int flags, rc;
|
||
|
||
flags = fcntl (fd, F_GETFD);
|
||
|
||
if (flags < 0)
|
||
{
|
||
__android_log_print (ANDROID_LOG_WARN, __func__,
|
||
"fcntl: %s", strerror (errno));
|
||
return;
|
||
}
|
||
|
||
rc = fcntl (fd, F_SETFD, flags | O_CLOEXEC);
|
||
|
||
if (rc < 0)
|
||
{
|
||
__android_log_print (ANDROID_LOG_WARN, __func__,
|
||
"fcntl: %s", strerror (errno));
|
||
return;
|
||
}
|
||
}
|
||
|
||
static int
|
||
android_afs_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd_return,
|
||
AAsset **asset_return)
|
||
{
|
||
AAsset *asset;
|
||
struct android_afs_vnode *vp;
|
||
const char *asset_dir;
|
||
int fd;
|
||
struct android_afs_open_fd *info;
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
|
||
/* Return suitable error indications for unsupported file
|
||
operations. */
|
||
|
||
if ((flags & O_WRONLY) || (flags & O_RDWR))
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
if (flags & O_DIRECTORY)
|
||
{
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
/* Now try to open this asset. Asset manager APIs expect there to
|
||
be no trailing directory separator. */
|
||
asset = AAssetManager_open (asset_manager, vp->name + 1,
|
||
AASSET_MODE_STREAMING);
|
||
|
||
/* If it can't be opened, return an error indication. */
|
||
|
||
if (!asset)
|
||
{
|
||
/* Scan the directory tree for this file. */
|
||
asset_dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
/* Default errno to ENOTENT. */
|
||
errno = ENOENT;
|
||
|
||
/* Maybe the caller wants to open a directory vnode as a
|
||
file? */
|
||
|
||
if (asset_dir && android_is_directory (asset_dir))
|
||
/* In that case, set errno to ENOSYS. */
|
||
errno = ENOSYS;
|
||
|
||
return -1;
|
||
}
|
||
|
||
/* An asset has been opened. If the caller wants a file descriptor,
|
||
a temporary one must be created and the file contents read
|
||
inside. */
|
||
|
||
if (!asset_p)
|
||
{
|
||
/* Create a shared memory file descriptor containing the asset
|
||
contents.
|
||
|
||
The documentation misleads people into thinking that
|
||
AAsset_openFileDescriptor does precisely this. However, it
|
||
instead returns an offset into any uncompressed assets in the
|
||
ZIP archive. This cannot be found in its documentation. */
|
||
|
||
fd = android_hack_asset_fd (asset);
|
||
|
||
if (fd == -1)
|
||
{
|
||
AAsset_close (asset);
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* If O_CLOEXEC is specified, make the file descriptor close on
|
||
exec too. */
|
||
|
||
if (flags & O_CLOEXEC)
|
||
android_close_on_exec (fd);
|
||
|
||
/* Keep a record linking ``hacked'' file descriptors with
|
||
their file status. */
|
||
info = xzalloc (sizeof *info);
|
||
info->fd = fd;
|
||
info->next = afs_file_descriptors;
|
||
|
||
/* Fill in some information that will be reported to
|
||
callers of android_fstat, among others. */
|
||
info->statb.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||
|
||
/* Owned by root. */
|
||
info->statb.st_uid = 0;
|
||
info->statb.st_gid = 0;
|
||
|
||
/* Concoct a nonexistent device and an inode number. */
|
||
info->statb.st_dev = -1;
|
||
info->statb.st_ino = 0;
|
||
|
||
/* Size of the file. */
|
||
info->statb.st_size = AAsset_getLength (asset);
|
||
|
||
/* If the installation date can be ascertained, return that as
|
||
the file's modification time. */
|
||
|
||
if (timespec_valid_p (emacs_installation_time))
|
||
{
|
||
#ifdef STAT_TIMESPEC
|
||
STAT_TIMESPEC (&info->statb, st_mtim) = emacs_installation_time;
|
||
#else /* !STAT_TIMESPEC */
|
||
/* Headers supplied by the NDK r10b contain a `struct stat'
|
||
without POSIX fields for nano-second timestamps. */
|
||
info->statb.st_mtime = emacs_installation_time.tv_sec;
|
||
info->statb.st_mtime_nsec = emacs_installation_time.tv_nsec;
|
||
#endif /* STAT_TIMESPEC */
|
||
}
|
||
|
||
/* Chain info onto afs_file_descriptors. */
|
||
afs_file_descriptors = info;
|
||
|
||
AAsset_close (asset);
|
||
|
||
/* Return the file descriptor. */
|
||
*fd_return = fd;
|
||
return 0;
|
||
}
|
||
|
||
/* Return the asset itself. */
|
||
*asset_return = asset;
|
||
return 1;
|
||
}
|
||
|
||
static void
|
||
android_afs_close (struct android_vnode *vnode)
|
||
{
|
||
struct android_afs_vnode *vp;
|
||
int save_errno;
|
||
|
||
save_errno = errno;
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
xfree (vp->name);
|
||
xfree (vp);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_afs_unlink (struct android_vnode *vnode)
|
||
{
|
||
const char *dir;
|
||
struct android_afs_vnode *vp;
|
||
|
||
/* If the vnode already exists, return EROFS. Else, return
|
||
ENOENT. */
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
if (dir)
|
||
errno = EROFS;
|
||
else
|
||
errno = ENOENT;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_symlink (const char *linkname, struct android_vnode *vnode)
|
||
{
|
||
struct android_afs_vnode *vp;
|
||
|
||
/* If this vnode already exists, return EEXIST. */
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
|
||
if (android_scan_directory_tree (vp->name, NULL))
|
||
{
|
||
errno = EEXIST;
|
||
return -1;
|
||
}
|
||
|
||
/* Symlinks aren't supported on this (read-only) ``file system'',
|
||
so return -1 with EROFS. */
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_rmdir (struct android_vnode *vnode)
|
||
{
|
||
const char *dir;
|
||
struct android_afs_vnode *vp;
|
||
|
||
/* If the vnode already exists and is a directory, return EROFS.
|
||
Else, return ENOTDIR or ENOENT. */
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
if (dir && android_is_directory (dir))
|
||
errno = EROFS;
|
||
else if (dir)
|
||
errno = ENOTDIR;
|
||
else
|
||
errno = ENOENT;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_rename (struct android_vnode *src, struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
/* If src and dst are different kinds of vnodes, return EXDEV.
|
||
Else, return EROFS. */
|
||
|
||
errno = EROFS;
|
||
if (src->type != dst->type)
|
||
errno = EXDEV;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_stat (struct android_vnode *vnode, struct stat *statb)
|
||
{
|
||
const char *dir;
|
||
struct android_afs_vnode *vp;
|
||
AAsset *asset_desc;
|
||
|
||
/* Scan for the vnode to see whether or not it exists. */
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
if (!dir)
|
||
{
|
||
/* Return ENOENT; whether the lookup failed because directory
|
||
components within vp->path weren't really directories is not
|
||
important to Emacs's error reporting. */
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
if (android_is_directory (dir))
|
||
{
|
||
memset (statb, 0, sizeof *statb);
|
||
|
||
/* Fill in the stat buffer. */
|
||
statb->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||
|
||
/* Grant search permissions as well. */
|
||
statb->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
|
||
|
||
/* Concoct a nonexistent device and an inode number. */
|
||
statb->st_dev = -1;
|
||
statb->st_ino = 0;
|
||
goto set_file_times;
|
||
}
|
||
|
||
/* AASSET_MODE_STREAMING is fastest here. */
|
||
asset_desc = AAssetManager_open (asset_manager, vp->name + 1,
|
||
AASSET_MODE_STREAMING);
|
||
|
||
if (!asset_desc)
|
||
{
|
||
/* If the asset exists in the directory tree but can't be
|
||
located by the asset manager, report OOM. */
|
||
errno = ENOMEM;
|
||
return 1;
|
||
}
|
||
|
||
memset (statb, 0, sizeof *statb);
|
||
|
||
/* Fill in the stat buffer. */
|
||
statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||
statb->st_dev = -1;
|
||
statb->st_ino = 0;
|
||
statb->st_size = AAsset_getLength (asset_desc);
|
||
|
||
/* Close the asset. */
|
||
AAsset_close (asset_desc);
|
||
|
||
set_file_times:
|
||
|
||
/* If the installation date can be ascertained, return that as the
|
||
file's modification time. */
|
||
|
||
if (timespec_valid_p (emacs_installation_time))
|
||
{
|
||
#ifdef STAT_TIMESPEC
|
||
STAT_TIMESPEC (statb, st_mtim) = emacs_installation_time;
|
||
#else /* !STAT_TIMESPEC */
|
||
/* Headers supplied by the NDK r10b contain a `struct stat'
|
||
without POSIX fields for nano-second timestamps. */
|
||
statb->st_mtime = emacs_installation_time.tv_sec;
|
||
statb->st_mtime_nsec = emacs_installation_time.tv_nsec;
|
||
#endif /* STAT_TIMESPEC */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_afs_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
const char *dir;
|
||
struct android_afs_vnode *vp;
|
||
|
||
/* Validate MODE. */
|
||
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
/* Scan for the vnode to see whether or not it exists. */
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
if (dir)
|
||
{
|
||
/* It exists. If MODE contains W_OK or X_OK, return
|
||
EACCESS. */
|
||
|
||
if (mode & (W_OK | X_OK))
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
/* If vp->name is a directory and DIR isn't, return ENOTDIR. */
|
||
|
||
if (vp->name[vp->name_length] == '/'
|
||
&& !android_is_directory (dir))
|
||
{
|
||
errno = ENOTDIR;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
struct android_afs_vnode *vp;
|
||
const char *dir;
|
||
|
||
/* If the vnode already exists, return EEXIST in lieu of EROFS. */
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
if (dir)
|
||
errno = EEXIST;
|
||
else
|
||
errno = EROFS;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_afs_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static ssize_t
|
||
android_afs_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
struct android_afs_vnode *vp;
|
||
const char *dir;
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
dir = android_scan_directory_tree (vp->name, NULL);
|
||
|
||
/* As there are no symlinks in /assets, just return -1 with errno
|
||
set to a reasonable value contingent upon whether VP->name
|
||
actually exists. */
|
||
|
||
if (dir)
|
||
errno = EINVAL;
|
||
else
|
||
errno = ENOENT;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static struct dirent *
|
||
android_afs_readdir (struct android_vdir *vdir)
|
||
{
|
||
static struct dirent dirent;
|
||
const char *last;
|
||
struct android_afs_vdir *dir;
|
||
|
||
dir = (struct android_afs_vdir *) vdir;
|
||
|
||
/* There are no more files to read. */
|
||
if (dir->asset_dir >= dir->asset_limit)
|
||
return NULL;
|
||
|
||
/* Otherwise, scan forward looking for the next NULL byte. */
|
||
last = memchr (dir->asset_dir, 0,
|
||
dir->asset_limit - dir->asset_dir);
|
||
|
||
/* No more NULL bytes remain. */
|
||
if (!last)
|
||
return NULL;
|
||
|
||
/* Forward last past the NULL byte. */
|
||
last++;
|
||
|
||
/* Make sure it is still within the directory tree. */
|
||
if (last >= directory_tree + directory_tree_size)
|
||
return NULL;
|
||
|
||
/* Now, fill in the dirent with the name. */
|
||
memset (&dirent, 0, sizeof dirent);
|
||
dirent.d_ino = 0;
|
||
dirent.d_off = 0;
|
||
dirent.d_reclen = sizeof dirent;
|
||
|
||
/* Note that dir->asset_dir is actually a NULL terminated
|
||
string. */
|
||
memcpy (dirent.d_name, dir->asset_dir,
|
||
MIN (sizeof dirent.d_name,
|
||
last - dir->asset_dir));
|
||
dirent.d_name[sizeof dirent.d_name - 1] = '\0';
|
||
|
||
/* Strip off the trailing slash, if any. */
|
||
if (dirent.d_name[MIN (sizeof dirent.d_name,
|
||
last - dir->asset_dir)
|
||
- 2] == '/')
|
||
dirent.d_name[MIN (sizeof dirent.d_name,
|
||
last - dir->asset_dir)
|
||
- 2] = '\0';
|
||
|
||
/* If this is not a directory, return DT_REG. Otherwise, return
|
||
DT_DIR. */
|
||
|
||
if (last - 2 >= directory_tree && last[-2] == '/')
|
||
dirent.d_type = DT_DIR;
|
||
else
|
||
dirent.d_type = DT_REG;
|
||
|
||
/* Forward dir->asset_dir to the file past last. */
|
||
dir->asset_dir = ((char *) directory_tree
|
||
+ android_extract_long ((char *) last));
|
||
|
||
return &dirent;
|
||
}
|
||
|
||
static void
|
||
android_afs_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_afs_vdir *dir, **next, *tem;
|
||
|
||
dir = (struct android_afs_vdir *) vdir;
|
||
|
||
/* If the ``directory file descriptor'' has been opened, close
|
||
it. */
|
||
|
||
if (dir->fd != -1)
|
||
close (dir->fd);
|
||
|
||
xfree (dir->asset_file);
|
||
|
||
/* Now unlink this directory. */
|
||
|
||
for (next = &all_afs_vdirs; (tem = *next);)
|
||
{
|
||
if (tem == dir)
|
||
*next = dir->next;
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
/* Free the directory itself. */
|
||
|
||
xfree (dir);
|
||
}
|
||
|
||
static int
|
||
android_afs_dirfd (struct android_vdir *vdir)
|
||
{
|
||
struct android_afs_vdir *dir;
|
||
|
||
dir = (struct android_afs_vdir *) vdir;
|
||
|
||
/* Since `android_afs_opendir' tries to avoid opening a file
|
||
descriptor if readdir isn't called, dirfd can fail if open fails.
|
||
|
||
open sets errno to a set of errors different from what POSIX
|
||
stipulates for dirfd, but for ease of implementation the open
|
||
errors are used instead. */
|
||
|
||
if (dir->fd >= 0)
|
||
return dir->fd;
|
||
|
||
dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
|
||
return dir->fd;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_afs_opendir (struct android_vnode *vnode)
|
||
{
|
||
char *asset_dir;
|
||
struct android_afs_vdir *dir;
|
||
struct android_afs_vnode *vp;
|
||
size_t limit;
|
||
|
||
vp = (struct android_afs_vnode *) vnode;
|
||
|
||
/* Scan for the asset directory by vp->name. */
|
||
|
||
asset_dir
|
||
= (char *) android_scan_directory_tree (vp->name, &limit);
|
||
|
||
if (!asset_dir)
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* Verify that asset_dir is indeed a directory. */
|
||
|
||
if (!android_is_directory (asset_dir))
|
||
{
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
/* Fill in the directory stream. */
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_afs_readdir;
|
||
dir->vdir.closedir = android_afs_closedir;
|
||
dir->vdir.dirfd = android_afs_dirfd;
|
||
dir->asset_dir = asset_dir;
|
||
dir->asset_limit = (char *) directory_tree + limit;
|
||
dir->fd = -1;
|
||
dir->asset_file = xzalloc (vp->name_length + 2);
|
||
strcpy (dir->asset_file, vp->name);
|
||
|
||
/* Make sure dir->asset_file is terminated with /. */
|
||
if (dir->asset_file[vp->name_length - 1] != '/')
|
||
dir->asset_file[vp->name_length] = '/';
|
||
|
||
/* Make sure dir->asset_limit is within bounds. It is a limit,
|
||
and as such can be exactly one byte past directory_tree. */
|
||
if (dir->asset_limit > directory_tree + directory_tree_size)
|
||
{
|
||
xfree (dir->asset_file);
|
||
xfree (dir);
|
||
errno = EACCES;
|
||
return NULL;
|
||
}
|
||
|
||
dir->next = all_afs_vdirs;
|
||
all_afs_vdirs = dir;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
/* Return the file name corresponding to DIRFD if it is a
|
||
``directory'' file descriptor returned by `android_afs_dirfd' or
|
||
NULL otherwise. These file names are relative to the `/assets'
|
||
directory, but with a leading separator character. */
|
||
|
||
static char *
|
||
android_afs_get_directory_name (int dirfd)
|
||
{
|
||
struct android_afs_vdir *dir;
|
||
|
||
for (dir = all_afs_vdirs; dir; dir = dir->next)
|
||
{
|
||
if (dir->fd == dirfd && dirfd != -1)
|
||
return dir->asset_file;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
struct android_content_vdir
|
||
{
|
||
/* The directory function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The next directory stream in `all_content_vdirs'. */
|
||
struct android_content_vdir *next;
|
||
|
||
/* Pointer to the next file to return. */
|
||
const char **next_name;
|
||
|
||
/* Temporary file descriptor used to identify this directory to
|
||
at-funcs, or -1. */
|
||
int fd;
|
||
};
|
||
|
||
static struct android_vnode *android_authority_initial (char *, size_t);
|
||
static struct android_vnode *android_authority_initial_name (char *, size_t);
|
||
static struct android_vnode *android_saf_root_initial (char *, size_t);
|
||
|
||
/* Content provider meta-interface. This implements a vnode at
|
||
/content, which is a directory itself containing two additional
|
||
directories.
|
||
|
||
/content/storage only exists on Android 5.0 and later, and contains
|
||
a list of each directory tree Emacs has been granted permanent
|
||
access to through the Storage Access Framework.
|
||
|
||
/content/by-authority and /content/by-authority-named exists on
|
||
Android 4.4 and later; it contains no directories, but provides a
|
||
`name' function that converts children into content URIs. */
|
||
|
||
static struct android_vnode *android_content_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_content_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_content_close (struct android_vnode *);
|
||
static int android_content_unlink (struct android_vnode *);
|
||
static int android_content_symlink (const char *, struct android_vnode *);
|
||
static int android_content_rmdir (struct android_vnode *);
|
||
static int android_content_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_content_stat (struct android_vnode *, struct stat *);
|
||
static int android_content_access (struct android_vnode *, int);
|
||
static int android_content_mkdir (struct android_vnode *, mode_t);
|
||
static int android_content_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_content_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_content_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with the content VFS node. */
|
||
|
||
static struct android_vops content_vfs_ops =
|
||
{
|
||
android_content_name,
|
||
android_content_open,
|
||
android_content_close,
|
||
android_content_unlink,
|
||
android_content_symlink,
|
||
android_content_rmdir,
|
||
android_content_rename,
|
||
android_content_stat,
|
||
android_content_access,
|
||
android_content_mkdir,
|
||
android_content_chmod,
|
||
android_content_readlink,
|
||
android_content_opendir,
|
||
};
|
||
|
||
/* Table of directories contained within a top-level vnode. */
|
||
|
||
static const char *content_directory_contents[] =
|
||
{
|
||
"storage", "by-authority", "by-authority-named",
|
||
};
|
||
|
||
/* Chain consisting of all open content directory streams. */
|
||
static struct android_content_vdir *all_content_vdirs;
|
||
|
||
static struct android_vnode *
|
||
android_content_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
char *remainder;
|
||
struct android_vnode *vp;
|
||
char *component_end;
|
||
struct android_special_vnode *special;
|
||
size_t i;
|
||
int api;
|
||
|
||
static struct android_special_vnode content_vnodes[] = {
|
||
{ "storage", 7, android_saf_root_initial, },
|
||
{ "by-authority", 12, android_authority_initial, },
|
||
{ "by-authority-named", 18, android_authority_initial_name, },
|
||
};
|
||
|
||
/* Canonicalize NAME. */
|
||
remainder = android_vfs_canonicalize_name (name, &length);
|
||
|
||
/* If remainder is set, it's a name relative to the root vnode. */
|
||
if (remainder)
|
||
goto parent_vnode;
|
||
|
||
/* If LENGTH is empty or NAME is a single directory separator,
|
||
return a copy of this vnode. */
|
||
|
||
if (length < 1 || (*name == '/' && length == 1))
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
return vp;
|
||
}
|
||
|
||
api = android_get_current_api_level ();
|
||
|
||
/* If NAME starts with a directory separator, move it past that. */
|
||
|
||
if (*name == '/')
|
||
name++, length -= 1;
|
||
|
||
/* Look for the first directory separator. */
|
||
component_end = strchr (name, '/');
|
||
|
||
/* If not there, use name + length. */
|
||
|
||
if (!component_end)
|
||
component_end = name + length;
|
||
else
|
||
/* Move past the separator character. */
|
||
component_end++;
|
||
|
||
/* Now, find out if the first component is a special vnode; if so,
|
||
call its root lookup function with the rest of NAME there. */
|
||
|
||
if (api < 19)
|
||
i = 3;
|
||
else if (api < 21)
|
||
i = 1;
|
||
else
|
||
i = 0;
|
||
|
||
for (; i < ARRAYELTS (content_vnodes); ++i)
|
||
{
|
||
special = &content_vnodes[i];
|
||
|
||
if (component_end - name == special->length
|
||
&& !memcmp (special->name, name, special->length))
|
||
return (*special->initial) (component_end,
|
||
length - special->length);
|
||
|
||
/* Detect the case where a special is named with a trailing
|
||
directory separator. */
|
||
|
||
if (component_end - name == special->length + 1
|
||
&& !memcmp (special->name, name, special->length)
|
||
&& name[special->length] == '/')
|
||
/* Make sure to include the directory separator. */
|
||
return (*special->initial) (component_end - 1,
|
||
length - special->length);
|
||
}
|
||
|
||
errno = ENOENT;
|
||
return NULL;
|
||
|
||
parent_vnode:
|
||
/* The parent of this vnode is always the root filesystem. */
|
||
vp = &root_vnode.vnode;
|
||
return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
|
||
}
|
||
|
||
static int
|
||
android_content_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd,
|
||
AAsset **asset)
|
||
{
|
||
/* Don't allow opening this special directory. */
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static void
|
||
android_content_close (struct android_vnode *vnode)
|
||
{
|
||
int save_errno;
|
||
|
||
save_errno = errno;
|
||
xfree (vnode);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_content_unlink (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_content_symlink (const char *target, struct android_vnode *vnode)
|
||
{
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_content_rmdir (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_content_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
if (src->type != dst->type)
|
||
{
|
||
/* If the types of both vnodes differ, complain that they're on
|
||
two different filesystems (which is correct from a abstract
|
||
viewpoint.) */
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
/* Otherwise, return ENOSYS. */
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_content_stat (struct android_vnode *vnode,
|
||
struct stat *statb)
|
||
{
|
||
memset (statb, 0, sizeof *statb);
|
||
|
||
statb->st_uid = getuid ();
|
||
statb->st_gid = getgid ();
|
||
statb->st_ino = 0;
|
||
statb->st_dev = -2;
|
||
statb->st_mode = S_IFDIR | S_IRUSR | S_IXUSR;
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_content_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
/* Validate MODE. */
|
||
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
/* Return EROFS if the caller is trying to check for write access to
|
||
this vnode. */
|
||
|
||
if (mode != F_OK && (mode & (W_OK | X_OK)))
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_content_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
errno = EEXIST;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_content_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
static ssize_t
|
||
android_content_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
static struct dirent *
|
||
android_content_readdir (struct android_vdir *vdir)
|
||
{
|
||
static struct dirent dirent;
|
||
struct android_content_vdir *dir;
|
||
const char *name;
|
||
|
||
dir = (struct android_content_vdir *) vdir;
|
||
|
||
/* There are no more files to be read. */
|
||
if (dir->next_name == (content_directory_contents
|
||
+ ARRAYELTS (content_directory_contents)))
|
||
return NULL;
|
||
|
||
/* Get the next child. */
|
||
name = *dir->next_name++;
|
||
|
||
/* Now, fill in the dirent with the name. */
|
||
memset (&dirent, 0, sizeof dirent);
|
||
dirent.d_ino = 0;
|
||
dirent.d_off = 0;
|
||
dirent.d_reclen = sizeof dirent;
|
||
dirent.d_type = DT_DIR;
|
||
strcpy (dirent.d_name, name);
|
||
return &dirent;
|
||
}
|
||
|
||
static void
|
||
android_content_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_content_vdir *dir, **next, *tem;
|
||
|
||
dir = (struct android_content_vdir *) vdir;
|
||
|
||
/* If the ``directory file descriptor'' has been opened, close
|
||
it. */
|
||
|
||
if (dir->fd != -1)
|
||
close (dir->fd);
|
||
|
||
/* Now unlink this directory. */
|
||
|
||
for (next = &all_content_vdirs; (tem = *next);)
|
||
{
|
||
if (tem == dir)
|
||
*next = dir->next;
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
xfree (dir);
|
||
}
|
||
|
||
static int
|
||
android_content_dirfd (struct android_vdir *vdir)
|
||
{
|
||
struct android_content_vdir *dir;
|
||
|
||
dir = (struct android_content_vdir *) vdir;
|
||
|
||
/* Since `android_content_opendir' tries to avoid opening a file
|
||
descriptor if readdir isn't called, dirfd can fail if open fails.
|
||
|
||
open sets errno to a set of errors different from what POSIX
|
||
stipulates for dirfd, but for ease of implementation the open
|
||
errors are used instead. */
|
||
|
||
if (dir->fd >= 0)
|
||
return dir->fd;
|
||
|
||
dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
|
||
return dir->fd;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_content_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_content_vdir *dir;
|
||
int api;
|
||
|
||
/* Allocate the virtual directory. */
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_content_readdir;
|
||
dir->vdir.closedir = android_content_closedir;
|
||
dir->vdir.dirfd = android_content_dirfd;
|
||
dir->fd = -1;
|
||
|
||
/* Fill in the directory contents. */
|
||
dir->next_name = content_directory_contents;
|
||
api = android_get_current_api_level ();
|
||
|
||
/* Android 4.4 and earlier don't support /content/storage. */
|
||
|
||
if (api < 21)
|
||
dir->next_name++;
|
||
|
||
/* Android 4.3 and earlier don't support /content/by-authority. */
|
||
|
||
if (api < 19)
|
||
dir->next_name += 2;
|
||
|
||
/* Link this stream onto the list of all content directory
|
||
streams. */
|
||
dir->next = all_content_vdirs;
|
||
all_content_vdirs = dir;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
/* Return the file name corresponding to DIRFD if it is a
|
||
``directory'' file descriptor returned by `android_content_dirfd'
|
||
or NULL otherwise. */
|
||
|
||
static char *
|
||
android_content_get_directory_name (int dirfd)
|
||
{
|
||
struct android_content_vdir *dir;
|
||
|
||
for (dir = all_content_vdirs; dir; dir = dir->next)
|
||
{
|
||
if (dir->fd == dirfd && dirfd != -1)
|
||
return (char *) "/content";
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* Find the vnode designated by the normalized NAME relative to the
|
||
root of the content file system. NAME may be modified, and must be
|
||
LENGTH bytes long, excluding its terminating NULL byte. */
|
||
|
||
static struct android_vnode *
|
||
android_content_initial (char *name, size_t length)
|
||
{
|
||
struct android_vnode temp;
|
||
|
||
/* Create a temporary vnode at the root of the asset file
|
||
system. */
|
||
|
||
temp.ops = &content_vfs_ops;
|
||
temp.type = ANDROID_VNODE_CONTENT;
|
||
temp.flags = 0;
|
||
|
||
/* Try to name this vnode. If NAME is empty, it will be duplicated
|
||
instead. */
|
||
return android_content_name (&temp, name, length);
|
||
}
|
||
|
||
|
||
|
||
#ifdef __clang__
|
||
#pragma clang diagnostic push
|
||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||
#else /* GNUC */
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
|
||
#endif /* __clang__ */
|
||
|
||
/* Content URI management functions. */
|
||
|
||
JNIEXPORT jstring JNICALL
|
||
NATIVE_NAME (displayNameHash) (JNIEnv *env, jobject object,
|
||
jbyteArray display_name)
|
||
{
|
||
char checksum[9], block[MD5_DIGEST_SIZE];
|
||
jbyte *data;
|
||
|
||
data = (*env)->GetByteArrayElements (env, display_name, NULL);
|
||
if (!data)
|
||
return NULL;
|
||
|
||
/* Hash the buffer. */
|
||
md5_buffer ((char *) data, (*env)->GetArrayLength (env, display_name),
|
||
block);
|
||
(*env)->ReleaseByteArrayElements (env, display_name, data, JNI_ABORT);
|
||
|
||
/* Generate the digest string. */
|
||
hexbuf_digest (checksum, (char *) block, 4);
|
||
checksum[8] = '\0';
|
||
return (*env)->NewStringUTF (env, checksum);
|
||
}
|
||
|
||
#ifdef __clang__
|
||
#pragma clang diagnostic pop
|
||
#else /* GNUC */
|
||
#pragma GCC diagnostic pop
|
||
#endif /* __clang__ */
|
||
|
||
/* Return the content URI corresponding to a `/content/by-authority'
|
||
file name, or NULL if it is invalid for some reason. FILENAME
|
||
should be relative to /content/by-authority, with no leading
|
||
directory separator character.
|
||
|
||
WITH_CHECKSUM should be true if FILENAME contains a display name and
|
||
a checksum for that display name. */
|
||
|
||
static char *
|
||
android_get_content_name (const char *filename, bool with_checksum)
|
||
{
|
||
char *fill, *buffer;
|
||
size_t length;
|
||
char checksum[9], new_checksum[9], block[MD5_DIGEST_SIZE];
|
||
const char *p2, *p1;
|
||
|
||
/* Make sure FILENAME isn't obviously invalid: it must contain an
|
||
authority name and a file name component. */
|
||
|
||
fill = strchr (filename, '/');
|
||
if (!fill || *(fill + 1) == '\0')
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* FILENAME must also not be a directory. Accessing content
|
||
provider directories is not supported by this interface. */
|
||
|
||
length = strlen (filename);
|
||
if (filename[length] == '/')
|
||
{
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
if (!with_checksum)
|
||
goto no_checksum;
|
||
|
||
/* Content file names hold two components providing a display name and
|
||
a short checksum that protects against files being opened under
|
||
display names besides those provided in the content file name at
|
||
the time of generation. */
|
||
|
||
p1 = strrchr (filename, '/'); /* Display name. */
|
||
p2 = memrchr (filename, '/', p1 - filename); /* Start of checksum. */
|
||
|
||
/* If the name be excessively short or the checksum of an invalid
|
||
length, return. */
|
||
if (!p2 || (p1 - p2) != 9)
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* Copy the checksum into CHECKSUM. */
|
||
memcpy (checksum, p2 + 1, 8);
|
||
new_checksum[8] = checksum[8] = '\0';
|
||
|
||
/* Hash this string and store 8 bytes of the resulting digest into
|
||
new_checksum. */
|
||
md5_buffer (p1 + 1, strlen (p1 + 1), block);
|
||
hexbuf_digest (new_checksum, (char *) block, 4);
|
||
|
||
/* Compare both checksums. */
|
||
if (strcmp (new_checksum, checksum))
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* Remove the checksum and file display name from the URI. */
|
||
length = p2 - filename;
|
||
|
||
no_checksum:
|
||
if (length > INT_MAX)
|
||
{
|
||
errno = ENOMEM;
|
||
return NULL;
|
||
}
|
||
|
||
/* Prefix FILENAME with content:// and return the buffer containing
|
||
that URI. */
|
||
buffer = xmalloc (sizeof "content://" + length + 1);
|
||
sprintf (buffer, "content://%.*s", (int) length, filename);
|
||
return buffer;
|
||
}
|
||
|
||
/* Return whether or not the specified URI is an accessible content
|
||
URI. MODE specifies what to check.
|
||
|
||
URI must be a string in the JVM's extended UTF-8 format. */
|
||
|
||
static bool
|
||
android_check_content_access (const char *uri, int mode)
|
||
{
|
||
jobject string;
|
||
jboolean rc, read, write;
|
||
jmethodID method;
|
||
|
||
string = (*android_java_env)->NewStringUTF (android_java_env, uri);
|
||
android_exception_check ();
|
||
|
||
/* Establish what is being checked. Checking for read access is
|
||
identical to checking if the file exists. */
|
||
|
||
read = (bool) (mode & R_OK || (mode == F_OK));
|
||
write = (bool) (mode & W_OK);
|
||
method = service_class.check_content_uri;
|
||
|
||
rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, string, read,
|
||
write);
|
||
android_exception_check_1 (string);
|
||
ANDROID_DELETE_LOCAL_REF (string);
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
/* Functions shared by authority and SAF nodes. */
|
||
|
||
/* Check for JNI exceptions, clear them, and set errno accordingly.
|
||
Also, free each of the N local references given as arguments if an
|
||
exception takes place.
|
||
|
||
Value is 1 if an exception has taken place, 0 otherwise.
|
||
|
||
If the exception thrown derives from FileNotFoundException, set
|
||
errno to ENOENT.
|
||
|
||
If the exception thrown derives from SecurityException, set errno
|
||
to EACCES.
|
||
|
||
If the exception thrown derives from OperationCanceledException,
|
||
set errno to EINTR.
|
||
|
||
If the exception thrown derives from UnsupportedOperationException,
|
||
set errno to ENOSYS.
|
||
|
||
If the exception thrown derives from OutOfMemoryException, call
|
||
`memory_full'.
|
||
|
||
If the exception thrown is anything else, set errno to EIO. */
|
||
|
||
static int
|
||
android_saf_exception_check (int n, ...)
|
||
{
|
||
jthrowable exception;
|
||
JNIEnv *env;
|
||
va_list ap;
|
||
int new_errno;
|
||
|
||
env = android_java_env;
|
||
va_start (ap, n);
|
||
|
||
/* First, check for an exception. */
|
||
|
||
if (!(*env)->ExceptionCheck (env))
|
||
{
|
||
/* No exception has taken place. Return 0. */
|
||
va_end (ap);
|
||
return 0;
|
||
}
|
||
|
||
/* Print the exception. */
|
||
(*env)->ExceptionDescribe (env);
|
||
|
||
exception = (*env)->ExceptionOccurred (env);
|
||
|
||
if (!exception)
|
||
/* JNI couldn't return a local reference to the exception. */
|
||
memory_full (0);
|
||
|
||
/* Clear the exception, making it safe to subsequently call other
|
||
JNI functions. */
|
||
(*env)->ExceptionClear (env);
|
||
|
||
/* Delete each of the N arguments. */
|
||
|
||
while (n > 0)
|
||
{
|
||
ANDROID_DELETE_LOCAL_REF (va_arg (ap, jobject));
|
||
n--;
|
||
}
|
||
|
||
/* Now set errno or signal memory_full as required. */
|
||
|
||
if ((*env)->IsInstanceOf (env, (jobject) exception,
|
||
file_not_found_exception))
|
||
new_errno = ENOENT;
|
||
else if ((*env)->IsInstanceOf (env, (jobject) exception,
|
||
security_exception))
|
||
new_errno = EACCES;
|
||
else if ((*env)->IsInstanceOf (env, (jobject) exception,
|
||
operation_canceled_exception))
|
||
new_errno = EINTR;
|
||
else if ((*env)->IsInstanceOf (env, (jobject) exception,
|
||
unsupported_operation_exception))
|
||
new_errno = ENOSYS;
|
||
else if ((*env)->IsInstanceOf (env, (jobject) exception,
|
||
out_of_memory_error))
|
||
{
|
||
ANDROID_DELETE_LOCAL_REF ((jobject) exception);
|
||
memory_full (0);
|
||
}
|
||
else
|
||
new_errno = EIO;
|
||
|
||
/* expression is still a local reference! */
|
||
ANDROID_DELETE_LOCAL_REF ((jobject) exception);
|
||
errno = new_errno;
|
||
va_end (ap);
|
||
return 1;
|
||
}
|
||
|
||
|
||
|
||
/* Content authority-based vnode implementation.
|
||
|
||
/content/by-authority is a simple vnode implementation that converts
|
||
components to content:// URIs.
|
||
|
||
It does not canonicalize file names by removing parent directory
|
||
separators, as these characters can appear in legitimate content
|
||
file names. */
|
||
|
||
struct android_authority_vnode
|
||
{
|
||
/* The vnode data itself. */
|
||
struct android_vnode vnode;
|
||
|
||
/* URI associated with this vnode, or NULL if this is the root of
|
||
the content authority tree. */
|
||
char *uri;
|
||
};
|
||
|
||
static struct android_vnode *android_authority_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_authority_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_authority_close (struct android_vnode *);
|
||
static int android_authority_unlink (struct android_vnode *);
|
||
static int android_authority_symlink (const char *, struct android_vnode *);
|
||
static int android_authority_rmdir (struct android_vnode *);
|
||
static int android_authority_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_authority_stat (struct android_vnode *, struct stat *);
|
||
static int android_authority_access (struct android_vnode *, int);
|
||
static int android_authority_mkdir (struct android_vnode *, mode_t);
|
||
static int android_authority_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_authority_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_authority_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with the content VFS node. */
|
||
|
||
static struct android_vops authority_vfs_ops =
|
||
{
|
||
android_authority_name,
|
||
android_authority_open,
|
||
android_authority_close,
|
||
android_authority_unlink,
|
||
android_authority_symlink,
|
||
android_authority_rmdir,
|
||
android_authority_rename,
|
||
android_authority_stat,
|
||
android_authority_access,
|
||
android_authority_mkdir,
|
||
android_authority_chmod,
|
||
android_authority_readlink,
|
||
android_authority_opendir,
|
||
};
|
||
|
||
static struct android_vnode *
|
||
android_authority_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
struct android_authority_vnode *vp;
|
||
char *uri_name;
|
||
|
||
if (!android_init_gui)
|
||
{
|
||
errno = EIO;
|
||
return NULL;
|
||
}
|
||
|
||
/* If NAME is empty or consists of a single directory separator
|
||
_and_ VP->uri is NULL, return a copy of VNODE. */
|
||
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
|
||
if (length < 1 || (*name == '/' && length == 1 && !vp->uri))
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
|
||
if (vp->uri)
|
||
vp->uri = xstrdup (vp->uri);
|
||
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* Else, if VP->uri is NULL, then it is the root of the by-authority
|
||
tree. If NAME starts with a directory separator character,
|
||
remove it. */
|
||
|
||
if (!vp->uri)
|
||
{
|
||
if (*name == '/')
|
||
name++, length -= 1;
|
||
|
||
/* If the provided URI is a directory, return NULL and set errno
|
||
to ENOTDIR. Content files are never directories. */
|
||
|
||
if (name[length - 1] == '/')
|
||
{
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
/* If the URI is not a valid JNI string, return immediately. This
|
||
should not be possible, since /content file names are encoded
|
||
into JNI strings at the naming stage; the check is performed
|
||
only out of an abundance of caution. */
|
||
|
||
if (android_verify_jni_string (name))
|
||
goto no_entry;
|
||
|
||
if (vp->vnode.type == ANDROID_VNODE_CONTENT_AUTHORITY_NAMED)
|
||
/* This indicates that the two trailing components of NAME
|
||
provide a checksum and a file display name, to be verified,
|
||
then excluded from the content URI. */
|
||
uri_name = android_get_content_name (name, true);
|
||
else
|
||
uri_name = android_get_content_name (name, false);
|
||
|
||
if (!uri_name)
|
||
goto error;
|
||
|
||
/* Now fill in the vnode. */
|
||
vp = xmalloc (sizeof *vp);
|
||
vp->vnode.ops = &authority_vfs_ops;
|
||
vp->vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
|
||
vp->vnode.flags = 0;
|
||
vp->uri = uri_name;
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* Content files can't have children. */
|
||
no_entry:
|
||
errno = ENOENT;
|
||
error:
|
||
return NULL;
|
||
}
|
||
|
||
static int
|
||
android_authority_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd_return,
|
||
AAsset **asset)
|
||
{
|
||
struct android_authority_vnode *vp;
|
||
jobject string;
|
||
int fd;
|
||
JNIEnv *env;
|
||
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
|
||
if (vp->uri == NULL)
|
||
{
|
||
/* This is the `by-authority' directory itself, which can't be
|
||
opened. */
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
/* Save the JNI environment within `env', to make wrapping
|
||
subsequent lines referencing CallNonvirtualIntMethod
|
||
feasible. */
|
||
env = android_java_env;
|
||
|
||
/* Allocate a JNI string to hold VP->uri. */
|
||
string = (*env)->NewStringUTF (env, vp->uri);
|
||
android_exception_check ();
|
||
|
||
/* Try to open the file descriptor. */
|
||
fd = (*env)->CallNonvirtualIntMethod (env, emacs_service,
|
||
service_class.class,
|
||
service_class.open_content_uri,
|
||
string,
|
||
(jboolean) ((mode & O_WRONLY
|
||
|| mode & O_RDWR)
|
||
!= 0),
|
||
(jboolean) !(mode & O_WRONLY),
|
||
(jboolean) ((mode & O_TRUNC)
|
||
!= 0));
|
||
if (android_saf_exception_check (1, string))
|
||
return -1;
|
||
ANDROID_DELETE_LOCAL_REF (string);
|
||
|
||
/* If fd is -1, just assume that the file does not exist,
|
||
and return -1 with errno set to ENOENT. */
|
||
|
||
if (fd == -1)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
if (mode & O_CLOEXEC)
|
||
android_close_on_exec (fd);
|
||
|
||
*fd_return = fd;
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
android_authority_close (struct android_vnode *vnode)
|
||
{
|
||
struct android_authority_vnode *vp;
|
||
int save_errno;
|
||
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
save_errno = errno;
|
||
xfree (vp->uri);
|
||
xfree (vp);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_authority_unlink (struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_authority_symlink (const char *target,
|
||
struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_authority_rmdir (struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_authority_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
if (src->type != dst->type)
|
||
{
|
||
/* If the types of both vnodes differ, complain that they're on
|
||
two different filesystems (which is correct from a abstract
|
||
viewpoint.) */
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
/* Otherwise, return ENOSYS. */
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_authority_stat (struct android_vnode *vnode,
|
||
struct stat *statb)
|
||
{
|
||
int rc, fd, save_errno;
|
||
struct android_authority_vnode *vp;
|
||
|
||
/* If this is a vnode representing `by-authority', return some
|
||
information about this directory. */
|
||
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
|
||
if (!vp->uri)
|
||
{
|
||
memset (statb, 0, sizeof *statb);
|
||
statb->st_uid = getuid ();
|
||
statb->st_gid = getgid ();
|
||
statb->st_ino = 0;
|
||
statb->st_dev = -3;
|
||
statb->st_mode = S_IFDIR | S_IRUSR;
|
||
return 0;
|
||
}
|
||
|
||
/* Try to open the file and call fstat. */
|
||
rc = (*vnode->ops->open) (vnode, O_RDONLY, 0, false, &fd, NULL);
|
||
|
||
if (rc < 0)
|
||
return -1;
|
||
|
||
/* If rc is 1, then an asset file descriptor has been returned.
|
||
This is impossible, so assert that it doesn't transpire. */
|
||
assert (rc != 1);
|
||
|
||
/* Now, try to stat the file. */
|
||
rc = fstat (fd, statb);
|
||
save_errno = errno;
|
||
|
||
/* Close the file descriptor. */
|
||
close (fd);
|
||
|
||
/* Restore errno. */
|
||
errno = save_errno;
|
||
return rc;
|
||
}
|
||
|
||
static int
|
||
android_authority_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
struct android_authority_vnode *vp;
|
||
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
|
||
/* Validate MODE. */
|
||
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
if (!vp->uri)
|
||
{
|
||
/* Return EACCES if the caller is trying to check for write
|
||
access to `by-authority'. */
|
||
|
||
if (mode != F_OK && (mode & (W_OK | X_OK)))
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
return (android_check_content_access (vp->uri, mode)
|
||
? 0 : -1);
|
||
}
|
||
|
||
static int
|
||
android_authority_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_authority_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
static ssize_t
|
||
android_authority_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_authority_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_authority_vnode *vp;
|
||
|
||
/* Forbid listing the `by-authority' directory. */
|
||
vp = (struct android_authority_vnode *) vnode;
|
||
errno = vp->uri ? ENOTDIR : EACCES;
|
||
return NULL;
|
||
}
|
||
|
||
/* Find the vnode designated by NAME relative to the root of the
|
||
by-authority directory.
|
||
|
||
If NAME is empty or a single leading separator character, return
|
||
a vnode representing the by-authority directory itself.
|
||
|
||
Otherwise, represent the remainder of NAME as a URI (without
|
||
normalizing it) and return a vnode corresponding to that.
|
||
|
||
Value may also be NULL with errno set if the designated vnode is
|
||
not available, such as when Android windowing has not been
|
||
initialized. */
|
||
|
||
static struct android_vnode *
|
||
android_authority_initial (char *name, size_t length)
|
||
{
|
||
struct android_authority_vnode temp;
|
||
|
||
temp.vnode.ops = &authority_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
|
||
temp.vnode.flags = 0;
|
||
temp.uri = NULL;
|
||
|
||
return android_authority_name (&temp.vnode, name, length);
|
||
}
|
||
|
||
/* Find the vnode designated by NAME relative to the root of the
|
||
by-authority-named directory.
|
||
|
||
If NAME is empty or a single leading separator character, return
|
||
a vnode representing the by-authority directory itself.
|
||
|
||
Otherwise, represent the remainder of NAME as a URI (without
|
||
normalizing it) and return a vnode corresponding to that.
|
||
|
||
Value may also be NULL with errno set if the designated vnode is
|
||
not available, such as when Android windowing has not been
|
||
initialized. */
|
||
|
||
static struct android_vnode *
|
||
android_authority_initial_name (char *name, size_t length)
|
||
{
|
||
struct android_authority_vnode temp;
|
||
|
||
temp.vnode.ops = &authority_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY_NAMED;
|
||
temp.vnode.flags = 0;
|
||
temp.uri = NULL;
|
||
|
||
return android_authority_name (&temp.vnode, name, length);
|
||
}
|
||
|
||
|
||
|
||
/* SAF ``root'' vnode implementation.
|
||
|
||
The Storage Access Framework is a system service that manages a
|
||
registry of document providers, which are a type of file system
|
||
server.
|
||
|
||
Normally, document providers can only provide individual files
|
||
through preestablished ``content URIs''. Android 5.0 and later add
|
||
to that ``tree URIs'', which designate entire file system trees.
|
||
|
||
Authorization to access document trees and content URIs is granted
|
||
transiently by default when an Intent is received, but Emacs can
|
||
also receive persistent authorization for a given document tree.
|
||
|
||
/content/storage returns a list of directories, each representing a
|
||
single authority providing at least one tree URI Emacs holds
|
||
persistent authorization for.
|
||
|
||
Each one of those directories then contains one document tree that
|
||
Emacs is authorized to access. */
|
||
|
||
struct android_saf_root_vnode
|
||
{
|
||
/* The vnode data. */
|
||
struct android_vnode vnode;
|
||
|
||
/* The name of the document authority this directory represents, or
|
||
NULL. */
|
||
char *authority;
|
||
};
|
||
|
||
struct android_saf_root_vdir
|
||
{
|
||
/* The directory stream function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The next directory stream in `all_saf_root_vdirs'. */
|
||
struct android_saf_root_vdir *next;
|
||
|
||
/* Array of strings, one for each directory to return. */
|
||
jobjectArray array;
|
||
|
||
/* Name of the authority this directory lists, or NULL. */
|
||
char *authority;
|
||
|
||
/* Length of that array, and the current within it. */
|
||
jsize length, i;
|
||
|
||
/* ``Directory'' file descriptor associated with this stream, or
|
||
-1. */
|
||
int fd;
|
||
};
|
||
|
||
static struct android_vnode *android_saf_root_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_saf_root_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_saf_root_close (struct android_vnode *);
|
||
static int android_saf_root_unlink (struct android_vnode *);
|
||
static int android_saf_root_symlink (const char *, struct android_vnode *);
|
||
static int android_saf_root_rmdir (struct android_vnode *);
|
||
static int android_saf_root_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_saf_root_stat (struct android_vnode *, struct stat *);
|
||
static int android_saf_root_access (struct android_vnode *, int);
|
||
static int android_saf_root_mkdir (struct android_vnode *, mode_t);
|
||
static int android_saf_root_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_saf_root_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_saf_root_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with the SAF root VFS node. */
|
||
|
||
static struct android_vops saf_root_vfs_ops =
|
||
{
|
||
android_saf_root_name,
|
||
android_saf_root_open,
|
||
android_saf_root_close,
|
||
android_saf_root_unlink,
|
||
android_saf_root_symlink,
|
||
android_saf_root_rmdir,
|
||
android_saf_root_rename,
|
||
android_saf_root_stat,
|
||
android_saf_root_access,
|
||
android_saf_root_mkdir,
|
||
android_saf_root_chmod,
|
||
android_saf_root_readlink,
|
||
android_saf_root_opendir,
|
||
};
|
||
|
||
/* Chain containing all SAF root directories. */
|
||
static struct android_saf_root_vdir *all_saf_root_vdirs;
|
||
|
||
/* Defined in the next page. */
|
||
static struct android_vnode *android_saf_tree_from_name (char *, const char *,
|
||
const char *);
|
||
|
||
/* Ascertain and return whether or not AUTHORITY designates a content
|
||
provider offering at least one directory tree accessible to
|
||
Emacs. */
|
||
|
||
static bool
|
||
android_saf_valid_authority_p (const char *authority)
|
||
{
|
||
jobject string;
|
||
jboolean valid;
|
||
jmethodID method;
|
||
|
||
/* Make certain AUTHORITY can actually be represented as a Java
|
||
string. */
|
||
|
||
if (android_verify_jni_string (authority))
|
||
return false;
|
||
|
||
/* Build a string containing AUTHORITY. */
|
||
|
||
string = (*android_java_env)->NewStringUTF (android_java_env,
|
||
authority);
|
||
android_exception_check ();
|
||
|
||
method = service_class.valid_authority;
|
||
valid
|
||
= (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, string);
|
||
android_exception_check_1 (string);
|
||
ANDROID_DELETE_LOCAL_REF (string);
|
||
return valid;
|
||
}
|
||
|
||
static struct android_vnode *
|
||
android_saf_root_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
char *remainder, *component_end;
|
||
struct android_saf_root_vnode *vp;
|
||
struct android_vnode *new;
|
||
char component[PATH_MAX];
|
||
|
||
/* Canonicalize NAME. */
|
||
remainder = android_vfs_canonicalize_name (name, &length);
|
||
|
||
/* If remainder is set, it's a name relative to the root vnode. */
|
||
if (remainder)
|
||
goto parent_vnode;
|
||
|
||
/* If LENGTH is empty or NAME is a single directory separator,
|
||
return a copy of this vnode. */
|
||
|
||
if (length < 1 || (*name == '/' && length == 1))
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
|
||
if (vp->authority)
|
||
vp->authority = xstrdup (vp->authority);
|
||
|
||
return &vp->vnode;
|
||
}
|
||
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
|
||
/* If NAME starts with a directory separator, move it past that. */
|
||
|
||
if (*name == '/')
|
||
name++, length -= 1;
|
||
|
||
/* Look for the first directory separator. */
|
||
component_end = strchr (name, '/');
|
||
|
||
/* If not there, use name + length. */
|
||
|
||
if (!component_end)
|
||
component_end = name + length;
|
||
|
||
if (component_end - name >= PATH_MAX)
|
||
{
|
||
errno = ENAMETOOLONG;
|
||
return NULL;
|
||
}
|
||
|
||
/* Copy the component over. */
|
||
memcpy (component, name, component_end - name);
|
||
component[component_end - name] = '\0';
|
||
|
||
/* Create a SAF document vnode for this tree if it represents an
|
||
authority. */
|
||
|
||
if (vp->authority)
|
||
return android_saf_tree_from_name (component_end, component,
|
||
vp->authority);
|
||
|
||
/* Create the vnode. */
|
||
vp = xmalloc (sizeof *vp);
|
||
vp->vnode.ops = &saf_root_vfs_ops;
|
||
vp->vnode.type = ANDROID_VNODE_SAF_ROOT;
|
||
vp->vnode.flags = 0;
|
||
vp->authority = xstrdup (component);
|
||
|
||
/* If there is more of this component to be named, name it through
|
||
the new vnode. */
|
||
|
||
if (component_end != name + length)
|
||
{
|
||
new = (*vp->vnode.ops->name) (&vp->vnode, component_end,
|
||
length - (component_end - name));
|
||
(*vp->vnode.ops->close) (&vp->vnode);
|
||
|
||
return new;
|
||
}
|
||
|
||
return &vp->vnode;
|
||
|
||
parent_vnode:
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
|
||
/* .. was encountered and the parent couldn't be found through
|
||
stripping off preceding components.
|
||
|
||
Find the parent vnode and name the rest of NAME starting from
|
||
there. */
|
||
|
||
if (!vp->authority)
|
||
/* Look this file name up relative to the root of the contents
|
||
directory. */
|
||
return android_content_initial (remainder, strlen (remainder));
|
||
else
|
||
/* Look this file name up relative to the root of the storage
|
||
directory. */
|
||
return android_saf_root_initial (remainder, strlen (remainder));
|
||
}
|
||
|
||
static int
|
||
android_saf_root_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd_return,
|
||
AAsset **asset)
|
||
{
|
||
/* /content/storage or one of its authority children cannot be
|
||
opened, as they are virtual directories. */
|
||
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static void
|
||
android_saf_root_close (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_root_vnode *vp;
|
||
int save_errno;
|
||
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
save_errno = errno;
|
||
xfree (vp->authority);
|
||
xfree (vp);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_unlink (struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_symlink (const char *target,
|
||
struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_rmdir (struct android_vnode *vnode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_stat (struct android_vnode *vnode,
|
||
struct stat *statb)
|
||
{
|
||
struct android_saf_root_vnode *vp;
|
||
|
||
/* Verify that the authority actually exists and return ENOENT
|
||
otherwise, lest `locate-dominating-file' & co call an operation
|
||
that doesn't require listing URIs under this authority, such as
|
||
access. */
|
||
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
|
||
if (vp->authority
|
||
&& !android_saf_valid_authority_p (vp->authority))
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
/* Make up some imaginary statistics for this vnode. */
|
||
|
||
memset (statb, 0, sizeof *statb);
|
||
statb->st_uid = getuid ();
|
||
statb->st_gid = getgid ();
|
||
statb->st_ino = 0;
|
||
statb->st_dev = -4;
|
||
statb->st_mode = S_IFDIR | S_IRUSR | S_IXUSR;
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
struct android_saf_root_vnode *vp;
|
||
|
||
/* Validate MODE. */
|
||
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
/* Now, don't allow writing or executing this directory. */
|
||
|
||
if (mode != F_OK && (mode & (W_OK | X_OK)))
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
/* Verify that the authority actually exists and return ENOENT
|
||
otherwise, lest `locate-dominating-file' & co call an operation
|
||
that doesn't require listing URIs under this authority, such as
|
||
access. */
|
||
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
|
||
if (vp->authority
|
||
&& !android_saf_valid_authority_p (vp->authority))
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_root_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
static ssize_t
|
||
android_saf_root_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
static struct dirent *
|
||
android_saf_root_readdir (struct android_vdir *vdir)
|
||
{
|
||
static struct dirent *dirent;
|
||
jobject string;
|
||
const char *chars;
|
||
size_t length, size;
|
||
struct android_saf_root_vdir *dir;
|
||
|
||
dir = (struct android_saf_root_vdir *) vdir;
|
||
|
||
if (dir->i == dir->length)
|
||
{
|
||
/* At the end of the stream. Free dirent and return NULL. */
|
||
|
||
xfree (dirent);
|
||
dirent = NULL;
|
||
return NULL;
|
||
}
|
||
|
||
/* Get this string. */
|
||
string = (*android_java_env)->GetObjectArrayElement (android_java_env,
|
||
dir->array, dir->i++);
|
||
android_exception_check ();
|
||
chars = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
(jstring) string,
|
||
NULL);
|
||
android_exception_check_nonnull ((void *) chars, string);
|
||
|
||
/* Figure out how large it is, and then resize dirent to fit--this
|
||
string is always ASCII. */
|
||
length = strlen (chars) + 1;
|
||
size = offsetof (struct dirent, d_name) + length;
|
||
dirent = xrealloc (dirent, size);
|
||
|
||
/* Clear dirent. */
|
||
memset (dirent, 0, size);
|
||
|
||
/* Fill in the generic directory information and copy the string
|
||
over. */
|
||
dirent->d_ino = 0;
|
||
dirent->d_off = 0;
|
||
dirent->d_reclen = size;
|
||
dirent->d_type = DT_DIR;
|
||
strcpy (dirent->d_name, chars);
|
||
|
||
/* Release the string data and the local reference to STRING. */
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
(jstring) string, chars);
|
||
ANDROID_DELETE_LOCAL_REF (string);
|
||
return dirent;
|
||
}
|
||
|
||
static void
|
||
android_saf_root_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_saf_root_vdir *dir, **next, *tem;
|
||
|
||
dir = (struct android_saf_root_vdir *) vdir;
|
||
|
||
/* If the ``directory file descriptor'' has been opened, close
|
||
it. */
|
||
|
||
if (dir->fd != -1)
|
||
close (dir->fd);
|
||
|
||
/* Delete the local reference to the file name array. */
|
||
ANDROID_DELETE_LOCAL_REF (dir->array);
|
||
|
||
/* Free the authority name if set. */
|
||
xfree (dir->authority);
|
||
|
||
/* Now unlink this directory. */
|
||
|
||
for (next = &all_saf_root_vdirs; (tem = *next);)
|
||
{
|
||
if (tem == dir)
|
||
*next = dir->next;
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
/* Free the directory itself. */
|
||
xfree (dir);
|
||
}
|
||
|
||
static int
|
||
android_saf_root_dirfd (struct android_vdir *vdir)
|
||
{
|
||
struct android_saf_root_vdir *dir;
|
||
|
||
dir = (struct android_saf_root_vdir *) vdir;
|
||
|
||
/* Since `android_saf_root_opendir' tries to avoid opening a file
|
||
descriptor if readdir isn't called, dirfd can fail if open fails.
|
||
|
||
open sets errno to a set of errors different from what POSIX
|
||
stipulates for dirfd, but for ease of implementation the open
|
||
errors are used instead. */
|
||
|
||
if (dir->fd >= 0)
|
||
return dir->fd;
|
||
|
||
dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
|
||
return dir->fd;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_saf_root_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_root_vnode *vp;
|
||
jobjectArray array;
|
||
jmethodID method;
|
||
jstring authority;
|
||
struct android_saf_root_vdir *dir;
|
||
size_t length;
|
||
|
||
vp = (struct android_saf_root_vnode *) vnode;
|
||
|
||
if (vp->authority)
|
||
{
|
||
/* Build a string containing the authority. */
|
||
length = strlen (vp->authority);
|
||
authority = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->authority);
|
||
android_exception_check ();
|
||
|
||
/* Acquire a list of every tree provided by this authority. */
|
||
|
||
method = service_class.get_document_trees;
|
||
array
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, authority);
|
||
android_exception_check_1 (authority);
|
||
ANDROID_DELETE_LOCAL_REF (authority);
|
||
|
||
/* Ascertain the length of the array. If it is empty or NULL,
|
||
return ENOENT. */
|
||
|
||
if (!array)
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
length = (*android_java_env)->GetArrayLength (android_java_env, array);
|
||
|
||
if (!length)
|
||
{
|
||
ANDROID_DELETE_LOCAL_REF (array);
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* Now allocate the directory stream. It will retain a local
|
||
reference to the array, and should thus only be used within the
|
||
same JNI local reference frame. */
|
||
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_saf_root_readdir;
|
||
dir->vdir.closedir = android_saf_root_closedir;
|
||
dir->vdir.dirfd = android_saf_root_dirfd;
|
||
dir->fd = -1;
|
||
dir->array = array;
|
||
dir->length = length;
|
||
dir->i = 0;
|
||
dir->authority = xstrdup (vp->authority);
|
||
|
||
/* Link this stream onto the list of all SAF root directory
|
||
streams. */
|
||
dir->next = all_saf_root_vdirs;
|
||
all_saf_root_vdirs = dir;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
/* Acquire a list of every document authority. */
|
||
|
||
method = service_class.get_document_authorities;
|
||
array = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method);
|
||
android_exception_check ();
|
||
|
||
if (!array)
|
||
emacs_abort ();
|
||
|
||
/* Now allocate the directory stream. It will retain a local
|
||
reference to the array, and should thus only be used within the
|
||
same JNI local reference frame. */
|
||
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_saf_root_readdir;
|
||
dir->vdir.closedir = android_saf_root_closedir;
|
||
dir->vdir.dirfd = android_saf_root_dirfd;
|
||
dir->fd = -1;
|
||
dir->array = array;
|
||
dir->length = (*android_java_env)->GetArrayLength (android_java_env,
|
||
array);
|
||
dir->i = 0;
|
||
dir->authority = NULL;
|
||
|
||
/* Link this stream onto the list of all SAF root directory
|
||
streams. */
|
||
dir->next = all_saf_root_vdirs;
|
||
all_saf_root_vdirs = dir;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
/* Find the vnode designated by NAME relative to the root of the
|
||
storage directory.
|
||
|
||
If NAME is empty or a single leading separator character, return a
|
||
vnode representing the storage directory itself.
|
||
|
||
If NAME actually resides in a parent directory, look for it within
|
||
the vnode representing the content directory. */
|
||
|
||
static struct android_vnode *
|
||
android_saf_root_initial (char *name, size_t length)
|
||
{
|
||
struct android_saf_root_vnode temp;
|
||
|
||
temp.vnode.ops = &saf_root_vfs_ops;
|
||
temp.vnode.type = ANDROID_VNODE_SAF_ROOT;
|
||
temp.vnode.flags = 0;
|
||
temp.authority = NULL;
|
||
|
||
return android_saf_root_name (&temp.vnode, name, length);
|
||
}
|
||
|
||
/* Return any open SAF root directory stream for which dirfd has
|
||
returned the file descriptor DIRFD. Return NULL otherwise. */
|
||
|
||
static struct android_saf_root_vdir *
|
||
android_saf_root_get_directory (int dirfd)
|
||
{
|
||
struct android_saf_root_vdir *dir;
|
||
|
||
for (dir = all_saf_root_vdirs; dir; dir = dir->next)
|
||
{
|
||
if (dir->fd == dirfd && dirfd != -1)
|
||
return dir;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
/* Functions common to both SAF directory and file nodes. */
|
||
|
||
/* Whether or not Emacs is within an operation running from the SAF
|
||
thread. */
|
||
static bool inside_saf_critical_section;
|
||
|
||
/* Return file status for the document designated by ID_NAME within
|
||
the document tree identified by URI_NAME.
|
||
|
||
If NO_CACHE, don't cache the resulting file status. Enable this
|
||
option if the file status is subject to imminent change.
|
||
|
||
If the file status is available, place it within *STATB and return
|
||
0. If not, return -1 and set errno to EPERM. */
|
||
|
||
static int
|
||
android_saf_stat (const char *uri_name, const char *id_name,
|
||
struct stat *statb, bool no_cache)
|
||
{
|
||
jmethodID method;
|
||
jstring uri, id;
|
||
jobject status;
|
||
jlong mode, size, mtim, *longs;
|
||
|
||
/* Now guarantee that it is safe to call functions which
|
||
synchronize with the SAF thread. */
|
||
|
||
if (inside_saf_critical_section)
|
||
{
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* Build strings for both URI and ID. */
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
|
||
android_exception_check ();
|
||
|
||
if (id_name)
|
||
{
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
id_name);
|
||
android_exception_check_1 (uri);
|
||
}
|
||
else
|
||
id = NULL;
|
||
|
||
/* Try to retrieve the file status. */
|
||
method = service_class.stat_document;
|
||
inside_saf_critical_section = true;
|
||
status
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
(jboolean) no_cache);
|
||
inside_saf_critical_section = false;
|
||
|
||
/* Check for exceptions and release unneeded local references. */
|
||
|
||
if (id)
|
||
{
|
||
if (android_saf_exception_check (2, uri, id))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
}
|
||
else if (android_saf_exception_check (1, uri))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
|
||
/* Check for failure. */
|
||
|
||
if (!status)
|
||
{
|
||
errno = EPERM;
|
||
return -1;
|
||
}
|
||
|
||
/* Read the file status from the array returned. */
|
||
|
||
longs = (*android_java_env)->GetLongArrayElements (android_java_env,
|
||
status, NULL);
|
||
android_exception_check_nonnull (longs, status);
|
||
mode = longs[0];
|
||
size = longs[1];
|
||
mtim = longs[2];
|
||
(*android_java_env)->ReleaseLongArrayElements (android_java_env, status,
|
||
longs, JNI_ABORT);
|
||
ANDROID_DELETE_LOCAL_REF (status);
|
||
|
||
/* Fill in STATB with this information. */
|
||
memset (statb, 0, sizeof *statb);
|
||
statb->st_size = MAX (0, MIN (TYPE_MAXIMUM (off_t), size));
|
||
statb->st_mode = mode;
|
||
statb->st_dev = -4;
|
||
#ifdef STAT_TIMESPEC
|
||
STAT_TIMESPEC (statb, st_mtim).tv_sec = mtim / 1000;
|
||
STAT_TIMESPEC (statb, st_mtim).tv_nsec = (mtim % 1000) * 1000000;
|
||
#else /* !STAT_TIMESPEC */
|
||
/* Headers supplied by the NDK r10b contain a `struct stat' without
|
||
POSIX fields for nano-second timestamps. */
|
||
statb->st_mtime = mtim / 1000;
|
||
statb->st_mtime_nsec = (mtim % 1000) * 1000000;
|
||
#endif /* STAT_TIMESPEC */
|
||
statb->st_uid = getuid ();
|
||
statb->st_gid = getgid ();
|
||
return 0;
|
||
}
|
||
|
||
/* Detect if Emacs has access to the document designated by the the
|
||
document ID ID_NAME within the tree URI_NAME. If ID_NAME is NULL,
|
||
use the document ID in URI_NAME itself.
|
||
|
||
If WRITABLE, also check that the file is writable, which is true
|
||
if it is either a directory or its flags contains
|
||
FLAG_SUPPORTS_WRITE.
|
||
|
||
Value is 0 if the file is accessible, and -1 with errno set
|
||
appropriately if not. */
|
||
|
||
static int
|
||
android_saf_access (const char *uri_name, const char *id_name,
|
||
bool writable)
|
||
{
|
||
jmethodID method;
|
||
jstring uri, id;
|
||
jint rc;
|
||
|
||
/* Now guarantee that it is safe to call functions which
|
||
synchronize with the SAF thread. */
|
||
|
||
if (inside_saf_critical_section)
|
||
{
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* Build strings for both URI and ID. */
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
|
||
android_exception_check ();
|
||
|
||
if (id_name)
|
||
{
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
id_name);
|
||
android_exception_check_1 (uri);
|
||
}
|
||
else
|
||
id = NULL;
|
||
|
||
/* Try to retrieve the file status. */
|
||
method = service_class.access_document;
|
||
inside_saf_critical_section = true;
|
||
rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
(jboolean) writable);
|
||
inside_saf_critical_section = false;
|
||
|
||
/* Check for exceptions and release unneeded local references. */
|
||
|
||
if (id)
|
||
{
|
||
if (android_saf_exception_check (2, uri, id))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
}
|
||
else if (android_saf_exception_check (1, uri))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
|
||
switch (rc)
|
||
{
|
||
case -1:
|
||
/* -1 means it doesn't exist. */
|
||
errno = ENOENT;
|
||
return -1;
|
||
|
||
case -2:
|
||
/* -2 means access has been denied. */
|
||
errno = EACCES;
|
||
return -1;
|
||
|
||
case -3:
|
||
/* -3 refers to an internal error. */
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* Return success. */
|
||
return 0;
|
||
}
|
||
|
||
/* Delete the document designated by DOC_ID within the tree identified
|
||
through the URI TREE. Return 0 if the document has been deleted,
|
||
set errno and return -1 upon failure.
|
||
|
||
DOC_NAME should be the name of the file itself, as a file name
|
||
whose constituent components lead to a document named DOC_ID. It
|
||
isn't used to search for a document ID, but is used to invalidate
|
||
the file cache. */
|
||
|
||
static int
|
||
android_saf_delete_document (const char *tree, const char *doc_id,
|
||
const char *doc_name)
|
||
{
|
||
jobject id, uri, name;
|
||
jmethodID method;
|
||
jint rc;
|
||
|
||
/* Build the strings holding the ID, URI and NAME. */
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
doc_id);
|
||
android_exception_check ();
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env,
|
||
tree);
|
||
android_exception_check_1 (id);
|
||
name = (*android_java_env)->NewStringUTF (android_java_env,
|
||
doc_name);
|
||
android_exception_check_2 (id, name);
|
||
|
||
/* Now, try to delete the document. */
|
||
method = service_class.delete_document;
|
||
rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
name);
|
||
|
||
if (android_saf_exception_check (3, id, uri, name))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
ANDROID_DELETE_LOCAL_REF (name);
|
||
|
||
if (rc)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Declared further below. */
|
||
static int android_document_id_from_name (const char *, const char *,
|
||
char **);
|
||
|
||
/* Rename the document designated by DOC_ID inside the directory tree
|
||
identified by URI, which should be within the directory by the name
|
||
of DIR, to NAME. If the document can't be renamed, return -1 and
|
||
set errno to a value describing the error. Return 0 if the rename
|
||
is successful.
|
||
|
||
Android permits the same document to appear in multiple
|
||
directories, but stores the display name inside the document
|
||
``inode'' itself instead of the directory entries that refer to it.
|
||
Because of this, this operation may cause other directory entries
|
||
outside DIR to be renamed. */
|
||
|
||
static int
|
||
android_saf_rename_document (const char *uri, const char *doc_id,
|
||
const char *dir, const char *name)
|
||
{
|
||
int rc;
|
||
jstring uri1, doc_id1, dir1, name1;
|
||
jmethodID method;
|
||
|
||
/* Now build the strings for the URI, document ID, directory name
|
||
and directory ID. */
|
||
|
||
uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
|
||
android_exception_check ();
|
||
doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, doc_id);
|
||
android_exception_check_1 (uri1);
|
||
dir1 = (*android_java_env)->NewStringUTF (android_java_env, dir);
|
||
android_exception_check_2 (doc_id1, uri1);
|
||
name1 = (*android_java_env)->NewStringUTF (android_java_env, name);
|
||
android_exception_check_3 (dir1, doc_id1, uri1);
|
||
|
||
method = service_class.rename_document;
|
||
rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri1, doc_id1,
|
||
dir1, name1);
|
||
|
||
/* Check for exceptions. */
|
||
|
||
if (android_saf_exception_check (4, uri1, doc_id1, dir1, name1))
|
||
{
|
||
/* Substitute EXDEV for ENOSYS, so callers fall back on
|
||
delete-then-copy. */
|
||
|
||
if (errno == ENOSYS)
|
||
errno = EXDEV;
|
||
|
||
return -1;
|
||
}
|
||
|
||
/* Delete unused local references. */
|
||
ANDROID_DELETE_LOCAL_REF (uri1);
|
||
ANDROID_DELETE_LOCAL_REF (doc_id1);
|
||
ANDROID_DELETE_LOCAL_REF (dir1);
|
||
ANDROID_DELETE_LOCAL_REF (name1);
|
||
|
||
/* Then check for errors handled within the Java code. */
|
||
|
||
if (rc == -1)
|
||
{
|
||
/* UnsupportedOperationException. Trick the caller into falling
|
||
back on delete-then-copy code. */
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Move the document designated by *DOC_ID from the directory under
|
||
DIR_NAME to the directory designated by DST_ID. All three
|
||
directories are located within the tree identified by the given
|
||
URI.
|
||
|
||
If the document's ID changes as a result of the movement, free
|
||
*DOC_ID and store the new document ID within.
|
||
|
||
Value is 0 upon success, -1 otherwise with errno set. */
|
||
|
||
static int
|
||
android_saf_move_document (const char *uri, char **doc_id,
|
||
const char *dir_name, const char *dst_id)
|
||
{
|
||
char *src_id, *id;
|
||
jobject uri1, doc_id1, dir_name1, dst_id1, src_id1;
|
||
jstring result;
|
||
jmethodID method;
|
||
int rc;
|
||
const char *new_id;
|
||
|
||
/* Obtain the name of the source directory. */
|
||
src_id = NULL;
|
||
rc = android_document_id_from_name (uri, dir_name, &src_id);
|
||
|
||
if (rc != 1)
|
||
{
|
||
/* This file is either not a directory or nonexistent. */
|
||
xfree (src_id);
|
||
|
||
switch (rc)
|
||
{
|
||
case 0:
|
||
errno = ENOTDIR;
|
||
return -1;
|
||
|
||
case -1:
|
||
case -2:
|
||
errno = ENOENT;
|
||
return -1;
|
||
|
||
default:
|
||
emacs_abort ();
|
||
}
|
||
}
|
||
|
||
/* Build Java strings for all five arguments. */
|
||
id = *doc_id;
|
||
uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
|
||
android_exception_check ();
|
||
doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, id);
|
||
android_exception_check_1 (uri1);
|
||
dir_name1 = (*android_java_env)->NewStringUTF (android_java_env, dir_name);
|
||
android_exception_check_2 (doc_id1, uri1);
|
||
dst_id1 = (*android_java_env)->NewStringUTF (android_java_env, dst_id);
|
||
android_exception_check_3 (dir_name1, doc_id1, uri1);
|
||
src_id1 = (*android_java_env)->NewStringUTF (android_java_env, src_id);
|
||
xfree (src_id);
|
||
android_exception_check_4 (dst_id1, dir_name1, doc_id1, uri1);
|
||
|
||
/* Do the rename. */
|
||
method = service_class.move_document;
|
||
result
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri1,
|
||
doc_id1, dir_name1,
|
||
dst_id1, src_id1);
|
||
if (android_saf_exception_check (5, src_id1, dst_id1, dir_name1,
|
||
doc_id1, uri1))
|
||
{
|
||
/* Substitute EXDEV for ENOSYS, so callers fall back on
|
||
delete-then-copy. */
|
||
|
||
if (errno == ENOSYS)
|
||
errno = EXDEV;
|
||
|
||
return -1;
|
||
}
|
||
|
||
/* Delete unused local references. */
|
||
ANDROID_DELETE_LOCAL_REF (src_id1);
|
||
ANDROID_DELETE_LOCAL_REF (dst_id1);
|
||
ANDROID_DELETE_LOCAL_REF (dir_name1);
|
||
ANDROID_DELETE_LOCAL_REF (doc_id1);
|
||
ANDROID_DELETE_LOCAL_REF (uri1);
|
||
|
||
if (result)
|
||
{
|
||
/* The document ID changed. Free id and replace *DOC_ID with
|
||
the new ID. */
|
||
xfree (id);
|
||
new_id = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
result, NULL);
|
||
android_exception_check_nonnull ((void *) new_id, result);
|
||
*doc_id = xstrdup (new_id);
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env, result,
|
||
new_id);
|
||
ANDROID_DELETE_LOCAL_REF (result);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* SAF directory vnode. A file within a SAF directory tree is
|
||
identified by the URI of the directory tree itself, an opaque
|
||
``file identifier'' value, and a display name. This information is
|
||
recorded in each vnode representing either a directory or a file
|
||
itself. */
|
||
|
||
struct android_saf_tree_vnode
|
||
{
|
||
/* The vnode data itself. */
|
||
struct android_vnode vnode;
|
||
|
||
/* The URI of the directory tree represented. This is Java string
|
||
data in ``modified UTF format'', which is essentially a modified
|
||
UTF-8 format capable of storing NULL bytes while also utilizing
|
||
NULL termination. */
|
||
const char *tree_uri;
|
||
|
||
/* The ID of the document tree designated by TREE_URI. */
|
||
char *tree_id;
|
||
|
||
/* The document ID of the directory represented, or NULL if this is
|
||
the root directory of the tree. Since file and new vnodes don't
|
||
represent the root directory, this field is always set in
|
||
them. */
|
||
char *document_id;
|
||
|
||
/* The file name of this tree vnode. This is a ``path'' to the
|
||
file, where each directory component consists of the display name
|
||
of a directory leading up to a file within, terminated with a
|
||
directory separator character. */
|
||
char *name;
|
||
};
|
||
|
||
struct android_saf_tree_vdir
|
||
{
|
||
/* The virtual directory stream function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The next directory in `all_saf_tree_vdirs'. */
|
||
struct android_saf_tree_vdir *next;
|
||
|
||
/* Name of this directory relative to the root file system. */
|
||
char *name;
|
||
|
||
/* Local reference to the cursor representing the directory
|
||
stream. */
|
||
jobject cursor;
|
||
|
||
/* The ``directory'' file descriptor used to identify this directory
|
||
stream, or -1. */
|
||
int fd;
|
||
};
|
||
|
||
static struct android_vnode *android_saf_tree_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_saf_tree_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static void android_saf_tree_close (struct android_vnode *);
|
||
static int android_saf_tree_unlink (struct android_vnode *);
|
||
static int android_saf_tree_symlink (const char *, struct android_vnode *);
|
||
static int android_saf_tree_rmdir (struct android_vnode *);
|
||
static int android_saf_tree_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_saf_tree_stat (struct android_vnode *, struct stat *);
|
||
static int android_saf_tree_access (struct android_vnode *, int);
|
||
static int android_saf_tree_mkdir (struct android_vnode *, mode_t);
|
||
static int android_saf_tree_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_saf_tree_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_saf_tree_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with SAF tree VFS nodes. */
|
||
|
||
static struct android_vops saf_tree_vfs_ops =
|
||
{
|
||
android_saf_tree_name,
|
||
android_saf_tree_open,
|
||
android_saf_tree_close,
|
||
android_saf_tree_unlink,
|
||
android_saf_tree_symlink,
|
||
android_saf_tree_rmdir,
|
||
android_saf_tree_rename,
|
||
android_saf_tree_stat,
|
||
android_saf_tree_access,
|
||
android_saf_tree_mkdir,
|
||
android_saf_tree_chmod,
|
||
android_saf_tree_readlink,
|
||
android_saf_tree_opendir,
|
||
};
|
||
|
||
/* Vector of VFS operations associated with SAF file VFS nodes.
|
||
Defined later in the next page. */
|
||
static struct android_vops saf_file_vfs_ops;
|
||
|
||
/* Vector of VFS operations associated with SAF ``new'' VFS nodes.
|
||
Defined two pages below. */
|
||
static struct android_vops saf_new_vfs_ops;
|
||
|
||
/* Chain of all open SAF directory streams. */
|
||
static struct android_saf_tree_vdir *all_saf_tree_vdirs;
|
||
|
||
/* Find the document ID of the file within TREE_URI designated by
|
||
NAME.
|
||
|
||
NAME is a ``file name'' comprised of the display names of
|
||
individual files. Each constituent component prior to the last
|
||
must name a directory file within TREE_URI.
|
||
|
||
If NAME is not correct for the Java ``modified UTF-8'' coding
|
||
system, return -1 and set errno to ENOENT.
|
||
|
||
Upon success, return 0 or 1 (contingent upon whether or not the
|
||
last component within NAME is a directory) and place the document
|
||
ID of the named file in ID.
|
||
|
||
If the designated file doesn't exist, but the penultimate component
|
||
within NAME does and is also a directory, return -2 and place the
|
||
document ID of that directory within *ID.
|
||
|
||
If the designated file can't be located, return -1 and set errno
|
||
accordingly. The reasons for which a file can't be located are not
|
||
all immediately obvious: quitting, for example, can cause document
|
||
ID lookup to be canceled. */
|
||
|
||
static int
|
||
android_document_id_from_name (const char *tree_uri, const char *name,
|
||
char **id)
|
||
{
|
||
jobjectArray result;
|
||
jstring uri;
|
||
jbyteArray java_name;
|
||
jint rc;
|
||
jmethodID method;
|
||
const char *doc_id;
|
||
|
||
/* Verify the format of NAME. Don't allow creating files that
|
||
contain characters that can't be encoded in Java. */
|
||
|
||
if (android_verify_jni_string (name))
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
/* Now guarantee that it is safe to call
|
||
`document_id_from_name'. */
|
||
|
||
if (inside_saf_critical_section)
|
||
{
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* First, create the array that will hold the result. */
|
||
result = (*android_java_env)->NewObjectArray (android_java_env, 1,
|
||
java_string_class,
|
||
NULL);
|
||
android_exception_check ();
|
||
|
||
/* Next, create the string for the tree URI and name. */
|
||
java_name = (*android_java_env)->NewStringUTF (android_java_env,
|
||
name);
|
||
android_exception_check_1 (result);
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env, tree_uri);
|
||
android_exception_check_2 (result, java_name);
|
||
|
||
/* Now, call documentIdFromName. This will synchronize with the SAF
|
||
thread, so make sure reentrant calls don't happen. */
|
||
method = service_class.document_id_from_name;
|
||
inside_saf_critical_section = true;
|
||
rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method,
|
||
uri, java_name,
|
||
result);
|
||
inside_saf_critical_section = false;
|
||
|
||
if (android_saf_exception_check (3, result, uri, java_name))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
ANDROID_DELETE_LOCAL_REF (java_name);
|
||
|
||
/* If rc indicates failure, don't try to copy from result. */
|
||
|
||
if (rc == -1)
|
||
{
|
||
ANDROID_DELETE_LOCAL_REF (result);
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
eassert (rc == -2 || rc >= 0);
|
||
|
||
/* Otherwise, obtain the contents of the string returned in Java
|
||
``UTF-8'' encoding. */
|
||
uri = (*android_java_env)->GetObjectArrayElement (android_java_env,
|
||
result, 0);
|
||
android_exception_check_nonnull (uri, result);
|
||
ANDROID_DELETE_LOCAL_REF (result);
|
||
|
||
doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
uri, NULL);
|
||
android_exception_check_nonnull ((void *) doc_id, uri);
|
||
|
||
/* Make *ID its copy. */
|
||
*id = xstrdup (doc_id);
|
||
|
||
/* And release it. */
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
(jstring) uri, doc_id);
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
return rc;
|
||
}
|
||
|
||
static struct android_vnode *
|
||
android_saf_tree_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
char *remainder;
|
||
int rc;
|
||
struct android_saf_tree_vnode *vp, *new;
|
||
size_t vp_length;
|
||
char *filename, *fill, *doc_id, *end;
|
||
struct android_saf_root_vnode root;
|
||
struct android_saf_tree_vnode tree;
|
||
|
||
/* Canonicalize NAME. */
|
||
remainder = android_vfs_canonicalize_name (name, &length);
|
||
|
||
/* If remainder is set, it's a name relative to the root vnode. */
|
||
if (remainder)
|
||
goto parent_vnode;
|
||
|
||
/* If LENGTH is empty or NAME is a single directory separator,
|
||
return a copy of this vnode. */
|
||
|
||
if (length < 1 || (*name == '/' && length == 1))
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
|
||
/* Duplicate the information contained within VNODE. */
|
||
|
||
vp->tree_uri = xstrdup (vp->tree_uri);
|
||
vp->tree_id = xstrdup (vp->tree_id);
|
||
vp->name = xstrdup (vp->name);
|
||
|
||
if (vp->document_id)
|
||
vp->document_id = xstrdup (vp->name);
|
||
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* Now, search for the document ID of the file designated by NAME
|
||
relative to this vnode. */
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
vp_length = strlen (vp->name);
|
||
|
||
/* If NAME starts with a directory separator, move it past that. */
|
||
|
||
if (*name == '/')
|
||
name++, length -= 1;
|
||
|
||
/* Concatenate VP->name with NAME. Leave one byte at the end for an
|
||
extra trailing directory separator. */
|
||
|
||
filename = xmalloc (vp_length + length + 2);
|
||
fill = stpcpy (filename, vp->name);
|
||
fill = stpcpy (fill, name);
|
||
|
||
/* And search for a document ID in the result. */
|
||
rc = android_document_id_from_name (vp->tree_uri, name,
|
||
&doc_id);
|
||
|
||
if (rc < 0)
|
||
{
|
||
if (rc == -2)
|
||
{
|
||
/* This is a vnode representing a nonexistent file in a real
|
||
directory, so create a vnode whose sole use is to create
|
||
the file. */
|
||
|
||
new = xmalloc (sizeof *new);
|
||
new->vnode.ops = &saf_new_vfs_ops;
|
||
new->vnode.type = ANDROID_VNODE_SAF_NEW;
|
||
new->vnode.flags = 0;
|
||
|
||
/* Here, doc_id is actually the ID of the penultimate
|
||
component in NAME. */
|
||
|
||
new->document_id = doc_id;
|
||
new->tree_uri = xstrdup (vp->tree_uri);
|
||
new->tree_id = xstrdup (vp->tree_id);
|
||
new->name = filename;
|
||
return &new->vnode;
|
||
}
|
||
|
||
/* The document ID can't be found. */
|
||
xfree (filename);
|
||
return NULL;
|
||
}
|
||
|
||
if (!rc)
|
||
{
|
||
/* rc set to 0 means that NAME is a regular file. Detect if
|
||
NAME is supposed to be a directory; if it is, set errno to
|
||
ENODIR. */
|
||
|
||
if (name[length - 1] == '/')
|
||
{
|
||
xfree (filename);
|
||
xfree (doc_id);
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/* So this is either a directory or really a file. Fortunately,
|
||
directory and file vnodes share everything in common except for a
|
||
few file operations, so create a new directory vnode with the new
|
||
file name and return it. */
|
||
|
||
new = xmalloc (sizeof *new);
|
||
new->vnode.ops = (rc ? &saf_tree_vfs_ops
|
||
: &saf_file_vfs_ops);
|
||
new->vnode.type = (rc ? ANDROID_VNODE_SAF_TREE
|
||
: ANDROID_VNODE_SAF_FILE);
|
||
new->vnode.flags = 0;
|
||
|
||
if (rc)
|
||
{
|
||
/* If fill[-1] is not a directory separator character, append
|
||
one to the end of filename. */
|
||
|
||
if (fill[-1] != '/')
|
||
{
|
||
*fill++ = '/';
|
||
*fill = '\0';
|
||
}
|
||
}
|
||
|
||
new->document_id = doc_id;
|
||
new->tree_uri = xstrdup (vp->tree_uri);
|
||
new->tree_id = xstrdup (vp->tree_id);
|
||
new->name = filename;
|
||
return &new->vnode;
|
||
|
||
parent_vnode:
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
/* .. was encountered and the parent couldn't be found through
|
||
stripping off preceding components.
|
||
|
||
Find the parent vnode and name the rest of NAME starting from
|
||
there. */
|
||
|
||
if (!vp->document_id)
|
||
{
|
||
/* VP->document_id is NULL, meaning this is the root of this
|
||
directory tree. The parent vnode is an SAF root vnode with
|
||
VP->tree_uri's authority. */
|
||
|
||
root.vnode.ops = &saf_root_vfs_ops;
|
||
root.vnode.type = ANDROID_VNODE_SAF_ROOT;
|
||
root.vnode.flags = 0;
|
||
|
||
/* Derive the authority from the URI. */
|
||
|
||
fill = (char *) vp->tree_uri;
|
||
|
||
if (strncmp (fill, "content://", 10))
|
||
emacs_abort ();
|
||
|
||
/* Skip the content header. */
|
||
fill += sizeof "content://" - 1;
|
||
|
||
/* The authority segment of the URI is between here and the
|
||
next slash. */
|
||
|
||
end = strchr (fill, '/');
|
||
|
||
if (!end)
|
||
emacs_abort ();
|
||
|
||
root.authority = xmalloc (end - fill + 1);
|
||
memcpy (root.authority, fill, end - fill);
|
||
root.authority[end - fill] = '\0';
|
||
|
||
/* Now search using this vnode. */
|
||
vnode = (*root.vnode.ops->name) (&root.vnode, remainder,
|
||
strlen (remainder));
|
||
xfree (root.authority);
|
||
return vnode;
|
||
}
|
||
|
||
/* Otherwise, strip off the last directory component. */
|
||
|
||
fill = strrchr (vp->name, '/');
|
||
if (!fill)
|
||
emacs_abort ();
|
||
|
||
/* Create a new vnode at the top of the directory tree, and search
|
||
for remainder from there. */
|
||
|
||
tree.vnode.ops = &saf_tree_vfs_ops;
|
||
tree.vnode.type = ANDROID_VNODE_SAF_TREE;
|
||
tree.vnode.flags = 0;
|
||
tree.document_id = NULL;
|
||
tree.name = (char *) "/";
|
||
tree.tree_uri = vp->tree_uri;
|
||
tree.tree_id = vp->tree_id;
|
||
|
||
length = strlen (remainder + (*remainder == '/'));
|
||
filename = xmalloc (fill - vp->name + length + 2);
|
||
fill = mempcpy (filename, vp->name,
|
||
/* Include the separator character (*FILL) within
|
||
this copy. */
|
||
fill - vp->name + 1);
|
||
/* Skip a leading separator in REMAINDER. */
|
||
strcpy (fill, remainder + (*remainder == '/'));
|
||
|
||
/* Use this filename to find a vnode relative to the start of this
|
||
tree. */
|
||
|
||
vnode = android_saf_tree_name (&tree.vnode, filename,
|
||
strlen (filename));
|
||
xfree (filename);
|
||
return vnode;
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd,
|
||
AAsset **asset)
|
||
{
|
||
/* Don't allow opening this special directory. */
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
static void
|
||
android_saf_tree_close (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_tree_vnode *vp;
|
||
int save_errno;
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
save_errno = errno;
|
||
xfree ((void *) vp->tree_uri);
|
||
xfree (vp->tree_id);
|
||
xfree (vp->name);
|
||
xfree (vp->document_id);
|
||
xfree (vp);
|
||
errno = save_errno;
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_unlink (struct android_vnode *vnode)
|
||
{
|
||
errno = EISDIR;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_symlink (const char *target, struct android_vnode *vnode)
|
||
{
|
||
errno = EPERM;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_rmdir (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_tree_vnode *vp;
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
/* Don't allow deleting the root directory. */
|
||
|
||
if (!vp->document_id)
|
||
{
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
return android_saf_delete_document (vp->tree_uri,
|
||
vp->document_id,
|
||
vp->name);
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
char *last, *dst_last;
|
||
struct android_saf_tree_vnode *vp, *vdst;
|
||
char path[EMACS_PATH_MAX], path1[EMACS_PATH_MAX];
|
||
char *fill, *dst_id;
|
||
int rc;
|
||
|
||
/* If dst isn't a tree, file or new vnode, return EXDEV. */
|
||
|
||
if (dst->type != ANDROID_VNODE_SAF_TREE
|
||
&& dst->type != ANDROID_VNODE_SAF_FILE
|
||
&& dst->type != ANDROID_VNODE_SAF_NEW)
|
||
{
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
vp = (struct android_saf_tree_vnode *) src;
|
||
vdst = (struct android_saf_tree_vnode *) dst;
|
||
|
||
/* if vp and vdst refer to different tree URIs, return EXDEV. */
|
||
|
||
if (strcmp (vp->tree_uri, vdst->tree_uri))
|
||
{
|
||
errno = EXDEV;
|
||
return -1;
|
||
}
|
||
|
||
/* If `keep_existing' and the destination vnode designates an
|
||
existing file, return EEXIST. */
|
||
|
||
if (keep_existing && dst->type != ANDROID_VNODE_SAF_NEW)
|
||
{
|
||
errno = EEXIST;
|
||
return -1;
|
||
}
|
||
|
||
/* Unix `rename' maps to two Android content provider operations.
|
||
The first case is a simple rename, where src and dst are both
|
||
located within the same directory. Compare the file names of
|
||
both up to the component before the last. */
|
||
|
||
last = strrchr (vp->name, '/');
|
||
eassert (last != NULL);
|
||
|
||
if (last[1] == '\0')
|
||
{
|
||
if (last == vp->name)
|
||
{
|
||
/* This means the caller is trying to rename the root
|
||
directory of the tree. */
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
/* The name is terminated by a trailing directory separator.
|
||
Search backwards for the preceding directory separator. */
|
||
last = memrchr (vp->name, '/', last - vp->name);
|
||
eassert (last != NULL);
|
||
}
|
||
|
||
/* Find the end of the second-to-last component in vdst's name. */
|
||
|
||
dst_last = strrchr (vdst->name, '/');
|
||
eassert (dst_last != NULL);
|
||
|
||
if (dst_last[1] == '\0')
|
||
{
|
||
if (dst_last == vdst->name)
|
||
{
|
||
/* Forbid overwriting the root of the tree either. */
|
||
errno = EROFS;
|
||
return -1;
|
||
}
|
||
|
||
dst_last = memrchr (vdst->name, '/', dst_last - vdst->name);
|
||
eassert (dst_last != NULL);
|
||
}
|
||
|
||
if (dst_last - vdst->name != last - vp->name
|
||
|| memcmp (vp->name, vdst->name, last - vp->name))
|
||
{
|
||
/* The second case is where the file must be moved from one
|
||
directory to the other, and possibly then recreated under a
|
||
new name. */
|
||
|
||
/* The names of the source and destination directories will have
|
||
to be copied to path. */
|
||
|
||
if (last - vp->name >= EMACS_PATH_MAX
|
||
|| dst_last - vdst->name >= EMACS_PATH_MAX)
|
||
{
|
||
errno = ENAMETOOLONG;
|
||
return -1;
|
||
}
|
||
|
||
fill = mempcpy (path, vp->name, last - vp->name);
|
||
*fill = '\0';
|
||
|
||
/* If vdst doesn't already exist, its document_id field is
|
||
already the name of its parent directory. */
|
||
|
||
if (dst->type == ANDROID_VNODE_SAF_NEW)
|
||
{
|
||
/* First, move the document. This will update
|
||
VP->document_id if it changes. */
|
||
|
||
if (android_saf_move_document (vp->tree_uri,
|
||
&vp->document_id,
|
||
path,
|
||
vdst->document_id))
|
||
return -1;
|
||
|
||
fill = mempcpy (path, vdst->name, dst_last - vdst->name);
|
||
*fill = '\0';
|
||
|
||
/* Next, rename the document, if its display name differs
|
||
from that of the source. */
|
||
|
||
if (strcmp (dst_last + 1, last + 1)
|
||
/* By now vp->document_id is already in the destination
|
||
directory. */
|
||
&& android_saf_rename_document (vp->tree_uri,
|
||
vp->document_id,
|
||
path,
|
||
dst_last + 1))
|
||
return -1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Retrieve the ID designating the destination document's parent
|
||
directory. */
|
||
|
||
fill = mempcpy (path1, vdst->name, dst_last - vdst->name);
|
||
*fill = '\0';
|
||
|
||
rc = android_document_id_from_name (vp->tree_uri,
|
||
path1, &dst_id);
|
||
|
||
if (rc != 1)
|
||
{
|
||
/* This file is either not a directory or nonexistent. */
|
||
|
||
switch (rc)
|
||
{
|
||
case 0:
|
||
errno = ENOTDIR;
|
||
goto error;
|
||
|
||
case -1:
|
||
/* dst_id is not set here, as the penultimate component
|
||
also couldn't be located. */
|
||
errno = ENOENT;
|
||
return -1;
|
||
|
||
case -2:
|
||
errno = ENOENT;
|
||
goto error;
|
||
|
||
default:
|
||
emacs_abort ();
|
||
}
|
||
}
|
||
|
||
/* vdst already exists, so it needs to be deleted first. */
|
||
|
||
if (android_saf_delete_document (vdst->tree_uri,
|
||
vdst->document_id,
|
||
vdst->name))
|
||
goto error;
|
||
|
||
/* First, move the document. This will update
|
||
VP->document_id if it changes. */
|
||
|
||
if (android_saf_move_document (vp->tree_uri,
|
||
&vp->document_id,
|
||
path, dst_id))
|
||
goto error;
|
||
|
||
/* Next, rename the document, if its display name differs from
|
||
that of the source. */
|
||
|
||
if (strcmp (dst_last + 1, last + 1)
|
||
/* By now vp->document_id is already in the destination
|
||
directory. */
|
||
&& android_saf_rename_document (vp->tree_uri,
|
||
vp->document_id,
|
||
path1,
|
||
dst_last + 1))
|
||
goto error;
|
||
|
||
xfree (dst_id);
|
||
return 0;
|
||
|
||
error:
|
||
xfree (dst_id);
|
||
return 1;
|
||
}
|
||
|
||
/* Otherwise, do this simple rename. The name of the parent
|
||
directory is required, as it provides the directory whose entries
|
||
will be modified. */
|
||
|
||
if (last - vp->name >= EMACS_PATH_MAX)
|
||
{
|
||
errno = ENAMETOOLONG;
|
||
return -1;
|
||
}
|
||
|
||
/* If the destination document exists, delete it. */
|
||
|
||
if (dst->type != ANDROID_VNODE_SAF_NEW
|
||
&& android_saf_delete_document (vdst->tree_uri,
|
||
vdst->document_id,
|
||
vdst->name))
|
||
return -1;
|
||
|
||
fill = mempcpy (path, vp->name, last - vp->name);
|
||
*fill = '\0';
|
||
return android_saf_rename_document (vp->tree_uri,
|
||
vp->document_id,
|
||
path,
|
||
dst_last + 1);
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_stat (struct android_vnode *vnode,
|
||
struct stat *statb)
|
||
{
|
||
struct android_saf_tree_vnode *vp;
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
return android_saf_stat (vp->tree_uri, vp->document_id,
|
||
statb, false);
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
struct android_saf_tree_vnode *vp;
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
/* Validate MODE. */
|
||
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
{
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
return android_saf_access (vp->tree_uri, vp->document_id,
|
||
mode & W_OK);
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
/* Since tree vnodes represent files that already exist, return
|
||
EEXIST. */
|
||
errno = EEXIST;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
/* Return EACCESS should MODE contain unusual bits besides the
|
||
standard file access permissions. */
|
||
|
||
if (mode & ~0777)
|
||
{
|
||
errno = EACCES;
|
||
return -1;
|
||
}
|
||
|
||
/* Otherwise, no further action is necessary, as SAF nodes already
|
||
pretend to be S_IRUSR | S_IWUSR. */
|
||
return 0;
|
||
}
|
||
|
||
static ssize_t
|
||
android_saf_tree_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
/* Return EINVAL. Symlinks aren't exposed to clients by the
|
||
SAF. */
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
/* Open a database Cursor containing each directory entry within the
|
||
supplied SAF tree vnode VP.
|
||
|
||
Value is NULL upon failure with errno set to a suitable value, a
|
||
local reference to the Cursor object otherwise. */
|
||
|
||
static jobject
|
||
android_saf_tree_opendir_1 (struct android_saf_tree_vnode *vp)
|
||
{
|
||
jobject uri, id, cursor;
|
||
jmethodID method;
|
||
|
||
if (inside_saf_critical_section)
|
||
{
|
||
errno = EIO;
|
||
return NULL;
|
||
}
|
||
|
||
/* Build strings for both URI and ID. */
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->tree_uri);
|
||
android_exception_check ();
|
||
|
||
if (vp->document_id)
|
||
{
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->document_id);
|
||
android_exception_check_1 (uri);
|
||
}
|
||
else
|
||
id = NULL;
|
||
|
||
/* Try to open the cursor. */
|
||
method = service_class.open_document_directory;
|
||
inside_saf_critical_section = true;
|
||
cursor
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id);
|
||
inside_saf_critical_section = false;
|
||
|
||
if (id)
|
||
{
|
||
if (android_saf_exception_check (2, id, uri))
|
||
return NULL;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
}
|
||
else if (android_saf_exception_check (1, uri))
|
||
return NULL;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
|
||
/* Return the resulting cursor. */
|
||
return cursor;
|
||
}
|
||
|
||
static struct dirent *
|
||
android_saf_tree_readdir (struct android_vdir *vdir)
|
||
{
|
||
struct android_saf_tree_vdir *dir;
|
||
static struct dirent *dirent;
|
||
jobject entry, d_name;
|
||
jint d_type;
|
||
jmethodID method;
|
||
size_t length, size;
|
||
const char *chars;
|
||
struct coding_system coding;
|
||
|
||
dir = (struct android_saf_tree_vdir *) vdir;
|
||
|
||
/* Try to read one entry from the cursor. */
|
||
method = service_class.read_directory_entry;
|
||
entry
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, dir->cursor);
|
||
android_exception_check ();
|
||
|
||
/* If ENTRY is NULL, we're at the end of the directory. */
|
||
|
||
if (!entry)
|
||
{
|
||
xfree (entry);
|
||
entry = NULL;
|
||
return NULL;
|
||
}
|
||
|
||
/* Load both fields from ENTRY. */
|
||
d_name = (*android_java_env)->GetObjectField (android_java_env, entry,
|
||
entry_class.d_name);
|
||
if (!d_name)
|
||
{
|
||
/* If an error transpires, d_name is set to NULL. */
|
||
(*android_java_env)->ExceptionClear (android_java_env);
|
||
ANDROID_DELETE_LOCAL_REF (entry);
|
||
|
||
/* XXX: what would be a better error indication? */
|
||
errno = EIO;
|
||
return NULL;
|
||
}
|
||
|
||
/* d_type is 1 if this is a directory, and 0 if it's a regular
|
||
file. */
|
||
d_type = (*android_java_env)->GetIntField (android_java_env, entry,
|
||
entry_class.d_type);
|
||
ANDROID_DELETE_LOCAL_REF (entry);
|
||
|
||
/* Copy the name of the directory over. */
|
||
chars = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
(jstring) d_name,
|
||
NULL);
|
||
android_exception_check_nonnull ((void *) chars, d_name);
|
||
|
||
/* Decode this JNI string into utf-8-emacs; see
|
||
android_vfs_convert_name for considerations regarding coding
|
||
systems. */
|
||
length = strlen (chars);
|
||
setup_coding_system (Qandroid_jni, &coding);
|
||
coding.mode |= CODING_MODE_LAST_BLOCK;
|
||
coding.source = (const unsigned char *) chars;
|
||
coding.dst_bytes = 0;
|
||
coding.destination = NULL;
|
||
decode_coding_object (&coding, Qnil, 0, 0, length, length, Qnil);
|
||
|
||
/* Release the string data and the local reference to STRING. */
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
(jstring) d_name,
|
||
chars);
|
||
|
||
/* Resize dirent to accommodate the decoded text. */
|
||
size = offsetof (struct dirent, d_name) + 1 + coding.produced;
|
||
dirent = xrealloc (dirent, size);
|
||
|
||
/* Clear dirent. */
|
||
memset (dirent, 0, size);
|
||
|
||
/* Fill in the generic directory information and copy the string
|
||
over. */
|
||
dirent->d_ino = 0;
|
||
dirent->d_off = 0;
|
||
dirent->d_reclen = size;
|
||
dirent->d_type = d_type ? DT_DIR : DT_UNKNOWN;
|
||
memcpy (dirent->d_name, coding.destination, coding.produced);
|
||
dirent->d_name[coding.produced] = '\0';
|
||
|
||
/* Free the coding system destination buffer. */
|
||
xfree (coding.destination);
|
||
|
||
ANDROID_DELETE_LOCAL_REF (d_name);
|
||
return dirent;
|
||
}
|
||
|
||
static void
|
||
android_saf_tree_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_saf_tree_vdir *dir, **next, *tem;
|
||
|
||
dir = (struct android_saf_tree_vdir *) vdir;
|
||
|
||
/* dir->name is allocated by asprintf, which uses regular
|
||
malloc. */
|
||
free (dir->name);
|
||
|
||
/* Yes, DIR->cursor is a local reference. */
|
||
(*android_java_env)->CallVoidMethod (android_java_env,
|
||
dir->cursor,
|
||
cursor_class.close);
|
||
(*android_java_env)->ExceptionClear (android_java_env);
|
||
ANDROID_DELETE_LOCAL_REF (dir->cursor);
|
||
|
||
/* If the ``directory file descriptor'' has been opened, close
|
||
it. */
|
||
if (dir->fd != -1)
|
||
close (dir->fd);
|
||
|
||
/* Now unlink this directory. */
|
||
|
||
for (next = &all_saf_tree_vdirs; (tem = *next);)
|
||
{
|
||
if (tem == dir)
|
||
*next = dir->next;
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
xfree (dir);
|
||
}
|
||
|
||
static int
|
||
android_saf_tree_dirfd (struct android_vdir *vdir)
|
||
{
|
||
struct android_saf_tree_vdir *dir;
|
||
|
||
dir = (struct android_saf_tree_vdir *) vdir;
|
||
|
||
/* Since `android_saf_tree_opendir' tries to avoid opening a file
|
||
descriptor if readdir isn't called, dirfd can fail if open fails.
|
||
|
||
open sets errno to a set of errors different from what POSIX
|
||
stipulates for dirfd, but for ease of implementation the open
|
||
errors are used instead. */
|
||
|
||
if (dir->fd >= 0)
|
||
return dir->fd;
|
||
|
||
dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
|
||
return dir->fd;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_saf_tree_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_tree_vnode *vp;
|
||
struct android_saf_tree_vdir *dir;
|
||
char *fill, *end;
|
||
jobject cursor;
|
||
char component[EMACS_PATH_MAX];
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
/* First, fill the directory stream with the right functions and
|
||
file name. */
|
||
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_saf_tree_readdir;
|
||
dir->vdir.closedir = android_saf_tree_closedir;
|
||
dir->vdir.dirfd = android_saf_tree_dirfd;
|
||
|
||
/* Derive the authority from the URI. */
|
||
|
||
fill = (char *) vp->tree_uri;
|
||
|
||
if (strncmp (fill, "content://", 10))
|
||
emacs_abort ();
|
||
|
||
/* Skip the content header. */
|
||
fill += sizeof "content://" - 1;
|
||
|
||
/* The authority segment of the URI is between here and the
|
||
next slash. */
|
||
|
||
end = strchr (fill, '/');
|
||
|
||
if (!end)
|
||
emacs_abort ();
|
||
|
||
if (end - fill >= EMACS_PATH_MAX)
|
||
{
|
||
errno = ENAMETOOLONG;
|
||
xfree (dir);
|
||
return NULL;
|
||
}
|
||
|
||
/* Copy the authority over. */
|
||
|
||
memcpy (component, fill, end - fill);
|
||
component[end - fill] = '\0';
|
||
|
||
if (asprintf (&dir->name, "/content/storage/%s/%s%s",
|
||
component, vp->tree_id, vp->name) < 0)
|
||
{
|
||
/* Out of memory. */
|
||
xfree (dir);
|
||
memory_full (0);
|
||
}
|
||
|
||
/* Now open a cursor that iterates through each file in this
|
||
directory. */
|
||
|
||
cursor = android_saf_tree_opendir_1 (vp);
|
||
|
||
if (!cursor)
|
||
{
|
||
xfree (dir->name);
|
||
xfree (dir);
|
||
return NULL;
|
||
}
|
||
|
||
dir->cursor = cursor;
|
||
dir->fd = -1;
|
||
dir->next = all_saf_tree_vdirs;
|
||
all_saf_tree_vdirs = dir;
|
||
return &dir->vdir;
|
||
}
|
||
|
||
/* Create a vnode designating the file NAME within a directory tree
|
||
whose identifier is TREE. As with all other `name' functions, NAME
|
||
may be modified.
|
||
|
||
AUTHORITY is the name of the content provider authority that is
|
||
offering TREE.
|
||
|
||
Value is NULL and errno is set if no document tree or provider by
|
||
those names exists, or some other error takes place (for example,
|
||
if TREE and AUTHORITY aren't encoded correctly.) */
|
||
|
||
static struct android_vnode *
|
||
android_saf_tree_from_name (char *name, const char *tree,
|
||
const char *authority)
|
||
{
|
||
struct android_saf_tree_vnode root;
|
||
jobject tree_string, authority_string, result;
|
||
jmethodID method;
|
||
const char *uri;
|
||
struct android_vnode *vp;
|
||
|
||
/* It's not a given that NAME and TREE are actually in the modified
|
||
UTF-8 format used by the JVM to encode strings, and the JVM
|
||
aborts when encountering a string that is not. Make sure they
|
||
are valid before continuing. */
|
||
|
||
if (android_verify_jni_string (name)
|
||
|| android_verify_jni_string (authority))
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
tree_string = (*android_java_env)->NewStringUTF (android_java_env,
|
||
tree);
|
||
android_exception_check ();
|
||
|
||
authority_string
|
||
= (*android_java_env)->NewStringUTF (android_java_env,
|
||
authority);
|
||
android_exception_check_1 (tree_string);
|
||
|
||
/* Now create the URI and detect if Emacs has the rights to access
|
||
it. */
|
||
|
||
method = service_class.get_tree_uri;
|
||
result
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, tree_string,
|
||
authority_string);
|
||
android_exception_check_2 (tree_string, authority_string);
|
||
ANDROID_DELETE_LOCAL_REF (tree_string);
|
||
ANDROID_DELETE_LOCAL_REF (authority_string);
|
||
|
||
/* If it doesn't, return NULL and set errno to ENOENT. */
|
||
|
||
if (!result)
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
/* Otherwise, decode this string. */
|
||
uri = (*android_java_env)->GetStringUTFChars (android_java_env, result,
|
||
NULL);
|
||
android_exception_check_nonnull ((void *) uri, result);
|
||
|
||
/* Fill in root.tree_uri with values that represent the root of this
|
||
document tree. */
|
||
|
||
root.vnode.ops = &saf_tree_vfs_ops;
|
||
root.vnode.type = ANDROID_VNODE_SAF_TREE;
|
||
root.vnode.flags = 0;
|
||
root.tree_uri = uri;
|
||
root.tree_id = (char *) tree;
|
||
root.document_id = NULL;
|
||
root.name = (char *) "/";
|
||
|
||
vp = (*root.vnode.ops->name) (&root.vnode, name, strlen (name));
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
(jstring) result, uri);
|
||
ANDROID_DELETE_LOCAL_REF (result);
|
||
return vp;
|
||
}
|
||
|
||
/* Return any open SAF tree directory stream for which dirfd has
|
||
returned the file descriptor DIRFD. Return NULL otherwise. */
|
||
|
||
static struct android_saf_tree_vdir *
|
||
android_saf_tree_get_directory (int dirfd)
|
||
{
|
||
struct android_saf_tree_vdir *dir;
|
||
|
||
for (dir = all_saf_tree_vdirs; dir; dir = dir->next)
|
||
{
|
||
if (dir->fd == dirfd && dirfd != -1)
|
||
return dir;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
/* SAF file vnode. The information used to uniquely identify a file
|
||
is identical to that used to identify an SAF directory, but the
|
||
vnode operations are different. */
|
||
|
||
/* Define `struct android_saf_file_vnode' to be identical to a file
|
||
vnode. */
|
||
|
||
#define android_saf_file_vnode android_saf_tree_vnode
|
||
|
||
/* Structure describing an open ParcelFileDescriptor. */
|
||
|
||
struct android_parcel_fd
|
||
{
|
||
/* The next open parcel file descriptor. */
|
||
struct android_parcel_fd *next;
|
||
|
||
/* Global reference to this parcel file descriptor. */
|
||
jobject descriptor;
|
||
|
||
/* The modification time of this parcel file descriptor, or
|
||
`invalid_timespec'. */
|
||
struct timespec mtime;
|
||
|
||
/* The file descriptor itself. */
|
||
int fd;
|
||
};
|
||
|
||
static struct android_vnode *android_saf_file_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_saf_file_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static int android_saf_file_unlink (struct android_vnode *);
|
||
static int android_saf_file_rmdir (struct android_vnode *);
|
||
static struct android_vdir *android_saf_file_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with SAF tree VFS nodes. */
|
||
|
||
static struct android_vops saf_file_vfs_ops =
|
||
{
|
||
android_saf_file_name,
|
||
android_saf_file_open,
|
||
android_saf_tree_close,
|
||
android_saf_file_unlink,
|
||
android_saf_tree_symlink,
|
||
android_saf_file_rmdir,
|
||
android_saf_tree_rename,
|
||
android_saf_tree_stat,
|
||
android_saf_tree_access,
|
||
android_saf_tree_mkdir,
|
||
android_saf_tree_chmod,
|
||
android_saf_tree_readlink,
|
||
android_saf_file_opendir,
|
||
};
|
||
|
||
/* Chain of all parcel file descriptors currently open. */
|
||
static struct android_parcel_fd *open_parcel_fds;
|
||
|
||
static struct android_vnode *
|
||
android_saf_file_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
struct android_saf_file_vnode *vp;
|
||
|
||
/* If LENGTH is empty, make a copy of this vnode and return it. */
|
||
|
||
if (length < 1)
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
|
||
/* Duplicate the information contained within VNODE. */
|
||
|
||
vp->tree_uri = xstrdup (vp->tree_uri);
|
||
vp->tree_id = xstrdup (vp->tree_id);
|
||
vp->name = xstrdup (vp->name);
|
||
vp->document_id = xstrdup (vp->name);
|
||
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* A file vnode has no children of its own. */
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
static int
|
||
android_saf_file_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd_return,
|
||
AAsset **asset)
|
||
{
|
||
struct android_saf_file_vnode *vp;
|
||
jobject uri, id, descriptor;
|
||
jmethodID method;
|
||
jboolean read, trunc, write;
|
||
jint fd;
|
||
struct android_parcel_fd *info;
|
||
struct stat statb;
|
||
|
||
if (inside_saf_critical_section)
|
||
{
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* O_APPEND isn't supported as a consequence of Android content
|
||
providers defaulting to truncating the file. */
|
||
|
||
if (flags & O_APPEND)
|
||
{
|
||
errno = EOPNOTSUPP;
|
||
return -1;
|
||
}
|
||
|
||
/* Build strings for both the URI and ID. */
|
||
|
||
vp = (struct android_saf_file_vnode *) vnode;
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->tree_uri);
|
||
android_exception_check ();
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->document_id);
|
||
android_exception_check_1 (uri);
|
||
|
||
/* Open a parcel file descriptor according to flags. Documentation
|
||
for the SAF openDocument operation is scant and seldom helpful.
|
||
From observations made, it is clear that their file access modes
|
||
are inconsistently implemented, and that at least:
|
||
|
||
r = either an FIFO or a real file, without truncation.
|
||
w = either an FIFO or a real file, with OR without truncation.
|
||
wt = either an FIFO or a real file, with truncation.
|
||
rw = a real file, without truncation.
|
||
rwt = a real file, with truncation.
|
||
|
||
This diverges from the self-contradicting documentation, where
|
||
openDocument says nothing about truncation, and openFile mentions
|
||
that w can elect not to truncate and programs which rely on
|
||
truncation should use wt.
|
||
|
||
Since Emacs is prepared to handle FIFOs within fileio.c, simply
|
||
specify the straightforward relationship between FLAGS and the
|
||
file access modes listed above. */
|
||
|
||
method = service_class.open_document;
|
||
read = trunc = write = false;
|
||
|
||
if ((flags & O_RDWR) == O_RDWR || (flags & O_WRONLY))
|
||
write = true;
|
||
|
||
if (flags & O_TRUNC)
|
||
trunc = true;
|
||
|
||
if ((flags & O_RDWR) == O_RDWR || !write)
|
||
read = true;
|
||
|
||
inside_saf_critical_section = true;
|
||
descriptor
|
||
= (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
read, write, trunc);
|
||
inside_saf_critical_section = false;
|
||
|
||
if (android_saf_exception_check (2, uri, id))
|
||
return -1;
|
||
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
|
||
if (!descriptor)
|
||
{
|
||
/* Assume that permission has been denied if DESCRIPTOR cannot
|
||
be opened. */
|
||
errno = EPERM;
|
||
return -1;
|
||
}
|
||
|
||
/* Allocate a record for this file descriptor. Parcel file
|
||
descriptors should be closed using their own `close' function,
|
||
which takes care of notifying the source that it has been
|
||
closed. */
|
||
info = xmalloc (sizeof *info);
|
||
|
||
/* Now obtain the file descriptor. */
|
||
fd = (*android_java_env)->CallIntMethod (android_java_env,
|
||
descriptor,
|
||
fd_class.get_fd);
|
||
android_exception_check_1 (descriptor);
|
||
|
||
/* Create a global reference to descriptor. */
|
||
info->descriptor
|
||
= (*android_java_env)->NewGlobalRef (android_java_env,
|
||
descriptor);
|
||
|
||
if (!info->descriptor)
|
||
{
|
||
/* If the global reference can't be created, delete
|
||
descriptor. */
|
||
(*android_java_env)->ExceptionClear (android_java_env);
|
||
(*android_java_env)->CallVoidMethod (android_java_env,
|
||
descriptor,
|
||
fd_class.close);
|
||
(*android_java_env)->ExceptionClear (android_java_env);
|
||
ANDROID_DELETE_LOCAL_REF (descriptor);
|
||
|
||
/* Free INFO. */
|
||
xfree (info);
|
||
|
||
/* Set errno to EMFILE and return. */
|
||
errno = EMFILE;
|
||
return -1;
|
||
}
|
||
|
||
/* Delete the local ref to DESCRIPTOR. */
|
||
ANDROID_DELETE_LOCAL_REF (descriptor);
|
||
|
||
/* Try to retrieve the modification time of this file from the
|
||
content provider.
|
||
|
||
Refrain from introducing the file status into the file status
|
||
cache if FLAGS & O_RDWR or FLAGS & O_WRONLY: the cached file
|
||
status will contain a size and modification time inconsistent
|
||
with the result of any modifications that later transpire. */
|
||
|
||
if (!android_saf_stat (vp->tree_uri, vp->document_id,
|
||
&statb, write))
|
||
info->mtime = get_stat_mtime (&statb);
|
||
else
|
||
info->mtime = invalid_timespec ();
|
||
|
||
/* Set info->fd and chain it onto the list. */
|
||
info->fd = fd;
|
||
info->next = open_parcel_fds;
|
||
open_parcel_fds = info;
|
||
|
||
/* Return the file descriptor. */
|
||
*fd_return = fd;
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_saf_file_unlink (struct android_vnode *vnode)
|
||
{
|
||
struct android_saf_file_vnode *vp;
|
||
|
||
vp = (struct android_saf_file_vnode *) vnode;
|
||
return android_saf_delete_document (vp->tree_uri,
|
||
vp->document_id,
|
||
vp->name);
|
||
}
|
||
|
||
static int
|
||
android_saf_file_rmdir (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOTDIR;
|
||
return -1;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_saf_file_opendir (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
/* Close FD if it's a parcel file descriptor and return true.
|
||
If FD isn't, return false.
|
||
|
||
Such file descriptors need to be closed using a function
|
||
written in Java, to tell the sender that it has been
|
||
closed. */
|
||
|
||
static bool
|
||
android_close_parcel_fd (int fd)
|
||
{
|
||
struct android_parcel_fd *tem, **next, *temp;
|
||
|
||
for (next = &open_parcel_fds; (tem = *next);)
|
||
{
|
||
if (tem->fd == fd)
|
||
{
|
||
(*android_java_env)->CallVoidMethod (android_java_env,
|
||
tem->descriptor,
|
||
fd_class.close);
|
||
|
||
/* Ignore exceptions for the same reason EINTR errors from
|
||
`close' should be ignored. */
|
||
(*android_java_env)->ExceptionClear (android_java_env);
|
||
(*android_java_env)->DeleteGlobalRef (android_java_env,
|
||
tem->descriptor);
|
||
|
||
temp = tem->next;
|
||
xfree (tem);
|
||
*next = temp;
|
||
|
||
return true;
|
||
}
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
/* SAF ``new'' vnodes. These nodes share their data structures
|
||
with tree and file vnodes, but represent files that don't actually
|
||
exist within a directory. In them, the document ID represents not
|
||
the file designated by the vnode itself, but rather its parent
|
||
directory.
|
||
|
||
The only vops defined serve to create directories or files, at
|
||
which point the vnode becomes invalid. */
|
||
|
||
#define android_saf_new_vnode android_saf_tree_vnode
|
||
|
||
static struct android_vnode *android_saf_new_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static int android_saf_new_open (struct android_vnode *, int,
|
||
mode_t, bool, int *, AAsset **);
|
||
static int android_saf_new_unlink (struct android_vnode *);
|
||
static int android_saf_new_symlink (const char *, struct android_vnode *);
|
||
static int android_saf_new_rmdir (struct android_vnode *);
|
||
static int android_saf_new_rename (struct android_vnode *,
|
||
struct android_vnode *, bool);
|
||
static int android_saf_new_stat (struct android_vnode *, struct stat *);
|
||
static int android_saf_new_access (struct android_vnode *, int);
|
||
static int android_saf_new_mkdir (struct android_vnode *, mode_t);
|
||
static int android_saf_new_chmod (struct android_vnode *, mode_t, int);
|
||
static ssize_t android_saf_new_readlink (struct android_vnode *, char *,
|
||
size_t);
|
||
static struct android_vdir *android_saf_new_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with SAF new VFS nodes. */
|
||
|
||
static struct android_vops saf_new_vfs_ops =
|
||
{
|
||
android_saf_new_name,
|
||
android_saf_new_open,
|
||
android_saf_tree_close,
|
||
android_saf_new_unlink,
|
||
android_saf_new_symlink,
|
||
android_saf_new_rmdir,
|
||
android_saf_new_rename,
|
||
android_saf_new_stat,
|
||
android_saf_new_access,
|
||
android_saf_new_mkdir,
|
||
android_saf_new_chmod,
|
||
android_saf_new_readlink,
|
||
android_saf_new_opendir,
|
||
};
|
||
|
||
static struct android_vnode *
|
||
android_saf_new_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
struct android_saf_new_vnode *vp;
|
||
|
||
/* If LENGTH is empty, make a copy of this vnode and return it. */
|
||
|
||
if (length < 1)
|
||
{
|
||
vp = xmalloc (sizeof *vp);
|
||
memcpy (vp, vnode, sizeof *vp);
|
||
|
||
/* Duplicate the information contained within VNODE. */
|
||
|
||
vp->tree_uri = xstrdup (vp->tree_uri);
|
||
vp->tree_id = xstrdup (vp->tree_id);
|
||
vp->name = xstrdup (vp->name);
|
||
vp->document_id = xstrdup (vp->name);
|
||
|
||
return &vp->vnode;
|
||
}
|
||
|
||
/* A nonexistent vnode has no children of its own. */
|
||
errno = ENOTDIR;
|
||
return NULL;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_open (struct android_vnode *vnode, int flags,
|
||
mode_t mode, bool asset_p, int *fd_return,
|
||
AAsset **asset)
|
||
{
|
||
struct android_saf_new_vnode *vp;
|
||
char *end;
|
||
jstring name, id, uri, new_id;
|
||
const char *new_doc_id;
|
||
jmethodID method;
|
||
|
||
/* If creating a file wasn't intended, return ENOENT. */
|
||
|
||
if (!(flags & O_CREAT))
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
/* If vp->name indicates that it's a directory, return ENOENT. */
|
||
|
||
vp = (struct android_saf_new_vnode *) vnode;
|
||
end = strrchr (vp->name, '/');
|
||
|
||
/* VP->name must contain at least one directory separator. */
|
||
eassert (end);
|
||
|
||
if (end[1] == '\0')
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
/* Otherwise, try to create a new document. First, build strings
|
||
for the name, ID and document URI. */
|
||
|
||
name = (*android_java_env)->NewStringUTF (android_java_env,
|
||
end + 1);
|
||
android_exception_check ();
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->document_id);
|
||
android_exception_check_1 (name);
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->tree_uri);
|
||
android_exception_check_2 (name, id);
|
||
|
||
/* Next, try to create a new document and retrieve its ID. */
|
||
|
||
method = service_class.create_document;
|
||
new_id = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
name);
|
||
|
||
if (android_saf_exception_check (3, name, id, uri))
|
||
return -1;
|
||
|
||
/* Delete unused local references. */
|
||
ANDROID_DELETE_LOCAL_REF (name);
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
|
||
if (!new_id)
|
||
{
|
||
/* The file couldn't be created for some reason. */
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* Now, free VP->document_id and replace it with the service
|
||
document ID. */
|
||
|
||
new_doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
new_id, NULL);
|
||
android_exception_check_nonnull ((void *) new_doc_id, new_id);
|
||
|
||
xfree (vp->document_id);
|
||
vp->document_id = xstrdup (new_doc_id);
|
||
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
new_id, new_doc_id);
|
||
ANDROID_DELETE_LOCAL_REF (new_id);
|
||
|
||
/* Finally, transform this vnode into a file vnode and call its
|
||
`open' function. */
|
||
vp->vnode.type = ANDROID_VNODE_SAF_FILE;
|
||
vp->vnode.ops = &saf_file_vfs_ops;
|
||
return (*vp->vnode.ops->open) (vnode, flags, mode, asset_p,
|
||
fd_return, asset);
|
||
}
|
||
|
||
static int
|
||
android_saf_new_unlink (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_symlink (const char *target, struct android_vnode *vnode)
|
||
{
|
||
errno = EPERM;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_rmdir (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_rename (struct android_vnode *src,
|
||
struct android_vnode *dst,
|
||
bool keep_existing)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_stat (struct android_vnode *vnode,
|
||
struct stat *statb)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_access (struct android_vnode *vnode, int mode)
|
||
{
|
||
if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
|
||
errno = EINVAL;
|
||
else
|
||
errno = ENOENT;
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_mkdir (struct android_vnode *vnode, mode_t mode)
|
||
{
|
||
struct android_saf_new_vnode *vp;
|
||
jstring name, id, uri, new_id;
|
||
jmethodID method;
|
||
const char *new_doc_id;
|
||
char *end;
|
||
|
||
vp = (struct android_saf_tree_vnode *) vnode;
|
||
|
||
/* Find the last component of vp->name. */
|
||
end = strrchr (vp->name, '/');
|
||
|
||
/* VP->name must contain at least one directory separator. */
|
||
eassert (end);
|
||
|
||
if (end[1] == '\0')
|
||
{
|
||
/* There's a trailing directory separator. Search
|
||
backwards. */
|
||
|
||
end--;
|
||
while (end != vp->name && *end != '/')
|
||
end--;
|
||
|
||
/* vp->name[0] is always a directory separator. */
|
||
eassert (*end == '/');
|
||
}
|
||
|
||
/* Otherwise, try to create a new document. First, build strings
|
||
for the name, ID and document URI. */
|
||
|
||
name = (*android_java_env)->NewStringUTF (android_java_env,
|
||
end + 1);
|
||
android_exception_check ();
|
||
id = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->document_id);
|
||
android_exception_check_1 (name);
|
||
uri = (*android_java_env)->NewStringUTF (android_java_env,
|
||
vp->tree_uri);
|
||
android_exception_check_2 (name, id);
|
||
|
||
/* Next, try to create a new document and retrieve its ID. */
|
||
|
||
method = service_class.create_directory;
|
||
new_id = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, uri, id,
|
||
name);
|
||
|
||
if (android_saf_exception_check (3, name, id, uri))
|
||
return -1;
|
||
|
||
/* Delete unused local references. */
|
||
ANDROID_DELETE_LOCAL_REF (name);
|
||
ANDROID_DELETE_LOCAL_REF (id);
|
||
ANDROID_DELETE_LOCAL_REF (uri);
|
||
|
||
if (!new_id)
|
||
{
|
||
/* The file couldn't be created for some reason. */
|
||
errno = EIO;
|
||
return -1;
|
||
}
|
||
|
||
/* Now, free VP->document_id and replace it with the service
|
||
document ID. */
|
||
|
||
new_doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
|
||
new_id, NULL);
|
||
|
||
if (android_saf_exception_check (3, name, id, uri))
|
||
return -1;
|
||
|
||
xfree (vp->document_id);
|
||
vp->document_id = xstrdup (new_doc_id);
|
||
|
||
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
|
||
new_id, new_doc_id);
|
||
ANDROID_DELETE_LOCAL_REF (new_id);
|
||
|
||
/* Finally, transform this vnode into a directory vnode. */
|
||
vp->vnode.type = ANDROID_VNODE_SAF_TREE;
|
||
vp->vnode.ops = &saf_tree_vfs_ops;
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
android_saf_new_chmod (struct android_vnode *vnode, mode_t mode,
|
||
int flags)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static ssize_t
|
||
android_saf_new_readlink (struct android_vnode *vnode, char *buffer,
|
||
size_t size)
|
||
{
|
||
errno = ENOENT;
|
||
return -1;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_saf_new_opendir (struct android_vnode *vnode)
|
||
{
|
||
errno = ENOENT;
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
/* Synchronization between SAF and Emacs. Consult EmacsSafThread.java
|
||
for more details. */
|
||
|
||
/* Semaphore posted upon the completion of an SAF operation. */
|
||
static sem_t saf_completion_sem;
|
||
|
||
#ifdef __clang__
|
||
#pragma clang diagnostic push
|
||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||
#else /* GNUC */
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
|
||
#endif /* __clang__ */
|
||
|
||
JNIEXPORT jint JNICALL
|
||
NATIVE_NAME (safSyncAndReadInput) (JNIEnv *env, jobject object)
|
||
{
|
||
JNI_STACK_ALIGNMENT_PROLOGUE;
|
||
|
||
while (sem_wait (&saf_completion_sem) < 0)
|
||
{
|
||
if (input_blocked_p ())
|
||
continue;
|
||
|
||
process_pending_signals ();
|
||
|
||
if (!NILP (Vquit_flag))
|
||
{
|
||
__android_log_print (ANDROID_LOG_VERBOSE, __func__,
|
||
"quitting from IO operation");
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
JNIEXPORT void JNICALL
|
||
NATIVE_NAME (safSync) (JNIEnv *env, jobject object)
|
||
{
|
||
JNI_STACK_ALIGNMENT_PROLOGUE;
|
||
|
||
while (sem_wait (&saf_completion_sem) < 0)
|
||
process_pending_signals ();
|
||
}
|
||
|
||
JNIEXPORT void JNICALL
|
||
NATIVE_NAME (safPostRequest) (JNIEnv *env, jobject object)
|
||
{
|
||
JNI_STACK_ALIGNMENT_PROLOGUE;
|
||
|
||
sem_post (&saf_completion_sem);
|
||
}
|
||
|
||
JNIEXPORT jboolean JNICALL
|
||
NATIVE_NAME (ftruncate) (JNIEnv *env, jobject object, jint fd)
|
||
{
|
||
JNI_STACK_ALIGNMENT_PROLOGUE;
|
||
|
||
if (ftruncate (fd, 0) < 0)
|
||
return false;
|
||
|
||
/* Reset the file pointer. */
|
||
if (lseek (fd, 0, SEEK_SET) < 0)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
#ifdef __clang__
|
||
#pragma clang diagnostic pop
|
||
#else /* GNUC */
|
||
#pragma GCC diagnostic pop
|
||
#endif /* __clang__ */
|
||
|
||
|
||
|
||
/* Root vnode. This vnode represents the root inode, and is a regular
|
||
Unix vnode with modifications to `name' so that it returns asset and
|
||
content vnodes, and to `opendir', so that asset and content vnodes
|
||
are read from the root directory, whether or not Emacs holds rights
|
||
to access the underlying filesystem. */
|
||
|
||
struct android_root_vdir
|
||
{
|
||
/* The directory function table. */
|
||
struct android_vdir vdir;
|
||
|
||
/* The directory stream, or NULL if it could not be opened. */
|
||
DIR *directory;
|
||
|
||
/* Index of the next directory to return in `special_vnodes'. */
|
||
int index;
|
||
};
|
||
|
||
/* File descriptor for instances of the foregoing structure when the
|
||
true root is unavailable. */
|
||
static int root_fd = -1;
|
||
|
||
/* Number of open instances referencing this file descriptor. */
|
||
static ptrdiff_t root_fd_references;
|
||
|
||
static struct android_vnode *android_root_name (struct android_vnode *,
|
||
char *, size_t);
|
||
static struct android_vdir *android_root_opendir (struct android_vnode *);
|
||
|
||
/* Vector of VFS operations associated with Unix root filesystem VFS
|
||
nodes. */
|
||
|
||
static struct android_vops root_vfs_ops =
|
||
{
|
||
android_root_name,
|
||
android_unix_open,
|
||
android_unix_close,
|
||
android_unix_unlink,
|
||
android_unix_symlink,
|
||
android_unix_rmdir,
|
||
android_unix_rename,
|
||
android_unix_stat,
|
||
android_unix_access,
|
||
android_unix_mkdir,
|
||
android_unix_chmod,
|
||
android_unix_readlink,
|
||
android_root_opendir,
|
||
};
|
||
|
||
/* Array of special named vnodes. */
|
||
|
||
static struct android_special_vnode special_vnodes[] =
|
||
{
|
||
{ "assets", 6, android_afs_initial, },
|
||
{ "content", 7, android_content_initial,
|
||
LISPSYM_INITIALLY (Qandroid_jni), },
|
||
};
|
||
|
||
/* Convert the file name NAME from Emacs's internal character encoding
|
||
to CODING, and return a Lisp string with the data so produced.
|
||
|
||
Calling this function creates an implicit assumption that
|
||
`file-name-coding-system' is compatible with `utf-8-emacs', which is
|
||
not unacceptable as users with cause to modify
|
||
file-name-coding-system should be aware and prepared for adverse
|
||
consequences affecting files stored on different filesystems,
|
||
including virtual ones. */
|
||
|
||
static Lisp_Object
|
||
android_vfs_convert_name (const char *name, Lisp_Object coding)
|
||
{
|
||
Lisp_Object name1;
|
||
|
||
/* Convert the contents of the buffer after BUFFER_END from the file
|
||
name coding system to special->special_coding_system. */
|
||
name1 = build_string (name);
|
||
name1 = code_convert_string (name1, coding, Qt, true, true, true);
|
||
return name1;
|
||
}
|
||
|
||
static struct android_vnode *
|
||
android_root_name (struct android_vnode *vnode, char *name,
|
||
size_t length)
|
||
{
|
||
char *component_end;
|
||
struct android_special_vnode *special;
|
||
size_t i;
|
||
Lisp_Object file_name;
|
||
struct android_vnode *vp;
|
||
|
||
/* Skip any leading separator in NAME. */
|
||
|
||
if (*name == '/')
|
||
name++, length--;
|
||
|
||
/* Look for the first directory separator. */
|
||
component_end = strchr (name, '/');
|
||
|
||
/* If not there, use name + length. */
|
||
|
||
if (!component_end)
|
||
component_end = name + length;
|
||
else
|
||
/* Move past the separator character. */
|
||
component_end++;
|
||
|
||
/* Now, find out if the first component is a special vnode; if so,
|
||
call its root lookup function with the rest of NAME there. */
|
||
|
||
for (i = 0; i < ARRAYELTS (special_vnodes); ++i)
|
||
{
|
||
special = &special_vnodes[i];
|
||
|
||
if (component_end - name == special->length
|
||
&& !memcmp (special->name, name, special->length))
|
||
{
|
||
if (!NILP (special->special_coding_system))
|
||
{
|
||
USE_SAFE_ALLOCA;
|
||
|
||
file_name
|
||
= android_vfs_convert_name (component_end,
|
||
special->special_coding_system);
|
||
|
||
/* Allocate a buffer and copy file_name into the same. */
|
||
length = SBYTES (file_name) + 1;
|
||
name = SAFE_ALLOCA (length);
|
||
|
||
/* Copy the trailing NULL byte also. */
|
||
memcpy (name, SDATA (file_name), length);
|
||
vp = (*special->initial) (name, length - 1);
|
||
SAFE_FREE ();
|
||
return vp;
|
||
}
|
||
|
||
return (*special->initial) (component_end,
|
||
length - special->length);
|
||
}
|
||
|
||
/* Detect the case where a special is named with a trailing
|
||
directory separator. */
|
||
|
||
if (component_end - name == special->length + 1
|
||
&& !memcmp (special->name, name, special->length)
|
||
&& name[special->length] == '/')
|
||
{
|
||
if (!NILP (special->special_coding_system))
|
||
{
|
||
USE_SAFE_ALLOCA;
|
||
|
||
file_name
|
||
= android_vfs_convert_name (component_end - 1,
|
||
special->special_coding_system);
|
||
|
||
/* Allocate a buffer and copy file_name into the same. */
|
||
length = SBYTES (file_name) + 1;
|
||
name = SAFE_ALLOCA (length);
|
||
|
||
/* Copy the trailing NULL byte also. */
|
||
memcpy (name, SDATA (file_name), length);
|
||
vp = (*special->initial) (name, length - 1);
|
||
SAFE_FREE ();
|
||
return vp;
|
||
}
|
||
|
||
/* Make sure to include the directory separator. */
|
||
return (*special->initial) (component_end - 1,
|
||
length - special->length);
|
||
}
|
||
}
|
||
|
||
/* Otherwise, continue searching for a vnode normally. */
|
||
return android_unix_name (vnode, name, length);
|
||
}
|
||
|
||
static struct dirent *
|
||
android_root_readdir (struct android_vdir *vdir)
|
||
{
|
||
struct android_root_vdir *dir;
|
||
static struct dirent dirent, *p;
|
||
|
||
dir = (struct android_root_vdir *) vdir;
|
||
p = dir->directory ? readdir (dir->directory) : NULL;
|
||
|
||
if (p || dir->index >= ARRAYELTS (special_vnodes))
|
||
return p;
|
||
|
||
dirent.d_ino = 0;
|
||
dirent.d_off = 0;
|
||
dirent.d_reclen = sizeof dirent;
|
||
dirent.d_type = DT_DIR;
|
||
|
||
/* No element in special_vnode must overflow dirent.d_name. */
|
||
strcpy ((char *) &dirent.d_name,
|
||
special_vnodes[dir->index++].name);
|
||
return &dirent;
|
||
}
|
||
|
||
static void
|
||
android_root_closedir (struct android_vdir *vdir)
|
||
{
|
||
struct android_root_vdir *dir;
|
||
|
||
dir = (struct android_root_vdir *) vdir;
|
||
|
||
if (dir->directory)
|
||
closedir (dir->directory);
|
||
|
||
if (root_fd_references--)
|
||
;
|
||
else
|
||
{
|
||
/* Close root_fd, for which no references remain. */
|
||
close (root_fd);
|
||
root_fd = -1;
|
||
}
|
||
|
||
xfree (vdir);
|
||
}
|
||
|
||
static int
|
||
android_root_dirfd (struct android_vdir *vdir)
|
||
{
|
||
eassert (root_fd != -1);
|
||
return root_fd;
|
||
}
|
||
|
||
static struct android_vdir *
|
||
android_root_opendir (struct android_vnode *vnode)
|
||
{
|
||
struct android_unix_vnode *vp;
|
||
struct android_root_vdir *dir;
|
||
DIR *directory;
|
||
|
||
/* Try to opendir the vnode. */
|
||
vp = (struct android_unix_vnode *) vnode;
|
||
|
||
directory = opendir (vp->name);
|
||
|
||
/* Proceed with the remaining code if directory is nil, in which event
|
||
directory functions will simply forgo listing files inside the real
|
||
root directory. */
|
||
|
||
dir = xmalloc (sizeof *dir);
|
||
dir->vdir.readdir = android_root_readdir;
|
||
dir->vdir.closedir = android_root_closedir;
|
||
dir->vdir.dirfd = android_root_dirfd;
|
||
dir->directory = directory;
|
||
dir->index = 0;
|
||
|
||
/* Allocate a temporary file descriptor for this ersatz root. This is
|
||
required regardless of the value of DIRECTORY, as android_fstatat
|
||
and co. will not defer to the VFS layer if a directory file
|
||
descriptor is not known to be special. */
|
||
if (root_fd < 0)
|
||
root_fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
|
||
root_fd_references++;
|
||
|
||
return &dir->vdir;
|
||
}
|
||
|
||
|
||
|
||
/* File system lookup. */
|
||
|
||
/* Look up the vnode that designates NAME, a file name that is at least
|
||
N bytes, converting between different file name coding systems as
|
||
necessary.
|
||
|
||
NAME may be either an absolute file name or a name relative to the
|
||
current working directory. It must not be longer than EMACS_PATH_MAX
|
||
bytes.
|
||
|
||
Value is NULL upon failure with errno set accordingly, or the
|
||
vnode. */
|
||
|
||
static struct android_vnode *
|
||
android_name_file (const char *name)
|
||
{
|
||
char buffer[EMACS_PATH_MAX + 1], *head;
|
||
const char *end;
|
||
size_t len;
|
||
int nslash, c;
|
||
struct android_vnode *vp;
|
||
|
||
len = strlen (name);
|
||
if (len > EMACS_PATH_MAX)
|
||
{
|
||
errno = ENAMETOOLONG;
|
||
return NULL;
|
||
}
|
||
|
||
/* Now, try to ``normalize'' the file name by removing consecutive
|
||
slash characters while copying it to BUFFER. */
|
||
|
||
head = buffer;
|
||
nslash = 0;
|
||
for (end = name + len; name < end; ++name)
|
||
{
|
||
c = *name;
|
||
|
||
switch (c)
|
||
{
|
||
case '/':
|
||
/* This is a directory separator character. Two consecutive
|
||
separator characters should be replaced by a single
|
||
character; more than three in a row means that the
|
||
section of the file name before the last slash character
|
||
should be discarded. */
|
||
|
||
if (!nslash)
|
||
*head++ = '/';
|
||
|
||
nslash++;
|
||
|
||
if (nslash >= 3)
|
||
/* Return to the root directory. */
|
||
head = buffer, *head++ = '/', nslash = 0;
|
||
break;
|
||
|
||
default:
|
||
/* Otherwise, copy the file name over. */
|
||
nslash = 0;
|
||
*head++ = *name;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Terminate the file name. */
|
||
*head = '\0';
|
||
|
||
/* If HEAD is a relative file name, it can't reside inside the
|
||
virtual mounts; create a Unix vnode instead. */
|
||
|
||
if (head == buffer || buffer[0] != '/')
|
||
return android_unix_vnode (buffer);
|
||
|
||
/* Start looking from the root vnode. */
|
||
vp = &root_vnode.vnode;
|
||
|
||
/* If buffer is empty, this will create a duplicate of the root
|
||
vnode. */
|
||
return (*vp->ops->name) (vp, buffer + 1, head - buffer - 1);
|
||
}
|
||
|
||
|
||
|
||
/* Initialize the virtual filesystem layer. Load the directory tree
|
||
from the given asset MANAGER (which should be a local reference
|
||
within ENV) that will be used to access assets in the future, and
|
||
create the root vnode.
|
||
|
||
ENV should be a JNI environment valid for future calls to VFS
|
||
functions. */
|
||
|
||
void
|
||
android_vfs_init (JNIEnv *env, jobject manager)
|
||
{
|
||
jclass old;
|
||
|
||
android_init_assets (env, manager);
|
||
|
||
/* Create the root vnode, which is used to locate all other
|
||
vnodes. */
|
||
root_vnode.vnode.ops = &root_vfs_ops;
|
||
root_vnode.vnode.type = ANDROID_VNODE_UNIX;
|
||
root_vnode.vnode.flags = 0;
|
||
root_vnode.name_length = 1;
|
||
root_vnode.name = (char *) "/";
|
||
|
||
/* Initialize some required classes. */
|
||
java_string_class = (*env)->FindClass (env, "java/lang/String");
|
||
eassert (java_string_class);
|
||
|
||
old = java_string_class;
|
||
java_string_class = (jclass) (*env)->NewGlobalRef (env,
|
||
java_string_class);
|
||
eassert (java_string_class);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
|
||
if (android_get_current_api_level () < 19)
|
||
return;
|
||
|
||
/* Initialize each of the exception classes used by
|
||
`android_saf_exception_check'. */
|
||
|
||
old = (*env)->FindClass (env, "java/io/FileNotFoundException");
|
||
file_not_found_exception = (*env)->NewGlobalRef (env, old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
eassert (file_not_found_exception);
|
||
|
||
old = (*env)->FindClass (env, "java/lang/SecurityException");
|
||
security_exception = (*env)->NewGlobalRef (env, old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
eassert (security_exception);
|
||
|
||
old = (*env)->FindClass (env, "android/os/OperationCanceledException");
|
||
operation_canceled_exception = (*env)->NewGlobalRef (env, old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
eassert (operation_canceled_exception);
|
||
|
||
old = (*env)->FindClass (env, "java/lang/UnsupportedOperationException");
|
||
unsupported_operation_exception = (*env)->NewGlobalRef (env, old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
eassert (unsupported_operation_exception);
|
||
|
||
old = (*env)->FindClass (env, "java/lang/OutOfMemoryError");
|
||
out_of_memory_error = (*env)->NewGlobalRef (env, old);
|
||
(*env)->DeleteLocalRef (env, old);
|
||
eassert (out_of_memory_error);
|
||
|
||
/* And initialize those used on Android 5.0 and later. */
|
||
|
||
if (android_get_current_api_level () < 21)
|
||
return;
|
||
|
||
android_init_cursor_class (env);
|
||
android_init_entry_class (env);
|
||
android_init_fd_class (env);
|
||
|
||
/* Initialize the semaphore used to wait for SAF operations to
|
||
complete. */
|
||
|
||
if (sem_init (&saf_completion_sem, 0, 0) < 0)
|
||
emacs_abort ();
|
||
}
|
||
|
||
/* The replacement functions that follow have several major
|
||
drawbacks:
|
||
|
||
The first is that CWD relative file names will always be Unix
|
||
vnodes, and looking up their parents will always return another
|
||
Unix vnode. For example, with the working directory set to
|
||
/sdcard:
|
||
|
||
../content/storage
|
||
|
||
will find /sdcard/../content/storage on the Unix filesystem,
|
||
opposed to /content/storage within the ``content'' VFS.
|
||
|
||
Emacs only uses file names expanded through `expand-file-name', so
|
||
this is unproblematic in practice.
|
||
|
||
The second is that `..' components do not usually check that their
|
||
preceding component is a directory. This is a side effect of their
|
||
removal from file names as part of a pre-processing step before
|
||
they are opened. So, even if:
|
||
|
||
/sdcard/foo.txt
|
||
|
||
is a file, opening the directory:
|
||
|
||
/sdcard/foo.txt/..
|
||
|
||
will be successful.
|
||
|
||
The third is that the handling of `..' components relative to
|
||
another vnode hasn't been tested and is only assumed to work
|
||
because the code has been written. It does not pose a practical
|
||
problem, however, as Emacs only names files starting from the root
|
||
vnode.
|
||
|
||
The fourth is that errno values from vnode operations don't always
|
||
reflect what the Unix system calls they emulate can return: for
|
||
example, `open' may return EIO, while trying to `mkdir' within
|
||
/content will return ENOENT instead of EROFS. This is a
|
||
consequence of how accessing a non-existent file may fail at vnode
|
||
lookup, instead of when a vop is used. This problem hasn't made a
|
||
sufficient nuisance of itself to justify its fix yet.
|
||
|
||
The fifth is that trailing directory separators may be lost when
|
||
naming files relative to another vnode, as a consequence of an
|
||
optimization used to avoid allocating too much stack or heap
|
||
space.
|
||
|
||
The sixth is that flags and other argument checking is nowhere near
|
||
exhaustive on vnode types other than Unix vnodes.
|
||
|
||
The seventh is that certain vnode types may read async input and
|
||
return EINTR not upon the arrival of a signal itself, but instead
|
||
if subsequently read input causes Vquit_flag to be set. These
|
||
vnodes may not be reentrant, but operating on them from within an
|
||
async input handler will at worst cause an error to be returned.
|
||
|
||
The eight is that some vnode types do not support O_APPEND.
|
||
|
||
And the final drawback is that directories cannot be directly
|
||
opened. Instead, `dirfd' must be called on a directory stream used
|
||
by `openat'.
|
||
|
||
Caveat emptor! */
|
||
|
||
/* Open the VFS node designated by NAME, taking into account FLAGS and
|
||
MODE, both of which mean the same as they do in a call to `open'.
|
||
|
||
Value is -1 upon failure with errno set accordingly, and a file
|
||
descriptor otherwise. */
|
||
|
||
int
|
||
android_open (const char *name, int flags, mode_t mode)
|
||
{
|
||
struct android_vnode *vp;
|
||
int fd, rc;
|
||
|
||
vp = android_name_file (name);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->open) (vp, flags, mode, false, &fd, NULL);
|
||
(*vp->ops->close) (vp);
|
||
|
||
if (rc < 0)
|
||
return -1;
|
||
|
||
/* If rc is 1, then an asset file descriptor has been returned.
|
||
This is impossible, so assert that it doesn't transpire. */
|
||
assert (rc != 1);
|
||
return fd;
|
||
}
|
||
|
||
/* Unlink the VFS node designated by the specified FILE.
|
||
Value is -1 upon failure with errno set, and 0 otherwise. */
|
||
|
||
int
|
||
android_unlink (const char *name)
|
||
{
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
vp = android_name_file (name);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->unlink) (vp);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Symlink the VFS node designated by LINKPATH to TARGET.
|
||
Value is -1 upon failure with errno set, and 0 otherwise. */
|
||
|
||
int
|
||
android_symlink (const char *target, const char *linkpath)
|
||
{
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
vp = android_name_file (linkpath);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->symlink) (target, vp);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Remove the empty directory at the VFS node designated by NAME.
|
||
Value is -1 upon failure with errno set, and 0 otherwise. */
|
||
|
||
int
|
||
android_rmdir (const char *name)
|
||
{
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
vp = android_name_file (name);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->rmdir) (vp);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Create a directory at the VFS node designated by NAME and the given
|
||
access MODE. Value is -1 upon failure with errno set, 0
|
||
otherwise. */
|
||
|
||
int
|
||
android_mkdir (const char *name, mode_t mode)
|
||
{
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
vp = android_name_file (name);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->mkdir) (vp, mode);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Rename the vnode designated by SRC to the vnode designated by DST.
|
||
If DST already exists, return -1 and set errno to EEXIST.
|
||
|
||
SRCFD and DSTFD should be AT_FDCWD, or else value is -1 and errno
|
||
is ENOSYS.
|
||
|
||
If the filesystem or vnodes containing either DST or SRC does not
|
||
support rename operations that also check for a preexisting
|
||
destination, return -1 and set errno to ENOSYS.
|
||
|
||
Otherwise, value and errno are identical to that of Unix
|
||
`rename' with the same arguments. */
|
||
|
||
int
|
||
android_renameat_noreplace (int srcfd, const char *src,
|
||
int dstfd, const char *dst)
|
||
{
|
||
struct android_vnode *vp, *vdst;
|
||
int rc;
|
||
|
||
if (srcfd != AT_FDCWD || dstfd != AT_FDCWD)
|
||
{
|
||
errno = ENOSYS;
|
||
return -1;
|
||
}
|
||
|
||
/* Find vnodes for both src and dst. */
|
||
|
||
vp = android_name_file (src);
|
||
if (!vp)
|
||
goto error;
|
||
|
||
vdst = android_name_file (dst);
|
||
if (!vdst)
|
||
goto error1;
|
||
|
||
/* Now try to rename vp to vdst. */
|
||
rc = (*vp->ops->rename) (vp, vdst, true);
|
||
(*vp->ops->close) (vp);
|
||
(*vdst->ops->close) (vdst);
|
||
return rc;
|
||
|
||
error1:
|
||
(*vp->ops->close) (vp);
|
||
error:
|
||
return -1;
|
||
}
|
||
|
||
/* Like `android_renameat_noreplace', but don't check for DST's
|
||
existence and don't accept placeholder SRCFD and DSTFD
|
||
arguments. */
|
||
|
||
int
|
||
android_rename (const char *src, const char *dst)
|
||
{
|
||
struct android_vnode *vp, *vdst;
|
||
int rc;
|
||
|
||
/* Find vnodes for both src and dst. */
|
||
|
||
vp = android_name_file (src);
|
||
if (!vp)
|
||
goto error;
|
||
|
||
vdst = android_name_file (dst);
|
||
if (!vdst)
|
||
goto error1;
|
||
|
||
/* Now try to rename vp to vdst. */
|
||
rc = (*vp->ops->rename) (vp, vdst, false);
|
||
(*vp->ops->close) (vp);
|
||
(*vdst->ops->close) (vdst);
|
||
return rc;
|
||
|
||
error1:
|
||
(*vp->ops->close) (vp);
|
||
error:
|
||
return -1;
|
||
}
|
||
|
||
|
||
|
||
/* fstat, fstatat, faccessat, close/fclose etc. These functions are
|
||
somewhat tricky to wrap: they (at least partially) operate on file
|
||
descriptors, which sometimes provide a base directory for the
|
||
filesystem operations they perform. VFS nodes aren't mapped to
|
||
file descriptors opened through them, which makes this troublesome.
|
||
|
||
openat is not wrapped at all; uses are defined out when Emacs is
|
||
being built for Android. The other functions fall back to directly
|
||
making Unix system calls when their base directory arguments are
|
||
not AT_FDCWD and no directory stream returned from
|
||
`android_opendir' ever returned that file descriptor, which is
|
||
enough to satisfy Emacs's current requirements for those functions
|
||
when a directory file descriptor is supplied.
|
||
|
||
fclose and close are finally wrapped because they need to erase
|
||
information used to link file descriptors with file statistics from
|
||
their origins; fstat is also wrapped to take this information into
|
||
account, so that it can return correct file statistics for asset
|
||
directory files. */
|
||
|
||
/* Like fstat. However, look up the asset corresponding to the file
|
||
descriptor. If it exists, return the right information. */
|
||
|
||
int
|
||
android_fstat (int fd, struct stat *statb)
|
||
{
|
||
struct android_afs_open_fd *tem;
|
||
struct android_parcel_fd *parcel_fd;
|
||
int rc;
|
||
|
||
for (tem = afs_file_descriptors; tem; tem = tem->next)
|
||
{
|
||
if (tem->fd == fd)
|
||
{
|
||
memcpy (statb, &tem->statb, sizeof *statb);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
rc = fstat (fd, statb);
|
||
|
||
/* Now look for a matching parcel file descriptor and use its
|
||
mtime if available. */
|
||
|
||
parcel_fd = open_parcel_fds;
|
||
for (; parcel_fd; parcel_fd = parcel_fd->next)
|
||
{
|
||
if (parcel_fd->fd == fd)
|
||
/* Set STATB->st_dev to a negative device number, signifying
|
||
that it's contained within a content provider. */
|
||
statb->st_dev = -4;
|
||
|
||
if (parcel_fd->fd == fd
|
||
&& timespec_valid_p (parcel_fd->mtime))
|
||
{
|
||
#ifdef STAT_TIMESPEC
|
||
STAT_TIMESPEC (statb, st_mtim) = parcel_fd->mtime;
|
||
#else /* !STAT_TIMESPEC */
|
||
statb->st_mtime = parcel_fd->mtime.tv_sec;
|
||
statb->st_mtime_nsec = parcel_fd->mtime.tv_nsec;
|
||
#endif /* STAT_TIMESPEC */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return rc;
|
||
}
|
||
|
||
/* If DIRFD is a file descriptor returned by `android_readdir' for a
|
||
non-Unix file stream, return FILENAME relative to the file name of
|
||
the directory represented by that stream within BUFFER, a buffer
|
||
SIZE bytes long.
|
||
|
||
Value is 0 if a file name is returned, 1 otherwise. */
|
||
|
||
static int
|
||
android_fstatat_1 (int dirfd, const char *filename,
|
||
char *restrict buffer, size_t size)
|
||
{
|
||
char *dir_name;
|
||
struct android_saf_root_vdir *vdir;
|
||
struct android_saf_tree_vdir *vdir1;
|
||
|
||
/* Now establish whether DIRFD is a file descriptor corresponding to
|
||
an open asset directory stream. */
|
||
|
||
dir_name = android_afs_get_directory_name (dirfd);
|
||
|
||
if (dir_name)
|
||
{
|
||
/* Look for PATHNAME relative to this directory within an asset
|
||
vnode. */
|
||
snprintf (buffer, size, "/assets%s%s", dir_name,
|
||
filename);
|
||
return 0;
|
||
}
|
||
|
||
/* Do the same, but for /content directories instead. */
|
||
|
||
dir_name = android_content_get_directory_name (dirfd);
|
||
|
||
if (dir_name)
|
||
{
|
||
/* Look for PATHNAME relative to this directory within an asset
|
||
vnode. */
|
||
snprintf (buffer, size, "%s/%s", dir_name,
|
||
filename);
|
||
return 0;
|
||
}
|
||
|
||
/* And for /content/storage. */
|
||
|
||
vdir = android_saf_root_get_directory (dirfd);
|
||
|
||
if (vdir)
|
||
{
|
||
if (vdir->authority)
|
||
snprintf (buffer, size, "/content/storage/%s/%s",
|
||
vdir->authority, filename);
|
||
else
|
||
snprintf (buffer, size, "/content/storage/%s",
|
||
filename);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* /content/storage/foo/... */
|
||
|
||
vdir1 = android_saf_tree_get_directory (dirfd);
|
||
|
||
if (vdir1)
|
||
{
|
||
snprintf (buffer, size, "%s%s", vdir1->name, filename);
|
||
return 0;
|
||
}
|
||
|
||
/* /foo... */
|
||
|
||
if (root_fd >= 0 && dirfd == root_fd)
|
||
{
|
||
snprintf (buffer, size, "/%s", filename);
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* If DIRFD is AT_FDCWD or a file descriptor returned by
|
||
`android_dirfd', or PATHNAME is an absolute file name, return the
|
||
file status of the VFS node designated by PATHNAME relative to the
|
||
VFS node corresponding to DIRFD, or relative to the current working
|
||
directory if DIRFD is AT_FDCWD.
|
||
|
||
Otherwise, call `fstatat' with DIRFD, PATHNAME, STATBUF and
|
||
FLAGS. */
|
||
|
||
int
|
||
android_fstatat (int dirfd, const char *restrict pathname,
|
||
struct stat *restrict statbuf, int flags)
|
||
{
|
||
char buffer[EMACS_PATH_MAX + 1];
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
/* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
|
||
never known to Emacs or AT_FDCWD when it originates from a VFS
|
||
node representing a filesystem that supports symlinks. */
|
||
|
||
if (dirfd == AT_FDCWD || pathname[0] == '/')
|
||
goto vfs;
|
||
|
||
/* Now establish whether DIRFD is a file descriptor corresponding to
|
||
an open VFS directory stream. */
|
||
|
||
if (!android_fstatat_1 (dirfd, pathname, buffer, EMACS_PATH_MAX + 1))
|
||
{
|
||
pathname = buffer;
|
||
goto vfs;
|
||
}
|
||
|
||
/* Fall back to fstatat. */
|
||
return fstatat (dirfd, pathname, statbuf, flags);
|
||
|
||
vfs:
|
||
vp = android_name_file (pathname);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->stat) (vp, statbuf);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Like `android_fstatat', but check file accessibility instead of
|
||
status. */
|
||
|
||
int
|
||
android_faccessat (int dirfd, const char *restrict pathname,
|
||
int mode, int flags)
|
||
{
|
||
char buffer[EMACS_PATH_MAX + 1];
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
/* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
|
||
never known to Emacs or AT_FDCWD when it originates from a VFS
|
||
node representing a filesystem that supports symlinks. */
|
||
|
||
if (dirfd == AT_FDCWD || pathname[0] == '/')
|
||
goto vfs;
|
||
|
||
/* Now establish whether DIRFD is a file descriptor corresponding to
|
||
an open VFS directory stream. */
|
||
|
||
if (!android_fstatat_1 (dirfd, pathname, buffer, EMACS_PATH_MAX + 1))
|
||
{
|
||
pathname = buffer;
|
||
goto vfs;
|
||
}
|
||
|
||
/* Fall back to faccessat. */
|
||
return faccessat (dirfd, pathname, mode, flags);
|
||
|
||
vfs:
|
||
vp = android_name_file (pathname);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->access) (vp, mode);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Like `android_fstatat', but set file modes instead of
|
||
checking file status and respect FLAGS. */
|
||
|
||
int
|
||
android_fchmodat (int dirfd, const char *pathname, mode_t mode,
|
||
int flags)
|
||
{
|
||
char buffer[EMACS_PATH_MAX + 1];
|
||
struct android_vnode *vp;
|
||
int rc;
|
||
|
||
if (dirfd == AT_FDCWD || pathname[0] == '/')
|
||
goto vfs;
|
||
|
||
/* Now establish whether DIRFD is a file descriptor corresponding to
|
||
an open VFS directory stream. */
|
||
|
||
if (!android_fstatat_1 (dirfd, pathname, buffer, EMACS_PATH_MAX + 1))
|
||
{
|
||
pathname = buffer;
|
||
goto vfs;
|
||
}
|
||
|
||
/* Fall back to fchmodat. */
|
||
return fchmodat (dirfd, pathname, mode, flags);
|
||
|
||
vfs:
|
||
vp = android_name_file (pathname);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->chmod) (vp, mode, flags);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Like `android_fstatat', but return the target of any symbolic link
|
||
at PATHNAME instead of checking file status. */
|
||
|
||
ssize_t
|
||
android_readlinkat (int dirfd, const char *restrict pathname,
|
||
char *restrict buf, size_t bufsiz)
|
||
{
|
||
char buffer[EMACS_PATH_MAX + 1];
|
||
struct android_vnode *vp;
|
||
ssize_t rc;
|
||
|
||
if (dirfd == AT_FDCWD || pathname[0] == '/')
|
||
goto vfs;
|
||
|
||
/* Now establish whether DIRFD is a file descriptor corresponding to
|
||
an open VFS directory stream. */
|
||
|
||
if (!android_fstatat_1 (dirfd, pathname, buffer, EMACS_PATH_MAX + 1))
|
||
{
|
||
pathname = buffer;
|
||
goto vfs;
|
||
}
|
||
|
||
/* Fall back to readlinkat. */
|
||
return readlinkat (dirfd, pathname, buf, bufsiz);
|
||
|
||
vfs:
|
||
vp = android_name_file (pathname);
|
||
if (!vp)
|
||
return -1;
|
||
|
||
rc = (*vp->ops->readlink) (vp, buf, bufsiz);
|
||
(*vp->ops->close) (vp);
|
||
return rc;
|
||
}
|
||
|
||
/* Like `fdopen', but if FD is a parcel file descriptor, ``detach'' it
|
||
from the original.
|
||
|
||
This is necessary because ownership over parcel file descriptors is
|
||
retained by the ParcelFileDescriptor objects that return them,
|
||
while file streams also require ownership over file descriptors
|
||
they are created on behalf of.
|
||
|
||
Detaching the parcel file descriptor linked to FD consequently
|
||
prevents the owner from being notified when it is eventually
|
||
closed, but for now that hasn't been demonstrated to be problematic
|
||
yet, as Emacs doesn't write to file streams. */
|
||
|
||
FILE *
|
||
android_fdopen (int fd, const char *mode)
|
||
{
|
||
struct android_parcel_fd *tem, **next, *temp;
|
||
int new_fd;
|
||
|
||
for (next = &open_parcel_fds; (tem = *next);)
|
||
{
|
||
if (tem->fd == fd)
|
||
{
|
||
new_fd
|
||
= (*android_java_env)->CallIntMethod (android_java_env,
|
||
tem->descriptor,
|
||
fd_class.detach_fd);
|
||
temp = tem->next;
|
||
xfree (tem);
|
||
*next = temp;
|
||
android_exception_check ();
|
||
|
||
/* Assert that FD (returned from `getFd') is identical to
|
||
the file descriptor returned by `detachFd'. */
|
||
|
||
if (fd != new_fd)
|
||
emacs_abort ();
|
||
|
||
break;
|
||
}
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
return fdopen (fd, mode);
|
||
}
|
||
|
||
/* Like close. However, remove the file descriptor from the asset
|
||
table as well. */
|
||
|
||
int
|
||
android_close (int fd)
|
||
{
|
||
struct android_afs_open_fd *tem, **next, *temp;
|
||
|
||
if (android_close_parcel_fd (fd))
|
||
return 0;
|
||
|
||
for (next = &afs_file_descriptors; (tem = *next);)
|
||
{
|
||
if (tem->fd == fd)
|
||
{
|
||
temp = tem->next;
|
||
xfree (tem);
|
||
*next = temp;
|
||
|
||
break;
|
||
}
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
return close (fd);
|
||
}
|
||
|
||
/* Like fclose. However, remove any information associated with
|
||
FILE's file descriptor from the asset table as well. */
|
||
|
||
int
|
||
android_fclose (FILE *stream)
|
||
{
|
||
int fd;
|
||
struct android_afs_open_fd *tem, **next, *temp;
|
||
|
||
fd = fileno (stream);
|
||
|
||
if (fd == -1)
|
||
goto skip;
|
||
|
||
for (next = &afs_file_descriptors; (tem = *next);)
|
||
{
|
||
if (tem->fd == fd)
|
||
{
|
||
temp = tem->next;
|
||
xfree (*next);
|
||
*next = temp;
|
||
|
||
break;
|
||
}
|
||
else
|
||
next = &(*next)->next;
|
||
}
|
||
|
||
skip:
|
||
return fclose (stream);
|
||
}
|
||
|
||
|
||
|
||
/* External asset management interface. By using functions here
|
||
to read and write from files, Emacs can avoid opening a
|
||
shared memory file descriptor for each ``asset'' file. */
|
||
|
||
/* Like android_open. However, return a structure that can
|
||
either directly hold an AAsset or a file descriptor.
|
||
|
||
Value is the structure upon success. Upon failure, value
|
||
consists of an uninitialized file descriptor, but its asset
|
||
field is set to -1, and errno is set accordingly. */
|
||
|
||
struct android_fd_or_asset
|
||
android_open_asset (const char *filename, int oflag, mode_t mode)
|
||
{
|
||
struct android_fd_or_asset fd;
|
||
AAsset *asset;
|
||
int rc;
|
||
struct android_vnode *vp;
|
||
|
||
/* Now name this file. */
|
||
vp = android_name_file (filename);
|
||
if (!vp)
|
||
goto failure;
|
||
|
||
rc = (*vp->ops->open) (vp, oflag, mode, true, &fd.fd,
|
||
&asset);
|
||
(*vp->ops->close) (vp);
|
||
|
||
/* Upon failure, return fd with its asset field set to (void *)
|
||
-1. */
|
||
|
||
if (rc < 0)
|
||
{
|
||
failure:
|
||
fd.asset = (void *) -1;
|
||
fd.fd = -1;
|
||
return fd;
|
||
}
|
||
|
||
if (rc == 1)
|
||
{
|
||
/* An asset file was returned. Return the structure containing
|
||
an asset. */
|
||
fd.asset = asset;
|
||
fd.fd = -1;
|
||
return fd;
|
||
}
|
||
|
||
/* Otherwise, a file descriptor has been returned. Set fd.asset to
|
||
NULL, signifying that it is a file descriptor. */
|
||
fd.asset = NULL;
|
||
return fd;
|
||
}
|
||
|
||
/* Like android_close. However, it takes a ``file descriptor''
|
||
opened using android_open_asset. */
|
||
|
||
int
|
||
android_close_asset (struct android_fd_or_asset asset)
|
||
{
|
||
if (!asset.asset)
|
||
return android_close (asset.fd);
|
||
|
||
AAsset_close (asset.asset);
|
||
return 0;
|
||
}
|
||
|
||
/* Like `emacs_read_quit'. However, it handles file descriptors
|
||
opened using `android_open_asset' as well. */
|
||
|
||
ssize_t
|
||
android_asset_read_quit (struct android_fd_or_asset asset,
|
||
void *buffer, size_t size)
|
||
{
|
||
if (!asset.asset)
|
||
return emacs_read_quit (asset.fd, buffer, size);
|
||
|
||
/* It doesn't seem possible to quit from inside AAsset_read,
|
||
sadly. */
|
||
return AAsset_read (asset.asset, buffer, size);
|
||
}
|
||
|
||
/* Like `read'. However, it handles file descriptors opened
|
||
using `android_open_asset' as well. */
|
||
|
||
ssize_t
|
||
android_asset_read (struct android_fd_or_asset asset,
|
||
void *buffer, size_t size)
|
||
{
|
||
if (!asset.asset)
|
||
return read (asset.fd, buffer, size);
|
||
|
||
/* It doesn't seem possible to quit from inside AAsset_read,
|
||
sadly. */
|
||
return AAsset_read (asset.asset, buffer, size);
|
||
}
|
||
|
||
/* Like `lseek', but it handles ``file descriptors'' opened with
|
||
android_open_asset. */
|
||
|
||
off_t
|
||
android_asset_lseek (struct android_fd_or_asset asset, off_t off,
|
||
int whence)
|
||
{
|
||
if (!asset.asset)
|
||
return lseek (asset.fd, off, whence);
|
||
|
||
return AAsset_seek (asset.asset, off, whence);
|
||
}
|
||
|
||
/* Like `fstat'. */
|
||
|
||
int
|
||
android_asset_fstat (struct android_fd_or_asset asset,
|
||
struct stat *statb)
|
||
{
|
||
if (!asset.asset)
|
||
return android_fstat (asset.fd, statb);
|
||
|
||
/* Clear statb. */
|
||
memset (statb, 0, sizeof *statb);
|
||
|
||
/* Set the mode. */
|
||
statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||
|
||
/* Concoct a nonexistent device and an inode number. */
|
||
statb->st_dev = -1;
|
||
statb->st_ino = 0;
|
||
|
||
/* Owned by root. */
|
||
statb->st_uid = 0;
|
||
statb->st_gid = 0;
|
||
|
||
/* If the installation date can be ascertained, return that as the
|
||
file's modification time. */
|
||
|
||
if (timespec_valid_p (emacs_installation_time))
|
||
{
|
||
#ifdef STAT_TIMESPEC
|
||
STAT_TIMESPEC (statb, st_mtim) = emacs_installation_time;
|
||
#else /* !STAT_TIMESPEC */
|
||
/* Headers supplied by the NDK r10b contain a `struct stat'
|
||
without POSIX fields for nano-second timestamps. */
|
||
statb->st_mtime = emacs_installation_time.tv_sec;
|
||
statb->st_mtime_nsec = emacs_installation_time.tv_nsec;
|
||
#endif /* STAT_TIMESPEC */
|
||
}
|
||
|
||
/* Size of the file. */
|
||
statb->st_size = AAsset_getLength (asset.asset);
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Directory listing emulation. */
|
||
|
||
/* Open a directory stream from the VFS node designated by NAME.
|
||
Value is NULL upon failure with errno set accordingly. `errno' may
|
||
be set to EINTR.
|
||
|
||
The directory stream returned holds local references to JNI objects
|
||
and shouldn't be used after the current local reference frame is
|
||
popped. */
|
||
|
||
struct android_vdir *
|
||
android_opendir (const char *name)
|
||
{
|
||
struct android_vnode *vp;
|
||
struct android_vdir *dir;
|
||
|
||
vp = android_name_file (name);
|
||
if (!vp)
|
||
return NULL;
|
||
|
||
dir = (*vp->ops->opendir) (vp);
|
||
(*vp->ops->close) (vp);
|
||
return dir;
|
||
}
|
||
|
||
/* Like dirfd. However, value is not a real directory file descriptor
|
||
if DIR is an asset directory. */
|
||
|
||
int
|
||
android_dirfd (struct android_vdir *dirp)
|
||
{
|
||
return (*dirp->dirfd) (dirp);
|
||
}
|
||
|
||
/* Like readdir, but for VFS directory streams instead. */
|
||
|
||
struct dirent *
|
||
android_readdir (struct android_vdir *dirp)
|
||
{
|
||
return (*dirp->readdir) (dirp);
|
||
}
|
||
|
||
/* Like closedir, but for VFS directory streams instead. */
|
||
|
||
void
|
||
android_closedir (struct android_vdir *dirp)
|
||
{
|
||
return (*dirp->closedir) (dirp);
|
||
}
|
||
|
||
|
||
|
||
DEFUN ("android-relinquish-directory-access",
|
||
Fandroid_relinquish_directory_access,
|
||
Sandroid_relinquish_directory_access, 1, 1,
|
||
"DDirectory: ",
|
||
doc: /* Relinquish access to the provided directory.
|
||
DIRECTORY must be the toplevel directory of an open SAF volume (i.e., a
|
||
file under /content/storage), or one of its inferiors. Once the command
|
||
completes, the SAF directory holding this directory will vanish, but no
|
||
files will be removed. */)
|
||
(Lisp_Object file)
|
||
{
|
||
struct android_vnode *vp;
|
||
struct android_saf_tree_vnode *saf_tree;
|
||
jstring string;
|
||
jmethodID method;
|
||
|
||
if (android_get_current_api_level () < 21)
|
||
error ("Emacs can only access or relinquish application storage on"
|
||
" Android 5.0 and later");
|
||
|
||
if (!android_init_gui)
|
||
return Qnil;
|
||
|
||
file = ENCODE_FILE (Fexpand_file_name (file, Qnil));
|
||
|
||
if (!NILP (call1 (Qfile_remote_p, file)))
|
||
signal_error ("Cannot relinquish access to remote file", file);
|
||
|
||
vp = android_name_file (SSDATA (file));
|
||
|
||
if (!vp)
|
||
report_file_error ("Relinquishing directory", file);
|
||
|
||
if (vp->type != ANDROID_VNODE_SAF_TREE)
|
||
{
|
||
(*vp->ops->close) (vp);
|
||
signal_error ("Access to this directory cannot be relinquished",
|
||
file);
|
||
}
|
||
|
||
saf_tree = (struct android_saf_tree_vnode *) vp;
|
||
string = android_build_jstring (saf_tree->tree_uri);
|
||
method = service_class.relinquish_uri_rights;
|
||
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
|
||
emacs_service,
|
||
service_class.class,
|
||
method, string);
|
||
(*vp->ops->close) (vp);
|
||
android_exception_check_1 (string);
|
||
ANDROID_DELETE_LOCAL_REF (string);
|
||
return Qnil;
|
||
}
|
||
|
||
|
||
|
||
void
|
||
syms_of_androidvfs (void)
|
||
{
|
||
DEFSYM (Qandroid_jni, "android-jni");
|
||
|
||
defsubr (&Sandroid_relinquish_directory_access);
|
||
}
|