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 @@ /* 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or 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. */ + /* 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); - } + if (truncate) + mode = "rwt"; else - { - /* Select the mode used to open the file. `openFile' - below means always open a stat-able file. */ + mode = "rw"; - if (truncate) - /* Invalid mode! */ - return null; - else - mode = "r"; - - 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 @@ type is either NULL (in which case id should also be NULL) or 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 @@ type is either NULL (in which case id should also be NULL) or = 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; }