1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media.util; 18 19 import static com.android.providers.media.util.FileUtils.extractDisplayName; 20 import static com.android.providers.media.util.FileUtils.extractRelativePath; 21 import static com.android.providers.media.util.Logging.TAG; 22 23 import android.os.SystemProperties; 24 import android.os.Trace; 25 import android.os.UserHandle; 26 import android.system.ErrnoException; 27 import android.system.Os; 28 import android.util.Log; 29 30 import com.android.providers.media.FileAccessAttributes; 31 32 import java.nio.ByteOrder; 33 import java.util.Optional; 34 35 public class XAttrUtils { 36 37 /** 38 * Path on which {@link XAttrUtils#DATA_MEDIA_XATTR_DIRECTORY_PATH} is set. 39 * /storage/emulated/.. can point to /data/media/.. on ext4/f2fs on modern devices. However, for 40 * legacy devices with sdcardfs, it points to /mnt/runtime/.. which then points to 41 * /data/media/.. sdcardfs does not support xattrs, hence xattrs are set on /data/media/.. path. 42 * 43 * TODO(b/220895679): Add logic to handle external sd cards with primary volume with paths 44 * /mnt/expand/<volume>/media/<user-id>. 45 */ 46 static final String DATA_MEDIA_XATTR_DIRECTORY_PATH = String.format( 47 "/data/media/%s", UserHandle.myUserId()); 48 49 static final int SIZE_OF_FILE_ATTRIBUTES = 18; 50 51 /** 52 * Flag to turn on reading file metadata through xattr in FUSE file open calls 53 */ 54 public static final boolean ENABLE_XATTR_METADATA_FOR_FUSE = 55 SystemProperties.getBoolean("persist.sys.fuse.perf.xattr_metadata_enabled", 56 false); 57 58 /** 59 * XAttribute key against which the file metadata is stored 60 */ 61 public static final String FILE_ACCESS_XATTR_KEY = "user.fattr"; 62 getFileAttributesFromXAttr(String path, String key)63 public static Optional<FileAccessAttributes> getFileAttributesFromXAttr(String path, 64 String key) { 65 Trace.beginSection("XAttrUtils.getFileAttributesFromXAttr"); 66 String relativePathWithDisplayName = DATA_MEDIA_XATTR_DIRECTORY_PATH + "/" 67 + extractRelativePath(path) + extractDisplayName(path); 68 try { 69 return Optional.of(deserializeFileAccessAttributes( 70 Os.getxattr(relativePathWithDisplayName, key))); 71 } catch (ErrnoException e) { 72 Log.w(TAG, 73 String.format("Exception encountered while reading xattr:%s from path:%s.", key, 74 path)); 75 return Optional.empty(); 76 } finally { 77 Trace.endSection(); 78 } 79 } 80 81 /** 82 * Serializes file access attributes into byte array that will be stored in the xattr. 83 * This method serializes only the id, mediaType, isPending, isTrashed and ownerId fields. 84 * @param fileAccessAttributes File attributes to be stored as byte[] in the file inode 85 * @return byte[] 86 */ serializeFileAccessAttributes( FileAccessAttributes fileAccessAttributes)87 public static byte[] serializeFileAccessAttributes( 88 FileAccessAttributes fileAccessAttributes) { 89 byte[] bytes = new byte[SIZE_OF_FILE_ATTRIBUTES]; 90 int offset = 0; 91 ByteOrder byteOrder = ByteOrder.nativeOrder(); 92 93 Memory.pokeLong(bytes, offset, fileAccessAttributes.getId(), byteOrder); 94 offset += Long.BYTES; 95 96 // TODO(b/227753174): Merge mediaType and the booleans in a single byte 97 Memory.pokeInt(bytes, offset, fileAccessAttributes.getMediaType(), byteOrder); 98 offset += Integer.BYTES; 99 100 bytes[offset++] = (byte) (fileAccessAttributes.isPending() ? 1 : 0); 101 bytes[offset++] = (byte) (fileAccessAttributes.isTrashed() ? 1 : 0); 102 103 Memory.pokeInt(bytes, offset, fileAccessAttributes.getOwnerId(), byteOrder); 104 offset += Integer.BYTES; 105 if (offset != SIZE_OF_FILE_ATTRIBUTES) { 106 Log.wtf(TAG, "Error: Serialized byte[] is of unexpected size"); 107 } 108 return bytes; 109 } 110 111 /** 112 * Deserialize the byte[] data into the corresponding fields - id, mediaType, isPending, 113 * isTrashed and ownerId in that order, and returns an instance of FileAccessAttributes 114 * containing this deserialized data. 115 * @param data Data that is read from the file inode as a result of the xattr call 116 * @return FileAccessAttributes 117 */ deserializeFileAccessAttributes(byte[] data)118 public static FileAccessAttributes deserializeFileAccessAttributes(byte[] data) { 119 ByteOrder byteOrder = ByteOrder.nativeOrder(); 120 int offset = 0; 121 122 long id = Memory.peekLong(data, offset, byteOrder); 123 offset += Long.BYTES; 124 125 int mediaType = Memory.peekInt(data, offset, byteOrder); 126 offset += Integer.BYTES; 127 128 boolean isPending = data[offset++] != 0; 129 boolean isTrashed = data[offset++] != 0; 130 131 int ownerId = Memory.peekInt(data, offset, byteOrder); 132 offset += Integer.BYTES; 133 if (offset != SIZE_OF_FILE_ATTRIBUTES) { 134 Log.wtf(TAG, " Error: Deserialized attributes are of unexpected size"); 135 } 136 return new FileAccessAttributes(id, mediaType, isPending, isTrashed, 137 ownerId, null); 138 } 139 } 140