From 65b58251b1b07b50672367c675efdfdc97354c4a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 27 Jul 2023 17:13:39 +0800 Subject: Update Android port * configure.ac (ANDROID_STUBIFY): Add androidvfs.o when building libemacs.so. * doc/emacs/android.texi (Android): Add `Android Document Providers'. (Android Startup): Update the location of the content identifier directory. (Android File System): Describe access to document provider directories. (Android Document Providers): New node. * doc/emacs/emacs.texi (Top): Update the menu for the Android appendix. * java/Makefile.in (filename, install_temp/assets/build_info): Make directory-tree depend on build_info. * java/org/gnu/emacs/EmacsActivity.java (onActivityResult): New function. When a document tree is accepted, persist access to it. * java/org/gnu/emacs/EmacsDirectoryEntry.java (EmacsDirectoryEntry): New struct. * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy): Use EmacsService.buildContentName. * java/org/gnu/emacs/EmacsService.java (getEmacsView, openContentUri) (checkContentUri): Remove excessive debug logging. (buildContentName, getDocumentAuthorities, requestDirectoryAccess) (getDocumentTrees, decodeFileName, documentIdFromName, getTreeUri) (statDocument, accessDocument, openDocumentDirectory, readDirectoryEntry) (openDocument, createDocument): New functions. * lib-src/asset-directory-tool.c: Improve commentary by illustrating the difference between directory and ordinary files. * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags) (struct android_emacs_service, android_extract_long) (android_scan_directory_tree, android_is_directory) (android_get_asset_name, android_url_encode, android_content_name_p) (android_get_content_name, android_check_content_access, android_fstat) (android_fstatat, android_file_access_p, android_hack_asset_fd_fallback) (android_detect_ashmem, android_hack_asset_fd, android_close_on_exec) (android_open, android_close, android_fclose, android_create_lib_link) (android_faccessat, struct android_dir, android_opendir, android_dirfd) (android_readdir, android_closedir, android_lookup_asset_directory_fd) (android_exception_check_3, android_get_current_api_level) (android_open_asset, android_close_asset, android_asset_read_quit) (android_asset_read, android_asset_lseek, android_asset_fstat): Move content and asset related functions to androidvfs.c. (android_init_emacs_service): Obtain handles for new JNI functions. (initEmacsParams): Initialize the VFS layer. (android_request_directory_access): New function. (android_display_toast): Remove unused function. * src/android.h (android_get_current_api_level): Assume that this function never returns less than __ANDROID_API__. (struct android_emacs_service): Move `struct android_emacs_service' here. * src/androidfns.c (Fandroid_request_directory_access): New interactive function. (syms_of_androidfns): Register new subr. * src/androidvfs.c (struct android_vdir, struct android_vops) (struct android_vnode, struct android_special_vnode) (enum android_vnode_type, struct android_cursor_class) (struct emacs_directory_entry_class) (struct android_parcel_file_descriptor_class) (android_init_cursor_class, android_init_entry_class) (android_init_fd_class, android_vfs_canonicalize_name) (struct android_unix_vnode, struct android_unix_vdir, unix_vfs_ops) (android_unix_name, android_unix_vnode, 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_readdir) (android_unix_closedir, android_unix_dirfd, android_unix_opendir) (android_extract_long, android_scan_directory_tree) (android_is_directory, android_init_assets) (android_hack_asset_fd_fallback, android_detect_ashmem) (android_hack_asset_fd, struct android_afs_vnode) (struct android_afs_vdir, struct android_afs_open_fd, afs_vfs_ops) (android_afs_name, android_afs_initial, android_close_on_exec) (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_readdir, android_afs_closedir, android_afs_dirfd) (android_afs_opendir, android_afs_get_directory_name) (struct android_content_vdir, content_vfs_ops) (content_directory_contents, 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_readdir) (android_content_closedir, android_content_dirfd) (android_content_opendir, android_content_get_directory_name) (android_content_initial, android_get_content_name) (android_check_content_access, struct android_authority_vnode) (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_opendir, android_authority_initial) (struct android_saf_root_vnode, struct android_saf_root_vdir) (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_readdir, android_saf_root_closedir) (android_saf_root_dirfd, android_saf_root_opendir) (android_saf_root_initial, android_saf_root_get_directory) (android_saf_stat, android_saf_access) (struct android_saf_tree_vnode, struct android_saf_tree_vdir) (saf_tree_vfs_ops, android_document_id_from_name) (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_opendir_1, android_saf_tree_readdir) (android_saf_tree_closedir, android_saf_tree_dirfd) (android_saf_tree_opendir, android_saf_tree_from_name) (android_saf_tree_get_directory, android_saf_file_vnode) (saf_file_vfs_ops, android_saf_file_name, android_saf_file_open) (android_saf_file_unlink, android_saf_file_rmdir) (android_saf_file_opendir, android_close_parcel_fd) (android_saf_new_vnode, android_saf_new_name, android_saf_new_open) (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_opendir, root_vfs_ops) (special_vnodes, android_root_name, android_name_file) (android_vfs_init, android_open, android_unlink, android_symlink) (android_rmdir, android_mkdir, android_renameat_noreplace) (android_rename, android_fstat, android_fstatat_1, android_fstatat) (android_faccessat, android_fdopen, android_close, android_fclose) (android_open_asset, android_close_asset, android_asset_read_quit) (android_asset_read, android_asset_lseek, android_asset_fstat) (android_opendir, android_dirfd, android_readdir) (android_closedir): Move file system emulation routines here. Introduce a new ``VFS'' layer for translating between Emacs-specific file names and the various disparate interfaces for accessing files on Android. * src/callproc.c (delete_temp_file): * src/charset.c (load_charset_map_from_file): * src/dired.c: * src/emacs.c (Fkill_emacs): * src/fileio.c (check_mutable_filename, Fcopy_file) (Fmake_directory_internal, Fdelete_directory_internal) (Fdelete_file, Frename_file, Fadd_name_to_file) (Fmake_symbolic_link, file_accessible_directory_p, Fset_file_modes) (Fset_file_times, write_region): * src/filelock.c (get_boot_time, rename_lock_file) (create_lock_file, current_lock_owner, unlock_file): * src/image.c (slurp_file, png_load_body, jpeg_load_body): * src/keyboard.c (Fopen_dribble_file): * src/lisp.h: * src/lread.c (Fload): * src/process.c (handle_child_signal): * src/sysdep.c (init_standard_fds, emacs_fopen, emacs_fdopen) (emacs_unlink, emacs_symlink, emacs_rmdir, emacs_mkdir) (emacs_renameat_noreplace, emacs_rename): * src/term.c (Fresume_tty, init_tty): Use and add new wrappers for fopen, fdopen, unlink, symlink, rmdir, mkdir, renameat_norepalce and rename. --- java/org/gnu/emacs/EmacsDirectoryEntry.java | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 java/org/gnu/emacs/EmacsDirectoryEntry.java (limited to 'java/org/gnu/emacs/EmacsDirectoryEntry.java') diff --git a/java/org/gnu/emacs/EmacsDirectoryEntry.java b/java/org/gnu/emacs/EmacsDirectoryEntry.java new file mode 100644 index 00000000000..9c10f2e8771 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDirectoryEntry.java @@ -0,0 +1,33 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +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 . */ + +package org.gnu.emacs; + +/* Structure holding a single ``directory entry'' from a document + provider. */ + +public class EmacsDirectoryEntry +{ + /* The type of this directory entry. 0 means a regular file and 1 + means a directory. */ + public int d_type; + + /* The display name of the file represented. */ + public String d_name; +}; -- cgit v1.3 From 03cf3bbb5c38aa55abd6f7d4860025f7482fcfc3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 28 Jul 2023 12:21:47 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsDirectoryEntry.java (EmacsDirectoryEntry): Make class final. * java/org/gnu/emacs/EmacsService.java (accessDocument) (openDocumentDirectory, openDocument, createDocument): Throw access and IO error exceptions instead of catching them. (createDirectory, deleteDocument): New functions. * src/android.c (android_init_emacs_service): Add new functions. * src/android.h (struct android_emacs_service): Likewise. * src/androidvfs.c (android_saf_exception_check): New function. Translate between Java exceptions and errno values. (android_saf_stat, android_saf_access, android_saf_delete_document) (struct android_saf_tree_vnode, android_document_id_from_name) (android_saf_tree_name, android_saf_tree_rmdir) (android_saf_tree_opendir_1, android_saf_tree_opendir) (android_saf_file_open, android_saf_file_unlink) (android_saf_new_open, android_saf_new_mkdir): Implement missing VFS operations and derive errno values from the type of any exceptions thrown. (android_vfs_init): Initialize exception classes. (android_mkdir, android_fstat): Remove trailing whitespace. --- java/org/gnu/emacs/EmacsDirectoryEntry.java | 2 +- java/org/gnu/emacs/EmacsService.java | 210 ++++++++++-------- src/android.c | 5 + src/android.h | 2 + src/androidvfs.c | 316 +++++++++++++++++++++++++--- 5 files changed, 410 insertions(+), 125 deletions(-) (limited to 'java/org/gnu/emacs/EmacsDirectoryEntry.java') diff --git a/java/org/gnu/emacs/EmacsDirectoryEntry.java b/java/org/gnu/emacs/EmacsDirectoryEntry.java index 9c10f2e8771..75c52e48002 100644 --- a/java/org/gnu/emacs/EmacsDirectoryEntry.java +++ b/java/org/gnu/emacs/EmacsDirectoryEntry.java @@ -22,7 +22,7 @@ package org.gnu.emacs; /* Structure holding a single ``directory entry'' from a document provider. */ -public class EmacsDirectoryEntry +public final class EmacsDirectoryEntry { /* The type of this directory entry. 0 means a regular file and 1 means a directory. */ diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index bc62e050345..aa672994f12 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -1723,9 +1723,12 @@ public final class EmacsService extends Service not: -1, if the file does not exist. - -2, upon a security exception or if WRITABLE the file - is not writable. - -3, upon any other error. */ + -2, if WRITABLE and the file is not writable. + -3, upon any other error. + + In addition, arbitrary runtime exceptions (such as + SecurityException or UnsupportedOperationException) may be + thrown. */ public int accessDocument (String uri, String documentId, boolean writable) @@ -1754,24 +1757,8 @@ public final class EmacsService extends Service Document.COLUMN_MIME_TYPE, }; - try - { - cursor = resolver.query (uriObject, projection, null, - null, null); - } - catch (SecurityException exception) - { - /* A SecurityException can be thrown if Emacs doesn't have - access to uriObject. */ - return -2; - } - catch (UnsupportedOperationException exception) - { - exception.printStackTrace (); - - /* Why is this? */ - return -3; - } + cursor = resolver.query (uriObject, projection, null, + null, null); if (cursor == null || !cursor.moveToFirst ()) return -1; @@ -1816,13 +1803,10 @@ public final class EmacsService extends Service if (writable && (tem & Document.FLAG_SUPPORTS_WRITE) == 0) return -3; } - catch (Exception exception) + finally { - /* Whether or not type errors cause exceptions to be signaled - is defined ``by the implementation of Cursor'', whatever - that means. */ - exception.printStackTrace (); - return -3; + /* Close the cursor if an exception occurs. */ + cursor.close (); } return 0; @@ -1832,7 +1816,11 @@ public final class EmacsService extends Service designated by the specified DOCUMENTID within the tree URI. If DOCUMENTID is NULL, use the document ID within URI itself. - Value is NULL upon failure. */ + Value is NULL upon failure. + + In addition, arbitrary runtime exceptions (such as + SecurityException or UnsupportedOperationException) may be + thrown. */ public Cursor openDocumentDirectory (String uri, String documentId) @@ -1861,25 +1849,8 @@ public final class EmacsService extends Service Document.COLUMN_MIME_TYPE, }; - try - { - cursor = resolver.query (uriObject, projection, null, null, - null); - } - catch (SecurityException exception) - { - /* A SecurityException can be thrown if Emacs doesn't have - access to uriObject. */ - return null; - } - catch (UnsupportedOperationException exception) - { - exception.printStackTrace (); - - /* Why is this? */ - return null; - } - + cursor = resolver.query (uriObject, projection, null, null, + null); /* Return the cursor. */ return cursor; } @@ -1966,11 +1937,15 @@ public final class EmacsService extends Service Value is NULL upon failure or a parcel file descriptor upon success. Call `ParcelFileDescriptor.close' on this file - descriptor instead of using the `close' system call. */ + descriptor instead of using the `close' system call. + + FileNotFoundException and/or SecurityException and + UnsupportedOperationException may be thrown upon failure. */ public ParcelFileDescriptor openDocument (String uri, String documentId, boolean write, boolean truncate) + throws FileNotFoundException { Uri treeUri, documentUri; String mode; @@ -1984,40 +1959,33 @@ public final class EmacsService extends Service documentUri = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId); - try + if (write || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - if (write || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) - { - /* Select the mode used to open the file. `rw' means open - a stat-able file, while `rwt' means that and to - truncate the file as well. */ - - if (truncate) - mode = "rwt"; - else - mode = "rw"; - - fileDescriptor - = resolver.openFileDescriptor (documentUri, mode, - null); - } - else - { - /* Select the mode used to open the file. `openFile' - below means always open a stat-able file. */ + /* Select the mode used to open the file. `rw' means open + a stat-able file, while `rwt' means that and to + truncate the file as well. */ - if (truncate) - /* Invalid mode! */ - return null; - else - mode = "r"; + if (truncate) + mode = "rwt"; + else + mode = "rw"; - fileDescriptor = resolver.openFile (documentUri, mode, null); - } + fileDescriptor + = resolver.openFileDescriptor (documentUri, mode, + null); } - catch (Exception exception) + else { - return null; + /* Select the mode used to open the file. `openFile' + below means always open a stat-able file. */ + + if (truncate) + /* Invalid mode! */ + return null; + else + mode = "r"; + + fileDescriptor = resolver.openFile (documentUri, mode, null); } return fileDescriptor; @@ -2030,11 +1998,15 @@ public final class EmacsService extends Service If DOCUMENTID is NULL, create the document inside the root of that tree. + Either FileNotFoundException, SecurityException or + UnsupportedOperationException may be thrown upon failure. + Return the document ID of the new file upon success, NULL otherwise. */ public String createDocument (String uri, String documentId, String name) + throws FileNotFoundException { String mimeType, separator, mime, extension; int index; @@ -2072,23 +2044,77 @@ public final class EmacsService extends Service = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri, documentId); - try - { - docUri = DocumentsContract.createDocument (resolver, - directoryUri, - mimeType, name); + docUri = DocumentsContract.createDocument (resolver, + directoryUri, + mimeType, name); - if (docUri == null) - return null; + if (docUri == null) + return null; - /* Return the ID of the new document. */ - return DocumentsContract.getDocumentId (docUri); - } - catch (Exception exception) - { - exception.printStackTrace (); - } + /* Return the ID of the new document. */ + return DocumentsContract.getDocumentId (docUri); + } - return null; + /* Like `createDocument', but create a directory instead of an + ordinary document. */ + + public String + createDirectory (String uri, String documentId, String name) + throws FileNotFoundException + { + int index; + Uri directoryUri, docUri; + + /* Now parse URI. */ + directoryUri = Uri.parse (uri); + + if (documentId == null) + documentId = DocumentsContract.getTreeDocumentId (directoryUri); + + /* And build a file URI referring to the directory. */ + + directoryUri + = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri, + documentId); + + /* If name ends with a directory separator character, delete + it. */ + + if (name.endsWith ("/")) + name = name.substring (0, name.length () - 1); + + /* From Android's perspective, directories are just ordinary + documents with the `MIME_TYPE_DIR' type. */ + + docUri = DocumentsContract.createDocument (resolver, + directoryUri, + Document.MIME_TYPE_DIR, + name); + + if (docUri == null) + return null; + + /* Return the ID of the new document. */ + return DocumentsContract.getDocumentId (docUri); + } + + /* Delete the document identified by ID from the document tree + identified by URI. Return 0 upon success and -1 upon + failure. */ + + public int + deleteDocument (String uri, String id) + throws FileNotFoundException + { + Uri uriObject; + + uriObject = Uri.parse (uri); + uriObject = DocumentsContract.buildDocumentUriUsingTree (uriObject, + id); + + if (DocumentsContract.deleteDocument (resolver, uriObject)) + return 0; + + return -1; } }; diff --git a/src/android.c b/src/android.c index 098fa6c383d..687c0b48a2a 100644 --- a/src/android.c +++ b/src/android.c @@ -1577,6 +1577,11 @@ android_init_emacs_service (void) FIND_METHOD (create_document, "createDocument", "(Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;)Ljava/lang/String;"); + FIND_METHOD (create_directory, "createDirectory", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + FIND_METHOD (delete_document, "deleteDocument", + "(Ljava/lang/String;Ljava/lang/String;)I"); #undef FIND_METHOD } diff --git a/src/android.h b/src/android.h index 94a3ad46e74..fd391fa6435 100644 --- a/src/android.h +++ b/src/android.h @@ -279,6 +279,8 @@ struct android_emacs_service jmethodID read_directory_entry; jmethodID open_document; jmethodID create_document; + jmethodID create_directory; + jmethodID delete_document; }; extern JNIEnv *android_java_env; diff --git a/src/androidvfs.c b/src/androidvfs.c index c174c35f02b..2cd50963e97 100644 --- a/src/androidvfs.c +++ b/src/androidvfs.c @@ -276,6 +276,10 @@ static struct emacs_directory_entry_class entry_class; 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 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. */ @@ -3688,6 +3692,85 @@ android_saf_root_get_directory (int dirfd) /* Functions common to both SAF directory and file nodes. */ +/* Check for JNI exceptions, clear them, and set errno accordingly. + Also, free each of the N local references given as arguments if an + exception takes place. + + Value is 1 if an exception has taken place, 0 otherwise. + + If the exception thrown derives from FileNotFoundException, set + errno to ENOENT. + + If the exception thrown derives from SecurityException, set errno + to EACCES. + + If the exception thrown derives from 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; + + env = android_java_env; + va_start (ap, n); + + /* First, check for an exception. */ + + if (!(*env)->ExceptionCheck (env)) + /* No exception has taken place. Return 0. */ + return 0; + + 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)) + errno = ENOENT; + else if ((*env)->IsInstanceOf (env, (jobject) exception, + security_exception)) + errno = EACCES; + else if ((*env)->IsInstanceOf (env, (jobject) exception, + unsupported_operation_exception)) + errno = ENOSYS; + else if ((*env)->IsInstanceOf (env, (jobject) exception, + out_of_memory_error)) + { + ANDROID_DELETE_LOCAL_REF ((jobject) exception); + memory_full (0); + } + else + errno = EIO; + + /* expression is still a local reference! */ + ANDROID_DELETE_LOCAL_REF ((jobject) exception); + return 1; +} + /* Return file status for the document designated by ID_NAME within the document tree identified by URI_NAME. @@ -3727,11 +3810,13 @@ android_saf_stat (const char *uri_name, const char *id_name, if (id) { - android_exception_check_2 (uri, id); + if (android_saf_exception_check (2, uri, id)) + return -1; + ANDROID_DELETE_LOCAL_REF (id); } - else - android_exception_check_1 (uri); + else if (android_saf_exception_check (1, uri)) + return -1; ANDROID_DELETE_LOCAL_REF (uri); @@ -3810,11 +3895,13 @@ android_saf_access (const char *uri_name, const char *id_name, if (id) { - android_exception_check_2 (uri, id); + if (android_saf_exception_check (2, uri, id)) + return -1; + ANDROID_DELETE_LOCAL_REF (id); } - else - android_exception_check_1 (uri); + else if (android_saf_exception_check (1, uri)) + return -1; ANDROID_DELETE_LOCAL_REF (uri); @@ -3840,6 +3927,46 @@ android_saf_access (const char *uri_name, const char *id_name, 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. */ + +static int +android_saf_delete_document (const char *tree, const char *doc_id) +{ + jobject id, uri; + jmethodID method; + jint rc; + + /* Build the strings holding the ID and URI. */ + 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); + + /* Now, try to delete the document. */ + method = service_class.delete_document; + rc = (*android_java_env)->CallIntMethod (android_java_env, + emacs_service, + method, uri, id); + + if (android_saf_exception_check (2, id, uri)) + return -1; + + ANDROID_DELETE_LOCAL_REF (id); + ANDROID_DELETE_LOCAL_REF (uri); + + if (rc) + { + errno = EACCES; + return -1; + } + + return 0; +} + /* SAF directory vnode. A file within a SAF directory tree is @@ -3863,7 +3990,9 @@ struct android_saf_tree_vnode char *tree_id; /* The document ID of the directory represented, or NULL if this is - the root directory of the tree. */ + 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 @@ -4004,7 +4133,7 @@ android_verify_jni_string (const char *name) must name a directory file within TREE_URI. If NAME is not correct for the Java ``modified UTF-8'' coding - system, return -1. + 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 @@ -4014,7 +4143,8 @@ android_verify_jni_string (const char *name) 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. */ + If the designated file can't be located, return -1 and set errno + accordingly. */ static int android_document_id_from_name (const char *tree_uri, char *name, @@ -4023,7 +4153,6 @@ android_document_id_from_name (const char *tree_uri, char *name, jobjectArray result; jstring uri; jbyteArray java_name; - size_t length; jint rc; jmethodID method; const char *doc_id; @@ -4055,7 +4184,10 @@ android_document_id_from_name (const char *tree_uri, char *name, method, uri, java_name, result); - android_exception_check_3 (result, uri, java_name); + + if (android_saf_exception_check (3, result, uri, java_name)) + goto finish; + ANDROID_DELETE_LOCAL_REF (uri); ANDROID_DELETE_LOCAL_REF (java_name); @@ -4178,7 +4310,6 @@ android_saf_tree_name (struct android_vnode *vnode, char *name, /* The document ID can't be found. */ xfree (filename); - errno = ENOENT; return NULL; } @@ -4354,9 +4485,19 @@ android_saf_tree_symlink (const char *target, struct android_vnode *vnode) static int android_saf_tree_rmdir (struct android_vnode *vnode) { - /* TODO */ - errno = ENOSYS; - return -1; + 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); } static int @@ -4412,8 +4553,8 @@ android_saf_tree_mkdir (struct android_vnode *vnode, mode_t mode) /* Open a database Cursor containing each directory entry within the supplied SAF tree vnode VP. - Value is NULL upon failure, a local reference to the Cursor object - otherwise. */ + 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) @@ -4445,11 +4586,13 @@ android_saf_tree_opendir_1 (struct android_saf_tree_vnode *vp) if (id) { - android_exception_check_2 (id, uri); + if (android_saf_exception_check (2, id, uri)) + return NULL; + ANDROID_DELETE_LOCAL_REF (id); } - else - android_exception_check_1 (uri); + else if (android_saf_exception_check (1, uri)) + return NULL; ANDROID_DELETE_LOCAL_REF (uri); @@ -4657,7 +4800,6 @@ android_saf_tree_opendir (struct android_vnode *vnode) { xfree (dir); xfree (dir->name); - errno = ENOENT; return NULL; } @@ -4880,7 +5022,10 @@ android_saf_file_open (struct android_vnode *vnode, int flags, service_class.class, method, uri, id, write, trunc); - android_exception_check_2 (uri, id); + + if (android_saf_exception_check (2, uri, id)) + return -1; + ANDROID_DELETE_LOCAL_REF (uri); ANDROID_DELETE_LOCAL_REF (id); @@ -4953,9 +5098,10 @@ android_saf_file_open (struct android_vnode *vnode, int flags, static int android_saf_file_unlink (struct android_vnode *vnode) { - /* TODO */ - errno = ENOSYS; - return -1; + struct android_saf_file_vnode *vp; + + vp = (struct android_saf_file_vnode *) vnode; + return android_saf_delete_document (vp->tree_uri, vp->document_id); } static int @@ -5114,7 +5260,7 @@ android_saf_new_open (struct android_vnode *vnode, int flags, { errno = ENOENT; return -1; - } + } /* Otherwise, try to create a new document. First, build strings for the name, ID and document URI. */ @@ -5137,7 +5283,9 @@ android_saf_new_open (struct android_vnode *vnode, int flags, service_class.class, method, uri, id, name); - android_exception_check_3 (name, id, uri); + + if (android_saf_exception_check (3, name, id, uri)) + return -1; /* Delete unused local references. */ ANDROID_DELETE_LOCAL_REF (name); @@ -5225,9 +5373,90 @@ android_saf_new_access (struct android_vnode *vnode, int mode) static int android_saf_new_mkdir (struct android_vnode *vnode, mode_t mode) { - /* TODO */ - errno = ENOSYS; - return -1; + 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 struct android_vdir * @@ -5449,6 +5678,29 @@ android_vfs_init (JNIEnv *env, jobject manager) 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, "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); } /* The replacement functions that follow have several major @@ -5606,7 +5858,7 @@ android_mkdir (const char *name, mode_t mode) rc = (*vp->ops->mkdir) (vp, mode); (*vp->ops->close) (vp); - return rc; + return rc; } /* Rename the vnode designated by SRC to the vnode designated by DST. @@ -5724,7 +5976,7 @@ android_fstat (int fd, struct stat *statb) for (tem = afs_file_descriptors; tem; tem = tem->next) { if (tem->fd == fd) - { + { memcpy (statb, &tem->statb, sizeof *statb); return 0; } -- cgit v1.3