1 /*
2  * Copyright (C) 2021 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.tradefed.util;
18 
19 import com.android.annotations.VisibleForTesting;
20 import com.android.tradefed.error.HarnessRuntimeException;
21 import com.android.tradefed.log.LogUtil;
22 import com.android.tradefed.result.error.InfraErrorIdentifier;
23 
24 import java.io.File;
25 
26 /** A helper class for FUSE operations. */
27 public class FuseUtil {
28     static final long FUSE_ZIP_TIMEOUT_MILLIS = 5 * 60 * 1000;
29 
30     private static Boolean sCanMountZip;
31 
32     @VisibleForTesting
resetCanMountZip()33     static void resetCanMountZip() {
34         sCanMountZip = null;
35     }
36 
37     @VisibleForTesting
getRunUtil()38     IRunUtil getRunUtil() {
39         return RunUtil.getDefault();
40     }
41 
42     /**
43      * Returns whether zip mounting is supported.
44      *
45      * <p>Zip mounting is only supported when fuse-zip is available.
46      *
47      * @return true if zip mounting is supported. Otherwise false.
48      */
canMountZip()49     public boolean canMountZip() {
50         if (sCanMountZip == null) {
51             CommandResult res =
52                     getRunUtil().runTimedCmd(FUSE_ZIP_TIMEOUT_MILLIS, "test", "-c", "/dev/fuse");
53             CommandResult res2 =
54                     getRunUtil().runTimedCmd(FUSE_ZIP_TIMEOUT_MILLIS, "which", "fuse-zip");
55             sCanMountZip =
56                     res.getStatus().equals(CommandStatus.SUCCESS)
57                             && res2.getStatus().equals(CommandStatus.SUCCESS)
58                             && !res2.getStdout().isEmpty();
59         }
60         return sCanMountZip.booleanValue();
61     }
62 
63     /**
64      * Mount a zip file as a file system in read-only mode.
65      *
66      * @param zipFile a zip file to mount.
67      * @param mountDir a mount point.
68      */
mountZip(File zipFile, File mountDir)69     public void mountZip(File zipFile, File mountDir) {
70         CommandResult res =
71                 getRunUtil()
72                         .runTimedCmd(
73                                 FUSE_ZIP_TIMEOUT_MILLIS,
74                                 "fuse-zip",
75                                 "-r",
76                                 zipFile.getAbsolutePath(),
77                                 mountDir.getAbsolutePath());
78         if (!res.getStatus().equals(CommandStatus.SUCCESS)) {
79             throw new HarnessRuntimeException(
80                     String.format("Failed to mount %s: %s", zipFile, res.getStderr()),
81                     InfraErrorIdentifier.LAB_HOST_FILESYSTEM_ERROR);
82         }
83     }
84 
85     /**
86      * Unmount a mounted zip file.
87      *
88      * @param mountDir a mount point.
89      */
unmountZip(File mountDir)90     public void unmountZip(File mountDir) {
91         CommandResult res =
92                 getRunUtil()
93                         .runTimedCmd(
94                                 FUSE_ZIP_TIMEOUT_MILLIS,
95                                 "fusermount",
96                                 "-u",
97                                 mountDir.getAbsolutePath());
98         if (!res.getStatus().equals(CommandStatus.SUCCESS)) {
99             LogUtil.CLog.w("Failed to unmount %s: %s", mountDir, res.getStderr());
100         }
101     }
102 }
103