Update Android port
* java/org/gnu/emacs/EmacsSafThread.java (CacheToplevel): (EmacsSafThread): (DocIdEntry): (getCache): (pruneCache): (cacheDirectoryFromCursor): (run): (documentIdFromName1): (statDocument1): (openDocumentDirectory1): (openDocument1): Introduce a file status cache and populate it with files within directories as they are opened. * java/org/gnu/emacs/EmacsService.java (createDocument): (createDirectory): (moveDocument): Invalidate the file status cache wherever needed. * src/fileio.c (check_vfs_filename): (Fset_file_modes): Permit `set-file-modes' to silently fail on asset and content files.
This commit is contained in:
parent
60dda3105c
commit
91a7e9d83f
3 changed files with 280 additions and 126 deletions
|
@ -139,11 +139,39 @@ private static final class CacheToplevel
|
|||
/* Map between document names and children. */
|
||||
HashMap<String, DocIdEntry> children;
|
||||
|
||||
/* Map between document names and file status. */
|
||||
HashMap<String, StatCacheEntry> statCache;
|
||||
|
||||
/* Map between document IDs and cache items. */
|
||||
HashMap<String, CacheEntry> idCache;
|
||||
};
|
||||
|
||||
private final class DocIdEntry
|
||||
private static final class StatCacheEntry
|
||||
{
|
||||
/* The time at which this cache entry was created. */
|
||||
long time;
|
||||
|
||||
/* Flags, size, and modification time of this file. */
|
||||
long flags, size, mtime;
|
||||
|
||||
/* Whether or not this file is a directory. */
|
||||
boolean isDirectory;
|
||||
|
||||
public
|
||||
StatCacheEntry ()
|
||||
{
|
||||
time = SystemClock.uptimeMillis ();
|
||||
}
|
||||
|
||||
public boolean
|
||||
isValid ()
|
||||
{
|
||||
return ((SystemClock.uptimeMillis () - time)
|
||||
< CACHE_INVALID_TIME * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
private static final class DocIdEntry
|
||||
{
|
||||
/* The document ID. */
|
||||
String documentId;
|
||||
|
@ -162,10 +190,14 @@ private final class DocIdEntry
|
|||
containing this entry, and TOPLEVEL is the toplevel
|
||||
representing it. SIGNAL is a cancellation signal.
|
||||
|
||||
RESOLVER is the content provider used to retrieve file
|
||||
information.
|
||||
|
||||
Value is NULL if the file cannot be found. */
|
||||
|
||||
public CacheEntry
|
||||
getCacheEntry (Uri tree, CacheToplevel toplevel,
|
||||
getCacheEntry (ContentResolver resolver, Uri tree,
|
||||
CacheToplevel toplevel,
|
||||
CancellationSignal signal)
|
||||
{
|
||||
Uri uri;
|
||||
|
@ -272,6 +304,7 @@ private static final class CacheEntry
|
|||
|
||||
toplevel = new CacheToplevel ();
|
||||
toplevel.children = new HashMap<String, DocIdEntry> ();
|
||||
toplevel.statCache = new HashMap<String, StatCacheEntry> ();
|
||||
toplevel.idCache = new HashMap<String, CacheEntry> ();
|
||||
cacheToplevels.put (uri, toplevel);
|
||||
return toplevel;
|
||||
|
@ -311,7 +344,9 @@ private static final class CacheEntry
|
|||
pruneCache ()
|
||||
{
|
||||
Iterator<CacheEntry> iter;
|
||||
Iterator<StatCacheEntry> statIter;
|
||||
CacheEntry tem;
|
||||
StatCacheEntry stat;
|
||||
|
||||
for (CacheToplevel toplevel : cacheToplevels.values ())
|
||||
{
|
||||
|
@ -339,6 +374,25 @@ private static final class CacheEntry
|
|||
|
||||
iter.remove ();
|
||||
}
|
||||
|
||||
statIter = toplevel.statCache.values ().iterator ();
|
||||
|
||||
while (statIter.hasNext ())
|
||||
{
|
||||
/* Get the cache entry. */
|
||||
stat = statIter.next ();
|
||||
|
||||
/* If it's not valid anymore, remove it. Iterating over a
|
||||
collection whose contents are being removed is
|
||||
undefined unless the removal is performed using the
|
||||
iterator's own `remove' function, so tem.remove cannot
|
||||
be used here. */
|
||||
|
||||
if (stat.isValid ())
|
||||
continue;
|
||||
|
||||
statIter.remove ();
|
||||
}
|
||||
}
|
||||
|
||||
postPruneMessage ();
|
||||
|
@ -379,8 +433,60 @@ private static final class CacheEntry
|
|||
return cacheEntry;
|
||||
}
|
||||
|
||||
/* Cache file status for DOCUMENTID within TOPLEVEL. Value is the
|
||||
new cache entry. CURSOR is the cursor from where to retrieve the
|
||||
file status, in the form of the columns COLUMN_FLAGS,
|
||||
COLUMN_SIZE, COLUMN_MIME_TYPE and COLUMN_LAST_MODIFIED. */
|
||||
|
||||
private StatCacheEntry
|
||||
cacheFileStatus (String documentId, CacheToplevel toplevel,
|
||||
Cursor cursor)
|
||||
{
|
||||
StatCacheEntry entry;
|
||||
int flagsIndex, columnIndex, typeIndex;
|
||||
int sizeIndex, mtimeIndex;
|
||||
String type;
|
||||
|
||||
/* Obtain the indices for columns wanted from this cursor. */
|
||||
flagsIndex = cursor.getColumnIndex (Document.COLUMN_FLAGS);
|
||||
sizeIndex = cursor.getColumnIndex (Document.COLUMN_SIZE);
|
||||
typeIndex = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
|
||||
mtimeIndex = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED);
|
||||
|
||||
/* COLUMN_LAST_MODIFIED is allowed to be absent in a
|
||||
conforming documents provider. */
|
||||
if (flagsIndex < 0 || sizeIndex < 0 || typeIndex < 0)
|
||||
return null;
|
||||
|
||||
/* Get the file status from CURSOR. */
|
||||
entry = new StatCacheEntry ();
|
||||
entry.flags = cursor.getInt (flagsIndex);
|
||||
type = cursor.getString (typeIndex);
|
||||
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
entry.isDirectory = type.equals (Document.MIME_TYPE_DIR);
|
||||
|
||||
if (cursor.isNull (sizeIndex))
|
||||
/* The size is unknown. */
|
||||
entry.size = -1;
|
||||
else
|
||||
entry.size = cursor.getLong (sizeIndex);
|
||||
|
||||
/* mtimeIndex is potentially unset, since document providers
|
||||
aren't obligated to provide modification times. */
|
||||
|
||||
if (mtimeIndex >= 0 && !cursor.isNull (mtimeIndex))
|
||||
entry.mtime = cursor.getLong (mtimeIndex);
|
||||
|
||||
/* Finally, add this entry to the cache and return. */
|
||||
toplevel.statCache.put (documentId, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/* Cache the type and as many of the children of the directory
|
||||
designated by DOC_ID as possible into TOPLEVEL.
|
||||
designated by DOCUMENTID as possible into TOPLEVEL.
|
||||
|
||||
CURSOR should be a cursor representing an open directory stream,
|
||||
with its projection consisting of at least the display name,
|
||||
|
@ -435,6 +541,12 @@ private static final class CacheEntry
|
|||
idEntry.documentId = id;
|
||||
entry.children.put (id, idEntry);
|
||||
|
||||
/* Cache the file status for ID within TOPELVEL too; if a
|
||||
directory listing is being requested, it's very likely
|
||||
that a series of calls for file status will follow. */
|
||||
|
||||
cacheFileStatus (id, toplevel, cursor);
|
||||
|
||||
/* If this constituent is a directory, don't cache any
|
||||
information about it. It cannot be cached without
|
||||
knowing its children. */
|
||||
|
@ -499,6 +611,7 @@ private static final class CacheEntry
|
|||
|
||||
toplevel = getCache (uri);
|
||||
toplevel.idCache.remove (documentId);
|
||||
toplevel.statCache.remove (documentId);
|
||||
|
||||
/* If the parent of CACHENAME is cached, remove it. */
|
||||
|
||||
|
@ -570,6 +683,7 @@ private static final class CacheEntry
|
|||
|
||||
toplevel = getCache (uri);
|
||||
toplevel.idCache.remove (documentId);
|
||||
toplevel.statCache.remove (documentId);
|
||||
|
||||
/* Now remove DOCUMENTID from CACHENAME's cache entry, if
|
||||
any. */
|
||||
|
@ -619,6 +733,27 @@ private static final class CacheEntry
|
|||
});
|
||||
}
|
||||
|
||||
/* Invalidate the file status cache entry for DOCUMENTID within URI.
|
||||
Call this when the contents of a file (i.e. the constituents of a
|
||||
directory file) may have changed, but the document's display name
|
||||
has not. */
|
||||
|
||||
public void
|
||||
postInvalidateStat (final Uri uri, final String documentId)
|
||||
{
|
||||
handler.post (new Runnable () {
|
||||
@Override
|
||||
public void
|
||||
run ()
|
||||
{
|
||||
CacheToplevel toplevel;
|
||||
|
||||
toplevel = getCache (uri);
|
||||
toplevel.statCache.remove (documentId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ``Prototypes'' for nested functions that are run within the SAF
|
||||
|
@ -857,7 +992,8 @@ public abstract Object runObject (CancellationSignal signal)
|
|||
/* Fetch just the information for this document. */
|
||||
|
||||
if (cache == null)
|
||||
cache = idEntry.getCacheEntry (uri, toplevel, signal);
|
||||
cache = idEntry.getCacheEntry (resolver, uri, toplevel,
|
||||
signal);
|
||||
|
||||
if (cache == null)
|
||||
{
|
||||
|
@ -1082,114 +1218,105 @@ type is either NULL (in which case id should also be NULL) or
|
|||
statDocument1 (String uri, String documentId,
|
||||
CancellationSignal signal)
|
||||
{
|
||||
Uri uriObject;
|
||||
Uri uriObject, tree;
|
||||
String[] projection;
|
||||
long[] stat;
|
||||
int flagsIndex, columnIndex, typeIndex;
|
||||
int sizeIndex, mtimeIndex, flags;
|
||||
long tem;
|
||||
String tem1;
|
||||
Cursor cursor;
|
||||
CacheToplevel toplevel;
|
||||
StatCacheEntry cache;
|
||||
|
||||
uriObject = Uri.parse (uri);
|
||||
tree = Uri.parse (uri);
|
||||
|
||||
if (documentId == null)
|
||||
documentId = DocumentsContract.getTreeDocumentId (uriObject);
|
||||
documentId = DocumentsContract.getTreeDocumentId (tree);
|
||||
|
||||
/* Create a document URI representing DOCUMENTID within URI's
|
||||
authority. */
|
||||
|
||||
uriObject
|
||||
= DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
|
||||
= DocumentsContract.buildDocumentUriUsingTree (tree, documentId);
|
||||
|
||||
/* Now stat this document. */
|
||||
/* See if the file status cache currently contains this
|
||||
document. */
|
||||
|
||||
projection = new String[] {
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_SIZE,
|
||||
};
|
||||
toplevel = getCache (tree);
|
||||
cache = toplevel.statCache.get (documentId);
|
||||
|
||||
cursor = resolver.query (uriObject, projection, null,
|
||||
null, null, signal);
|
||||
|
||||
if (cursor == null)
|
||||
return null;
|
||||
|
||||
/* Obtain the indices for columns wanted from this cursor. */
|
||||
flagsIndex = cursor.getColumnIndex (Document.COLUMN_FLAGS);
|
||||
sizeIndex = cursor.getColumnIndex (Document.COLUMN_SIZE);
|
||||
typeIndex = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
|
||||
mtimeIndex = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED);
|
||||
|
||||
if (!cursor.moveToFirst ()
|
||||
/* COLUMN_LAST_MODIFIED is allowed to be absent in a
|
||||
conforming documents provider. */
|
||||
|| flagsIndex < 0 || sizeIndex < 0 || typeIndex < 0)
|
||||
if (cache == null || !cache.isValid ())
|
||||
{
|
||||
cursor.close ();
|
||||
return null;
|
||||
/* Stat this document and enter its information into the
|
||||
cache. */
|
||||
|
||||
projection = new String[] {
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_SIZE,
|
||||
};
|
||||
|
||||
cursor = resolver.query (uriObject, projection, null,
|
||||
null, null, signal);
|
||||
|
||||
if (cursor == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!cursor.moveToFirst ())
|
||||
return null;
|
||||
|
||||
cache = cacheFileStatus (documentId, toplevel, cursor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cursor.close ();
|
||||
}
|
||||
|
||||
/* If cache is still null, return null. */
|
||||
|
||||
if (cache == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Create the array of file status. */
|
||||
/* Create the array of file status and populate it with the
|
||||
information within cache. */
|
||||
stat = new long[3];
|
||||
|
||||
try
|
||||
stat[0] |= S_IRUSR;
|
||||
if ((cache.flags & Document.FLAG_SUPPORTS_WRITE) != 0)
|
||||
stat[0] |= S_IWUSR;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
&& (cache.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0)
|
||||
stat[0] |= S_IFCHR;
|
||||
|
||||
stat[1] = cache.size;
|
||||
|
||||
/* Check if this is a directory file. */
|
||||
if (cache.isDirectory
|
||||
/* Files shouldn't be specials and directories at the same
|
||||
time, but Android doesn't forbid document providers
|
||||
from returning this information. */
|
||||
&& (stat[0] & S_IFCHR) == 0)
|
||||
{
|
||||
flags = cursor.getInt (flagsIndex);
|
||||
/* Since FLAG_SUPPORTS_WRITE doesn't apply to directories,
|
||||
just assume they're writable. */
|
||||
stat[0] |= S_IFDIR | S_IWUSR | S_IXUSR;
|
||||
|
||||
stat[0] |= S_IRUSR;
|
||||
if ((flags & Document.FLAG_SUPPORTS_WRITE) != 0)
|
||||
stat[0] |= S_IWUSR;
|
||||
/* Directory files cannot be modified if
|
||||
FLAG_DIR_SUPPORTS_CREATE is not set. */
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
&& (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0)
|
||||
stat[0] |= S_IFCHR;
|
||||
|
||||
if (cursor.isNull (sizeIndex))
|
||||
stat[1] = -1; /* The size is unknown. */
|
||||
else
|
||||
stat[1] = cursor.getLong (sizeIndex);
|
||||
|
||||
tem1 = cursor.getString (typeIndex);
|
||||
|
||||
/* Check if this is a directory file. */
|
||||
if (tem1.equals (Document.MIME_TYPE_DIR)
|
||||
/* Files shouldn't be specials and directories at the same
|
||||
time, but Android doesn't forbid document providers
|
||||
from returning this information. */
|
||||
&& (stat[0] & S_IFCHR) == 0)
|
||||
{
|
||||
/* Since FLAG_SUPPORTS_WRITE doesn't apply to directories,
|
||||
just assume they're writable. */
|
||||
stat[0] |= S_IFDIR | S_IWUSR | S_IXUSR;
|
||||
|
||||
/* Directory files cannot be modified if
|
||||
FLAG_DIR_SUPPORTS_CREATE is not set. */
|
||||
|
||||
if ((flags & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
|
||||
stat[0] &= ~S_IWUSR;
|
||||
}
|
||||
|
||||
/* If this file is neither a character special nor a
|
||||
directory, indicate that it's a regular file. */
|
||||
|
||||
if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0)
|
||||
stat[0] |= S_IFREG;
|
||||
|
||||
if (mtimeIndex >= 0 && !cursor.isNull (mtimeIndex))
|
||||
{
|
||||
/* Content providers are allowed to not provide mtime. */
|
||||
tem = cursor.getLong (mtimeIndex);
|
||||
stat[2] = tem;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cursor.close ();
|
||||
if ((cache.flags & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
|
||||
stat[0] &= ~S_IWUSR;
|
||||
}
|
||||
|
||||
/* If this file is neither a character special nor a
|
||||
directory, indicate that it's a regular file. */
|
||||
|
||||
if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0)
|
||||
stat[0] |= S_IFREG;
|
||||
|
||||
stat[2] = cache.mtime;
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
@ -1389,6 +1516,9 @@ In addition, arbitrary runtime exceptions (such as
|
|||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_SIZE,
|
||||
};
|
||||
|
||||
cursor = resolver.query (uriObject, projection, null, null,
|
||||
|
@ -1441,6 +1571,7 @@ In addition, arbitrary runtime exceptions (such as
|
|||
Uri treeUri, documentUri;
|
||||
String mode;
|
||||
ParcelFileDescriptor fileDescriptor;
|
||||
CacheToplevel toplevel;
|
||||
|
||||
treeUri = Uri.parse (uri);
|
||||
|
||||
|
@ -1450,35 +1581,26 @@ In addition, arbitrary runtime exceptions (such as
|
|||
documentUri
|
||||
= DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId);
|
||||
|
||||
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. */
|
||||
|
||||
if (write)
|
||||
{
|
||||
if (truncate)
|
||||
mode = "rwt";
|
||||
else
|
||||
mode = "rw";
|
||||
|
||||
fileDescriptor
|
||||
= resolver.openFileDescriptor (documentUri, mode,
|
||||
signal);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Select the mode used to open the file. `openFile'
|
||||
below means always open a stat-able file. */
|
||||
mode = "r";
|
||||
|
||||
if (truncate)
|
||||
/* Invalid mode! */
|
||||
return null;
|
||||
else
|
||||
mode = "r";
|
||||
fileDescriptor
|
||||
= resolver.openFileDescriptor (documentUri, mode,
|
||||
signal);
|
||||
|
||||
fileDescriptor = resolver.openFile (documentUri, mode,
|
||||
signal);
|
||||
}
|
||||
/* Every time a document is opened, remove it from the file status
|
||||
cache. */
|
||||
toplevel = getCache (treeUri);
|
||||
toplevel.statCache.remove (documentId);
|
||||
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
|
|
@ -1586,7 +1586,7 @@ In addition, arbitrary runtime exceptions (such as
|
|||
String mimeType, separator, mime, extension;
|
||||
int index;
|
||||
MimeTypeMap singleton;
|
||||
Uri directoryUri, docUri;
|
||||
Uri treeUri, directoryUri, docUri;
|
||||
|
||||
/* Try to get the MIME type for this document.
|
||||
Default to ``application/octet-stream''. */
|
||||
|
@ -1608,15 +1608,15 @@ In addition, arbitrary runtime exceptions (such as
|
|||
}
|
||||
|
||||
/* Now parse URI. */
|
||||
directoryUri = Uri.parse (uri);
|
||||
treeUri = Uri.parse (uri);
|
||||
|
||||
if (documentId == null)
|
||||
documentId = DocumentsContract.getTreeDocumentId (directoryUri);
|
||||
documentId = DocumentsContract.getTreeDocumentId (treeUri);
|
||||
|
||||
/* And build a file URI referring to the directory. */
|
||||
|
||||
directoryUri
|
||||
= DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri,
|
||||
= DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
|
||||
documentId);
|
||||
|
||||
docUri = DocumentsContract.createDocument (resolver,
|
||||
|
@ -1626,6 +1626,11 @@ In addition, arbitrary runtime exceptions (such as
|
|||
if (docUri == null)
|
||||
return null;
|
||||
|
||||
/* Invalidate the file status of the containing directory. */
|
||||
|
||||
if (storageThread != null)
|
||||
storageThread.postInvalidateStat (treeUri, documentId);
|
||||
|
||||
/* Return the ID of the new document. */
|
||||
return DocumentsContract.getDocumentId (docUri);
|
||||
}
|
||||
|
@ -1638,18 +1643,18 @@ In addition, arbitrary runtime exceptions (such as
|
|||
throws FileNotFoundException
|
||||
{
|
||||
int index;
|
||||
Uri directoryUri, docUri;
|
||||
Uri treeUri, directoryUri, docUri;
|
||||
|
||||
/* Now parse URI. */
|
||||
directoryUri = Uri.parse (uri);
|
||||
treeUri = Uri.parse (uri);
|
||||
|
||||
if (documentId == null)
|
||||
documentId = DocumentsContract.getTreeDocumentId (directoryUri);
|
||||
documentId = DocumentsContract.getTreeDocumentId (treeUri);
|
||||
|
||||
/* And build a file URI referring to the directory. */
|
||||
|
||||
directoryUri
|
||||
= DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri,
|
||||
= DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
|
||||
documentId);
|
||||
|
||||
/* If name ends with a directory separator character, delete
|
||||
|
@ -1669,7 +1674,12 @@ In addition, arbitrary runtime exceptions (such as
|
|||
if (docUri == null)
|
||||
return null;
|
||||
|
||||
/* Return the ID of the new document. */
|
||||
/* Return the ID of the new document, but first invalidate the
|
||||
state of the containing directory. */
|
||||
|
||||
if (storageThread != null)
|
||||
storageThread.postInvalidateStat (treeUri, documentId);
|
||||
|
||||
return DocumentsContract.getDocumentId (docUri);
|
||||
}
|
||||
|
||||
|
@ -1763,7 +1773,15 @@ In addition, arbitrary runtime exceptions (such as
|
|||
/* Now invalidate the caches for both DIRNAME and DOCID. */
|
||||
|
||||
if (storageThread != null)
|
||||
storageThread.postInvalidateCacheDir (uri1, docId, dirName);
|
||||
{
|
||||
storageThread.postInvalidateCacheDir (uri1, docId, dirName);
|
||||
|
||||
/* Invalidate the stat cache entries for both the source and
|
||||
destination directories, since their contents have
|
||||
changed. */
|
||||
storageThread.postInvalidateStat (uri1, dstId);
|
||||
storageThread.postInvalidateStat (uri1, srcId);
|
||||
}
|
||||
|
||||
return (name != null
|
||||
? DocumentsContract.getDocumentId (name)
|
||||
|
|
30
src/fileio.c
30
src/fileio.c
|
@ -184,9 +184,12 @@ static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
|
|||
|
||||
/* Establish that ENCODED is not contained within a special directory
|
||||
whose contents are not eligible for Unix VFS operations. Signal a
|
||||
`file-error' with REASON if it does. */
|
||||
`file-error' with REASON if it does.
|
||||
|
||||
static void
|
||||
If REASON is NULL, instead return whether ENCODED is contained
|
||||
within such a directory. */
|
||||
|
||||
static bool
|
||||
check_vfs_filename (Lisp_Object encoded, const char *reason)
|
||||
{
|
||||
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
|
||||
|
@ -194,11 +197,16 @@ check_vfs_filename (Lisp_Object encoded, const char *reason)
|
|||
|
||||
name = SSDATA (encoded);
|
||||
|
||||
if (android_is_special_directory (name, "/assets"))
|
||||
xsignal2 (Qfile_error, build_string (reason), encoded);
|
||||
if (android_is_special_directory (name, "/assets")
|
||||
|| android_is_special_directory (name, "/content"))
|
||||
{
|
||||
if (!reason)
|
||||
return true;
|
||||
|
||||
if (android_is_special_directory (name, "/content"))
|
||||
xsignal2 (Qfile_error, build_string (reason), encoded);
|
||||
xsignal2 (Qfile_error, build_string (reason), encoded);
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
|
||||
}
|
||||
|
||||
|
@ -3657,8 +3665,14 @@ command from GNU Coreutils. */)
|
|||
return call4 (handler, Qset_file_modes, absname, mode, flag);
|
||||
|
||||
encoded = ENCODE_FILE (absname);
|
||||
check_vfs_filename (encoded, "Trying to change access modes of file"
|
||||
" within special directory");
|
||||
|
||||
/* Silently ignore attempts to change the access modes of files
|
||||
within /contents on Android, preventing errors within backup file
|
||||
creation. */
|
||||
|
||||
if (check_vfs_filename (encoded, NULL))
|
||||
return Qnil;
|
||||
|
||||
char *fname = SSDATA (encoded);
|
||||
mode_t imode = XFIXNUM (mode) & 07777;
|
||||
if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
|
||||
|
|
Loading…
Add table
Reference in a new issue