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