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 #include "odr_fs_utils.h"
18 
19 #include <dirent.h>
20 #include <ftw.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/statvfs.h>
24 #include <unistd.h>
25 
26 #include <iosfwd>
27 #include <memory>
28 #include <ostream>
29 #include <queue>
30 #include <string>
31 #include <string_view>
32 #include <type_traits>
33 #include <vector>
34 
35 #include <android-base/logging.h>
36 #include <android-base/macros.h>
37 #include <android-base/strings.h>
38 #include <base/os.h>
39 
40 namespace art {
41 namespace odrefresh {
42 
43 // Callback for use with nftw(3) to assist with clearing files and sub-directories.
44 // This method removes files and directories below the top-level directory passed to nftw().
NftwCleanUpCallback(const char * fpath,const struct stat * sb,int typeflag,struct FTW * ftwbuf)45 static int NftwCleanUpCallback(const char* fpath,
46                                [[maybe_unused]] const struct stat* sb,
47                                int typeflag,
48                                struct FTW* ftwbuf) {
49   switch (typeflag) {
50     case FTW_F:
51       return unlink(fpath);
52     case FTW_DP:
53       return (ftwbuf->level == 0) ? 0 : rmdir(fpath);
54     default:
55       return -1;
56   }
57 }
58 
RemoveDirectory(const std::string & dir_path)59 WARN_UNUSED bool RemoveDirectory(const std::string& dir_path) {
60   if (!OS::DirectoryExists(dir_path.c_str())) {
61     return true;
62   }
63 
64   static constexpr int kMaxDescriptors = 4;  // Limit the need for nftw() to re-open descriptors.
65   if (nftw(dir_path.c_str(), NftwCleanUpCallback, kMaxDescriptors, FTW_DEPTH | FTW_MOUNT) != 0) {
66     LOG(ERROR) << "Failed to clean-up '" << dir_path << "'";
67     return false;
68   }
69 
70   if (rmdir(dir_path.c_str()) != 0) {
71     // It's possible that we are not able to remove the directory itself. For example, when
72     // odrefresh is running in CompOS, the staging dir is prepared beforehand passed to the VM as an
73     // FD. In this case, just log and ignore the error. It's okay to keep the directory.
74     LOG(WARNING) << "Failed to delete '" << dir_path << "'";
75   }
76 
77   return true;
78 }
79 
EnsureDirectoryExists(const std::string & absolute_path)80 WARN_UNUSED bool EnsureDirectoryExists(const std::string& absolute_path) {
81   if (absolute_path.empty() || absolute_path[0] != '/') {
82     LOG(ERROR) << "Path not absolute '" << absolute_path << "'";
83     return false;
84   }
85   std::string path;
86   for (const std::string& directory : android::base::Split(absolute_path, "/")) {
87     path.append("/").append(directory);
88     if (!OS::DirectoryExists(path.c_str())) {
89       static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH;
90       if (mkdir(path.c_str(), kDirectoryMode) != 0) {
91         PLOG(ERROR) << "Could not create directory: " << path;
92         return false;
93       }
94     }
95   }
96   return true;
97 }
98 
GetFreeSpace(const std::string & path,uint64_t * bytes)99 bool GetFreeSpace(const std::string& path, uint64_t* bytes) {
100   struct statvfs sv;
101   if (statvfs(path.c_str(), &sv) != 0) {
102     PLOG(ERROR) << "statvfs '" << path << "'";
103     return false;
104   }
105   *bytes = sv.f_bfree * sv.f_bsize;
106   return true;
107 }
108 
GetUsedSpace(const std::string & path,uint64_t * bytes)109 bool GetUsedSpace(const std::string& path, uint64_t* bytes) {
110   static constexpr std::string_view kCurrentDirectory{"."};
111   static constexpr std::string_view kParentDirectory{".."};
112   static constexpr size_t kBytesPerBlock = 512;  // see manual page for stat(2).
113 
114   uint64_t file_bytes = 0;
115   std::queue<std::string> unvisited;
116   unvisited.push(path);
117   while (!unvisited.empty()) {
118     std::string current = unvisited.front();
119     unvisited.pop();
120     std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(current.c_str()), closedir);
121     if (!dir) {
122       continue;
123     }
124     for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
125       std::string_view name{entity->d_name};
126       if (name == kCurrentDirectory || name == kParentDirectory) {
127         continue;
128       }
129       std::string entity_name = current + "/" + entity->d_name;
130       if (entity->d_type == DT_DIR) {
131         unvisited.push(entity_name.c_str());
132       } else if (entity->d_type == DT_REG) {
133         struct stat sb;
134         if (stat(entity_name.c_str(), &sb) != 0) {
135           PLOG(ERROR) << "Failed to stat() file " << entity_name;
136           continue;
137         }
138         file_bytes += sb.st_blocks * kBytesPerBlock;
139       }
140     }
141   }
142   *bytes = file_bytes;
143   return true;
144 }
145 
146 }  // namespace odrefresh
147 }  // namespace art
148