Bug 1736331 - Add nsILocalFileMac methods for interacting with extended attributes r=nika

This patch adds support for setting, getting, and reading extended filesystem
attributes on macOS.

Differential Revision: https://phabricator.services.mozilla.com/D133836
This commit is contained in:
Barret Rennie 2021-12-23 23:02:13 +00:00
Родитель f53fe4b9a2
Коммит f93fd9cc79
5 изменённых файлов: 199 добавлений и 6 удалений

Просмотреть файл

@ -168,6 +168,39 @@ interface nsILocalFileMac : nsIFile
* this is the same as lastModifiedTime.
*/
readonly attribute int64_t bundleContentsLastModifiedTime;
/**
* Return whether or not the file has an extended attribute.
*
* @param aAttrName The attribute name to check for.
*
* @return Whether or not the extended attribute is present.
*/
bool hasXAttr(in ACString aAttrName);
/**
* Get the value of the extended attribute.
*
* @param aAttrName The attribute name to read.
*
* @return The extended attribute value.
*/
Array<uint8_t> getXAttr(in ACString aAttrName);
/**
* Set an extended attribute.
*
* @param aAttrName The attribute name to set a value for.
* @param aAttrValue The value to set for the attribute.
*/
void setXAttr(in ACString aAttrName, in Array<uint8_t> aAttrValue);
/**
* Delete an extended attribute.
*
* @param aAttrName The extended attribute to delete.
*/
void delXAttr(in ACString aAttrName);
};
%{C++

Просмотреть файл

@ -109,6 +109,11 @@ inline nsresult nsresultForErrno(int aErr) {
case EFBIG: /* File too large. */
return NS_ERROR_FILE_TOO_BIG;
#ifdef ENOATTR
case ENOATTR:
return NS_ERROR_NOT_AVAILABLE;
#endif // ENOATTR
default:
return NS_ERROR_FAILURE;
}

Просмотреть файл

@ -917,11 +917,7 @@ nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
#if defined(XP_MACOSX)
bool quarantined = true;
if (getxattr(mPath.get(), "com.apple.quarantine", nullptr, 0, 0, 0) == -1) {
if (errno == ENOATTR) {
quarantined = false;
}
}
(void)HasXAttr("com.apple.quarantine"_ns, &quarantined);
#endif
PRFileDesc* oldFD;
@ -1007,7 +1003,7 @@ nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
else if (!quarantined) {
// If the original file was not in quarantine, lift the quarantine that
// file creation added because of LSFileQuarantineEnabled.
removexattr(newPathName.get(), "com.apple.quarantine", 0);
(void)newFile->DelXAttr("com.apple.quarantine"_ns);
}
#endif // defined(XP_MACOSX)
@ -2281,6 +2277,86 @@ nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks,
#ifdef MOZ_WIDGET_COCOA
NS_IMETHODIMP
nsLocalFile::HasXAttr(const nsACString& aAttrName, bool* aHasAttr) {
NS_ENSURE_ARG_POINTER(aHasAttr);
nsAutoCString attrName{aAttrName};
ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0);
if (size == -1) {
if (errno == ENOATTR) {
*aHasAttr = false;
} else {
return NSRESULT_FOR_ERRNO();
}
}
*aHasAttr = true;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetXAttr(const nsACString& aAttrName,
nsTArray<uint8_t>& aAttrValue) {
aAttrValue.Clear();
nsAutoCString attrName{aAttrName};
ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0);
if (size == -1) {
return NSRESULT_FOR_ERRNO();
}
for (;;) {
aAttrValue.SetCapacity(size);
// The attribute can change between our first call and this call, so we need
// to re-check the size and possibly call with a larger buffer.
ssize_t newSize = getxattr(mPath.get(), attrName.get(),
aAttrValue.Elements(), size, 0, 0);
if (newSize == -1) {
return NSRESULT_FOR_ERRNO();
}
if (newSize <= size) {
aAttrValue.SetLength(newSize);
break;
} else {
size = newSize;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetXAttr(const nsACString& aAttrName,
const nsTArray<uint8_t>& aAttrValue) {
nsAutoCString attrName{aAttrName};
if (setxattr(mPath.get(), attrName.get(), aAttrValue.Elements(),
aAttrValue.Length(), 0, 0) == -1) {
return NSRESULT_FOR_ERRNO();
}
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::DelXAttr(const nsACString& aAttrName) {
nsAutoCString attrName{aAttrName};
// Ignore removing an attribute that does not exist.
if (removexattr(mPath.get(), attrName.get(), 0) == -1) {
return NSRESULT_FOR_ERRNO();
}
return NS_OK;
}
static nsresult MacErrorMapper(OSErr inErr) {
nsresult outErr;

Просмотреть файл

@ -0,0 +1,77 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
const { FileUtils } = ChromeUtils.import(
"resource://gre/modules/FileUtils.jsm"
);
async function run_test() {
const path = PathUtils.join(await IOUtils.getTempDir(), "macos-xattrs.tmp.d");
await IOUtils.makeDirectory(path);
try {
test_macos_xattr(path);
} finally {
await IOUtils.remove(path, { recursive: true });
}
}
async function test_macos_xattr(tmpDir) {
const encoder = new TextEncoder();
const path = PathUtils.join(tmpDir, "file.tmp");
await IOUtils.writeUTF8(path, "");
const file = new FileUtils.File(path);
file.queryInterface(Cc.nsILocalFileMac);
info("Testing reading an attribute that does not exist");
Assert.ok(
!file.hasXAttr("bogus.attr"),
"File should not have attribute before being set."
);
Assert.throws(
() => file.getXAttr("bogus.attr"),
/NS_ERROR_NOT_AVAILABLE/,
"Attempting to get an attribute that does not exist throws"
);
{
info("Testing setting and reading an attribute");
file.setXAttr("bogus.attr", encoder.encode("bogus"));
Assert.ok(
file.hasXAttr("bogus.attr"),
"File should have attribute after being set"
);
const result = file.getXAttr("bogus.attr");
Assert.equal(
result,
encoder.encode("bogus"),
"File should have attribute value matching what was set"
);
}
info("Testing removing an attribute");
file.delXAttr("bogus.attr");
Assert.ok(
!file.hasXAttr("bogus.attr"),
"File should no longer have the attribute after removal"
);
Assert.throws(
() => file.getXAttr("bogus.attr"),
/NS_ERROR_NOT_AVAILABLE/,
"Attempting to get an attribute after removal results in an error"
);
info("Testing removing an attribute that does not exist");
Assert.throws(
() => file.delXAttr("bogus.attr"),
/NS_ERROR_NOT_AVAILABLE/,
"Attempting to remove an attribute that does not exist throws"
);
}

Просмотреть файл

@ -31,6 +31,8 @@ fail-if = os == "android"
[test_ioutil.js]
[test_localfile.js]
[test_mac_bundle.js]
[test_mac_xattrs.js]
skip-if = os != "mac"
[test_nsIMutableArray.js]
[test_nsIProcess.js]
skip-if = os == "win" || os == "linux" || os == "android" # bug 582821, bug 1325609, Bug 676998, Bug 1631671