diff options
Diffstat (limited to 'src/androidvfs.c')
-rw-r--r-- | src/androidvfs.c | 7479 |
1 files changed, 7479 insertions, 0 deletions
diff --git a/src/androidvfs.c b/src/androidvfs.c new file mode 100644 index 00000000000..3b7fb731e86 --- /dev/null +++ b/src/androidvfs.c @@ -0,0 +1,7479 @@ +/* Android virtual file-system support for GNU Emacs. + +Copyright (C) 2023 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 <linux/ashmem.h> + +#include "android.h" +#include "systime.h" +#include "blockinput.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); +}; + +enum android_vnode_type + { + ANDROID_VNODE_UNIX, + ANDROID_VNODE_AFS, + ANDROID_VNODE_CONTENT, + ANDROID_VNODE_CONTENT_AUTHORITY, + 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; +}; + +/* Structure describing the android.os.ParcelFileDescriptor class used + to wrap file descriptors sent over IPC. */ + +struct android_parcel_file_descriptor_class +{ + jclass class; + jmethodID close; + jmethodID get_fd; + jmethodID detach_fd; +}; + +/* The java.lang.String class. */ +static 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. */ +static 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. Calling + this function is not necessary on Android 4.4 and earlier. */ + +static void +android_init_fd_class (JNIEnv *env) +{ + jclass old; + + 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 +} + + + +/* 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, *copy1, *start, *max, *limit; + size_t token_length, ntokens, i; + 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. */ + + ntokens = 0; + saveptr = NULL; + copy = copy1 = xstrdup (file); + memset (tokens, 0, sizeof tokens); + + while ((token = strtok_r (copy, "/", &saveptr))) + { + copy = NULL; + + /* Make sure ntokens is within bounds. */ + if (ntokens == ARRAYELTS (tokens)) + { + xfree (copy1); + goto fail; + } + + tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1); + memcpy (tokens[ntokens], token, strlen (token) + 1); + ntokens++; + } + + /* Free the copy created for strtok_r. */ + xfree (copy1); + + /* 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); + xfree (dir->asset_file); + 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_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 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", + }; + +/* 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, }, + }; + + /* 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 = 2; + 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++; + + /* 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); +} + + + +/* Content URI management functions. */ + +/* 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. */ + +static char * +android_get_content_name (const char *filename) +{ + char *fill, *buffer; + size_t length; + + /* 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; + } + + /* Prefix FILENAME with content:// and return the buffer containing + that URI. */ + + buffer = xmalloc (sizeof "content://" + length); + sprintf (buffer, "content://%s", 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; +} + + + +/* Content authority-based vnode implementation. + + /contents/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; + } + + /* NAME must be a valid JNI string, so that it can be encoded + properly. */ + + if (android_verify_jni_string (name)) + goto no_entry; + + uri_name = android_get_content_name (name); + 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; + size_t length; + 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 buffer to hold the file name. */ + length = strlen (vp->uri); + string = (*env)->NewByteArray (env, length); + if (!string) + { + (*env)->ExceptionClear (env); + errno = ENOMEM; + return -1; + } + + /* Copy the URI into this byte array. */ + (*env)->SetByteArrayRegion (env, string, 0, length, + (jbyte *) vp->uri); + + /* 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 ((*env)->ExceptionCheck (env)) + { + (*env)->ExceptionClear (env); + errno = ENOMEM; + ANDROID_DELETE_LOCAL_REF (string); + return -1; + } + + /* 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; + goto skip; + } + + if (mode & O_CLOEXEC) + android_close_on_exec (fd); + + skip: + ANDROID_DELETE_LOCAL_REF (string); + + if (fd == -1) + return -1; + + *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); +} + + + +/* 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. */ + 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; + jbyteArray 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)->NewByteArray (android_java_env, + length); + android_exception_check (); + + /* Copy the authority name to that byte array. */ + (*android_java_env)->SetByteArrayRegion (android_java_env, + authority, 0, length, + (jbyte *) vp->authority); + + /* 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; + +/* 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; +} + +/* 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; + + /* Find 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; + + 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); + + /* Figure out how large it is, and then resize dirent to fit. */ + 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 = d_type ? DT_DIR : DT_UNKNOWN; + strcpy (dirent->d_name, chars); + + /* Release the string data and the local reference to STRING. */ + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + (jstring) d_name, + chars); + 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_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; + + /* Find 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); + xfree (dir->name); + 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) +{ + 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) +{ + while (sem_wait (&saf_completion_sem) < 0) + process_pending_signals (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (safPostRequest) (JNIEnv *env, jobject object) +{ + sem_post (&saf_completion_sem); +} + +JNIEXPORT jboolean JNICALL +NATIVE_NAME (ftruncate) (JNIEnv *env, jobject object, jint fd) +{ + 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' that make it return asset + vnodes. */ + +static struct android_vnode *android_root_name (struct android_vnode *, + char *, size_t); + +/* 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_unix_opendir, + }; + +/* Array of special named vnodes. */ + +static struct android_special_vnode special_vnodes[] = + { + { "assets", 6, android_afs_initial, }, + { "content", 7, android_content_initial, }, + }; + +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; + + /* 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)) + 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); + } + + /* Otherwise, continue searching for a vnode normally. */ + return android_unix_name (vnode, name, length); +} + + + +/* File system lookup. */ + +/* Look up the vnode that designates NAME, a file name that is at + least N bytes. + + 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); + + /* 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 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); + + /* 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; + } + + 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); +} |