1 /*
2  * Copyright 2020 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 "os/files.h"
18 
19 #include <fcntl.h>
20 #include <libgen.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 
24 #include <cerrno>
25 #include <cstring>
26 #include <fstream>
27 #include <streambuf>
28 #include <string>
29 
30 #include "os/log.h"
31 
32 namespace {
33 
HandleError(const std::string & temp_path,int * dir_fd,FILE ** fp)34 void HandleError(const std::string& temp_path, int* dir_fd, FILE** fp) {
35   // This indicates there is a write issue.  Unlink as partial data is not
36   // acceptable.
37   unlink(temp_path.c_str());
38   if (*fp) {
39     fclose(*fp);
40     *fp = nullptr;
41   }
42   if (*dir_fd != -1) {
43     close(*dir_fd);
44     *dir_fd = -1;
45   }
46 }
47 
48 }  // namespace
49 
50 namespace bluetooth {
51 namespace os {
52 
FileExists(const std::string & path)53 bool FileExists(const std::string& path) {
54   std::ifstream input(path, std::ios::binary | std::ios::ate);
55   return input.good();
56 }
57 
RenameFile(const std::string & from,const std::string & to)58 bool RenameFile(const std::string& from, const std::string& to) {
59   if (std::rename(from.c_str(), to.c_str()) != 0) {
60     LOG_ERROR("unable to rename file from '%s' to '%s', error: %s", from.c_str(), to.c_str(), strerror(errno));
61     return false;
62   }
63   return true;
64 }
65 
ReadSmallFile(const std::string & path)66 std::optional<std::string> ReadSmallFile(const std::string& path) {
67   std::ifstream input(path, std::ios::binary | std::ios::ate);
68   if (!input) {
69     LOG_WARN("Failed to open file '%s', error: %s", path.c_str(), strerror(errno));
70     return std::nullopt;
71   }
72   auto file_size = input.tellg();
73   if (file_size < 0) {
74     LOG_WARN("Failed to get file size for '%s', error: %s", path.c_str(), strerror(errno));
75     return std::nullopt;
76   }
77   std::string result(file_size, '\0');
78   if (!input.seekg(0)) {
79     LOG_WARN("Failed to go back to the beginning of file '%s', error: %s", path.c_str(), strerror(errno));
80     return std::nullopt;
81   }
82   if (!input.read(result.data(), result.size())) {
83     LOG_WARN("Failed to read file '%s', error: %s", path.c_str(), strerror(errno));
84     return std::nullopt;
85   }
86   input.close();
87   return result;
88 }
89 
WriteToFile(const std::string & path,const std::string & data)90 bool WriteToFile(const std::string& path, const std::string& data) {
91   ASSERT(!path.empty());
92   // Steps to ensure content of data gets to disk:
93   //
94   // 1) Open and write to temp file (e.g. bt_config.conf.new).
95   // 2) Flush the stream buffer to the temp file.
96   // 3) Sync the temp file to disk with fsync().
97   // 4) Rename temp file to actual config file (e.g. bt_config.conf).
98   //    This ensures atomic update.
99   // 5) Sync directory that has the conf file with fsync().
100   //    This ensures directory entries are up-to-date.
101   //
102   // We are using traditional C type file methods because C++ std::filesystem and std::ofstream do not support:
103   // - Operation on directories
104   // - fsync() to ensure content is written to disk
105 
106   // Build temp config file based on config file (e.g. bt_config.conf.new).
107   const std::string temp_path = path + ".new";
108 
109   // Extract directory from file path (e.g. /data/misc/bluedroid).
110   // libc++fs is not supported in APEX yet and hence cannot use std::filesystem::path::parent_path
111   std::string directory_path;
112   {
113     // Make a temporary variable as inputs to dirname() will be modified and return value points to input char array
114     // temp_path_for_dir must not be destroyed until results from dirname is appended to directory_path
115     std::string temp_path_for_dir(path);
116     directory_path.append(dirname(temp_path_for_dir.data()));
117   }
118   if (directory_path.empty()) {
119     LOG_ERROR("error extracting directory from '%s', error: %s", path.c_str(), strerror(errno));
120     return false;
121   }
122 
123   int dir_fd = open(directory_path.c_str(), O_RDONLY | O_DIRECTORY);
124   if (dir_fd < 0) {
125     LOG_ERROR("unable to open dir '%s', error: %s", directory_path.c_str(), strerror(errno));
126     return false;
127   }
128 
129   FILE* fp = std::fopen(temp_path.c_str(), "wt");
130   if (!fp) {
131     LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
132     HandleError(temp_path, &dir_fd, &fp);
133     return false;
134   }
135 
136   if (std::fprintf(fp, "%s", data.c_str()) < 0) {
137     LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
138     HandleError(temp_path, &dir_fd, &fp);
139     return false;
140   }
141 
142   // Flush the stream buffer to the temp file.
143   if (std::fflush(fp) != 0) {
144     LOG_ERROR("unable to write flush buffer to file '%s', error: %s", temp_path.c_str(), strerror(errno));
145     HandleError(temp_path, &dir_fd, &fp);
146     return false;
147   }
148 
149   // Sync written temp file out to disk. fsync() is blocking until data makes it
150   // to disk.
151   if (fsync(fileno(fp)) != 0) {
152     LOG_WARN("unable to fsync file '%s', error: %s", temp_path.c_str(), strerror(errno));
153     // Allow fsync to fail and continue
154   }
155 
156   if (std::fclose(fp) != 0) {
157     LOG_ERROR("unable to close file '%s', error: %s", temp_path.c_str(), strerror(errno));
158     HandleError(temp_path, &dir_fd, &fp);
159     return false;
160   }
161   fp = nullptr;
162 
163   // Change the file's permissions to Read/Write by User and Group
164   if (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) {
165     LOG_ERROR("unable to change file permissions '%s', error: %s", temp_path.c_str(), strerror(errno));
166     HandleError(temp_path, &dir_fd, &fp);
167     return false;
168   }
169 
170   // Rename written temp file to the actual config file.
171   if (std::rename(temp_path.c_str(), path.c_str()) != 0) {
172     LOG_ERROR("unable to commit file from '%s' to '%s', error: %s", temp_path.c_str(), path.c_str(), strerror(errno));
173     HandleError(temp_path, &dir_fd, &fp);
174     return false;
175   }
176 
177   // This should ensure the directory is updated as well.
178   if (fsync(dir_fd) != 0) {
179     LOG_WARN("unable to fsync dir '%s', error: %s", directory_path.c_str(), strerror(errno));
180   }
181 
182   if (close(dir_fd) != 0) {
183     LOG_ERROR("unable to close dir '%s', error: %s", directory_path.c_str(), strerror(errno));
184     HandleError(temp_path, &dir_fd, &fp);
185     return false;
186   }
187   return true;
188 }
189 
190 }  // namespace os
191 }  // namespace bluetooth