1 /*
2  * Copyright (C) 2017 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 "common/libs/utils/files.h"
18 
19 #include <android-base/logging.h>
20 
21 #include <array>
22 #include <climits>
23 #include <cstdio>
24 #include <cstdlib>
25 #include <fstream>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <dirent.h>
30 
31 #include "common/libs/fs/shared_fd.h"
32 
33 namespace cuttlefish {
34 
FileExists(const std::string & path)35 bool FileExists(const std::string& path) {
36   struct stat st;
37   return stat(path.c_str(), &st) == 0;
38 }
39 
FileHasContent(const std::string & path)40 bool FileHasContent(const std::string& path) {
41   return FileSize(path) > 0;
42 }
43 
DirectoryExists(const std::string & path)44 bool DirectoryExists(const std::string& path) {
45   struct stat st;
46   if (stat(path.c_str(), &st) == -1) {
47     return false;
48   }
49   if ((st.st_mode & S_IFMT) != S_IFDIR) {
50     return false;
51   }
52   return true;
53 }
54 
IsDirectoryEmpty(const std::string & path)55 bool IsDirectoryEmpty(const std::string& path) {
56   auto direc = ::opendir(path.c_str());
57   if (!direc) {
58     LOG(ERROR) << "IsDirectoryEmpty test failed with " << path
59                << " as it failed to be open" << std::endl;
60     return false;
61   }
62 
63   decltype(::readdir(direc)) sub = nullptr;
64   int cnt {0};
65   while ( (sub = ::readdir(direc)) ) {
66     cnt++;
67     if (cnt > 2) {
68     LOG(ERROR) << "IsDirectoryEmpty test failed with " << path
69                << " as it exists but not empty" << std::endl;
70       return false;
71     }
72   }
73   return true;
74 }
75 
AbsolutePath(const std::string & path)76 std::string AbsolutePath(const std::string& path) {
77   if (path.empty()) {
78     return {};
79   }
80   if (path[0] == '/') {
81     return path;
82   }
83   if (path[0] == '~') {
84     LOG(WARNING) << "Tilde expansion in path " << path <<" is not supported";
85     return {};
86   }
87 
88   std::array<char, PATH_MAX> buffer{};
89   if (!realpath(".", buffer.data())) {
90     LOG(WARNING) << "Could not get real path for current directory \".\""
91                  << ": " << strerror(errno);
92     return {};
93   }
94   return std::string{buffer.data()} + "/" + path;
95 }
96 
FileSize(const std::string & path)97 off_t FileSize(const std::string& path) {
98   struct stat st;
99   if (stat(path.c_str(), &st) == -1) {
100     return 0;
101   }
102   return st.st_size;
103 }
104 
105 // TODO(schuffelen): Use std::filesystem::last_write_time when on C++17
FileModificationTime(const std::string & path)106 std::chrono::system_clock::time_point FileModificationTime(const std::string& path) {
107   struct stat st;
108   if (stat(path.c_str(), &st) == -1) {
109     return std::chrono::system_clock::time_point();
110   }
111   std::chrono::seconds seconds(st.st_mtim.tv_sec);
112   return std::chrono::system_clock::time_point(seconds);
113 }
114 
RenameFile(const std::string & old_name,const std::string & new_name)115 bool RenameFile(const std::string& old_name, const std::string& new_name) {
116   LOG(DEBUG) << "Renaming " << old_name << " to " << new_name;
117   if(rename(old_name.c_str(), new_name.c_str())) {
118     LOG(ERROR) << "File rename failed due to " << strerror(errno);
119     return false;
120   }
121 
122   return true;
123 }
124 
RemoveFile(const std::string & file)125 bool RemoveFile(const std::string& file) {
126   LOG(DEBUG) << "Removing " << file;
127   return remove(file.c_str()) == 0;
128 }
129 
130 
ReadFile(const std::string & file)131 std::string ReadFile(const std::string& file) {
132   std::string contents;
133   std::ifstream in(file, std::ios::in | std::ios::binary);
134   in.seekg(0, std::ios::end);
135   contents.resize(in.tellg());
136   in.seekg(0, std::ios::beg);
137   in.read(&contents[0], contents.size());
138   in.close();
139   return(contents);
140 }
141 
CurrentDirectory()142 std::string CurrentDirectory() {
143   char* path = getcwd(nullptr, 0);
144   std::string ret(path);
145   free(path);
146   return ret;
147 }
148 
SparseFileSizes(const std::string & path)149 FileSizes SparseFileSizes(const std::string& path) {
150   auto fd = SharedFD::Open(path, O_RDONLY);
151   if (!fd->IsOpen()) {
152     LOG(ERROR) << "Could not open \"" << path << "\": " << fd->StrError();
153     return {};
154   }
155   off_t farthest_seek = fd->LSeek(0, SEEK_END);
156   LOG(VERBOSE) << "Farthest seek: " << farthest_seek;
157   if (farthest_seek == -1) {
158     LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError();
159     return {};
160   }
161   off_t data_bytes = 0;
162   off_t offset = 0;
163   while (offset < farthest_seek) {
164     off_t new_offset = fd->LSeek(offset, SEEK_HOLE);
165     if (new_offset == -1) {
166       // ENXIO is returned when there are no more blocks of this type coming.
167       if (fd->GetErrno() == ENXIO) {
168         break;
169       } else {
170         LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError();
171         return {};
172       }
173     } else {
174       data_bytes += new_offset - offset;
175       offset = new_offset;
176     }
177     if (offset >= farthest_seek) {
178       break;
179     }
180     new_offset = fd->LSeek(offset, SEEK_DATA);
181     if (new_offset == -1) {
182       // ENXIO is returned when there are no more blocks of this type coming.
183       if (fd->GetErrno() == ENXIO) {
184         break;
185       } else {
186         LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError();
187         return {};
188       }
189     } else {
190       offset = new_offset;
191     }
192   }
193   return (FileSizes) { .sparse_size = farthest_seek, .disk_size = data_bytes };
194 }
195 
196 }  // namespace cuttlefish
197