• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal.util;
17 
18 import static android.system.Os.readlink;
19 import static android.system.Os.symlink;
20 
21 import android.content.Context;
22 import android.net.Uri;
23 import android.os.Build.VERSION_CODES;
24 import android.system.ErrnoException;
25 import androidx.annotation.RequiresApi;
26 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
27 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUriAdapter;
28 import com.google.android.libraries.mobiledatadownload.file.common.MalformedUriException;
29 import java.io.IOException;
30 
31 /** Utility class to create symlinks (if supported). */
32 @RequiresApi(VERSION_CODES.LOLLIPOP)
33 public final class SymlinkUtil {
SymlinkUtil()34   private SymlinkUtil() {}
35 
36   /**
37    * Creates a symlink at the given link Uri to the given target Uri.
38    *
39    * <p>This method will only work for API level 21+ since this is when Android added a platform
40    * level symlink function.
41    *
42    * @param context the caller's context
43    * @param linkUri location to create the symlink
44    * @param targetUri location where the symlink should point
45    * @throws IOException when symlink could not be created
46    */
47   @RequiresApi(VERSION_CODES.LOLLIPOP)
createSymlink(Context context, Uri linkUri, Uri targetUri)48   public static void createSymlink(Context context, Uri linkUri, Uri targetUri) throws IOException {
49     try {
50       AndroidUriAdapter adapter = AndroidUriAdapter.forContext(context);
51       symlink(
52           adapter.toFile(targetUri).getAbsolutePath(), adapter.toFile(linkUri).getAbsolutePath());
53     } catch (MalformedUriException | ErrnoException e) {
54       // wrap the exception so it isn't explicitly referenced at higher levels that run on
55       // API level 21 or below.
56       throw new IOException("Unable to create symlink", e);
57     }
58   }
59 
60   /**
61    * Reads the given symlink Uri and returns its target Uri.
62    *
63    * <p>This method will only work for API level 21+ since this is when Android added a platform
64    * level readlink function. It wraps around Android's readlink function and exposes it in a
65    * version safe way behind the @RequiresApi annotation.
66    *
67    * <p>NOTE: This method only verifies the given input as a valid symlink and points to a target
68    * uri. However, it makes no guarantees about the target uri (i.e. whether the target exists).
69    *
70    * @param context the caller's context
71    * @param symlinkUri the symlink location that should be checked
72    * @return Uri to the target location of the symlink
73    * @throws IOException when symlink is unable to be checked
74    */
75   @RequiresApi(VERSION_CODES.LOLLIPOP)
readSymlink(Context context, Uri symlinkUri)76   public static Uri readSymlink(Context context, Uri symlinkUri) throws IOException {
77     try {
78       AndroidUriAdapter adapter = AndroidUriAdapter.forContext(context);
79       String targetPath = readlink(adapter.toFile(symlinkUri).getAbsolutePath());
80 
81       if (targetPath == null) {
82         throw new IOException("Unable to read symlink");
83       }
84 
85       return AndroidUri.builder(context)
86           .fromAbsolutePath(targetPath, /* accountManager= */ null)
87           .build();
88     } catch (MalformedUriException | ErrnoException e) {
89       // wrap the exception so it isn't explicitly referenced at higher levels that run on
90       // API level 21 or below.
91       throw new IOException("Unable to read symlink", e);
92     }
93   }
94 }
95