Add API to create new containers
[platform/core/security/vasum.git] / common / utils / fs.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Lukasz Pawelczyk <l.pawelczyk@partner.samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author  Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com)
22  * @brief   File utility functions
23  */
24
25 #include "config.hpp"
26 #include "logger/logger.hpp"
27 #include "utils/fs.hpp"
28 #include "utils/paths.hpp"
29 #include "utils/exception.hpp"
30
31 #include <boost/filesystem.hpp>
32 #include <dirent.h>
33 #include <fstream>
34 #include <streambuf>
35 #include <cstring>
36 #include <cerrno>
37 #include <sys/stat.h>
38 #include <sys/mount.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41
42 #include <iostream>
43
44
45 namespace security_containers {
46 namespace utils {
47
48
49 std::string readFileContent(const std::string& path)
50 {
51     std::string result;
52     if (!readFileContent(path, result)) {
53         throw UtilsException();
54     }
55     return result;
56 }
57
58 bool readFileContent(const std::string& path, std::string& result)
59 {
60     std::ifstream file(path);
61
62     if (!file) {
63         LOGD(path << ": could not open for reading");
64         return false;
65     }
66
67     file.seekg(0, std::ios::end);
68     std::streampos length = file.tellg();
69     if (length < 0) {
70         LOGD(path << ": tellg failed");
71         return false;
72     }
73     result.resize(static_cast<size_t>(length));
74     file.seekg(0, std::ios::beg);
75
76     file.read(&result[0], length);
77     if (!file) {
78         LOGD(path << ": read error");
79         result.clear();
80         return false;
81     }
82
83     return true;
84 }
85
86 bool saveFileContent(const std::string& path, const std::string& content)
87 {
88     std::ofstream file(path);
89     if (!file) {
90         LOGD(path << ": could not open for writing");
91         return false;
92     }
93     file.write(content.data(), static_cast<std::streamsize>(content.size()));
94     if (!file) {
95         LOGD(path << ": could not write to");
96         return false;
97     }
98     return true;
99 }
100
101 bool isCharDevice(const std::string& path)
102 {
103     struct stat s;
104     return ::stat(path.c_str(), &s) == 0 && S_IFCHR == (s.st_mode & S_IFMT);
105 }
106
107 namespace {
108 // NOTE: Should be the same as in systemd/src/core/mount-setup.c
109 const std::string RUN_MOUNT_POINT_OPTIONS = "mode=755,smackfstransmute=System::Run";
110 const std::string RUN_MOUNT_POINT_OPTIONS_NO_SMACK = "mode=755";
111 const unsigned long RUN_MOUNT_POINT_FLAGS = MS_NOSUID | MS_NODEV | MS_STRICTATIME;
112
113 bool mountTmpfs(const std::string& path, unsigned long flags, const std::string& options)
114 {
115     if (::mount("tmpfs", path.c_str(), "tmpfs", flags, options.c_str()) != 0) {
116         LOGD("Mount failed for '" << path << "', options=" << options << ": " << strerror(errno));
117         return false;
118     }
119     return true;
120 }
121
122 } // namespace
123
124 bool mountRun(const std::string& path)
125 {
126     return utils::mountTmpfs(path, RUN_MOUNT_POINT_FLAGS, RUN_MOUNT_POINT_OPTIONS)
127            || utils::mountTmpfs(path, RUN_MOUNT_POINT_FLAGS, RUN_MOUNT_POINT_OPTIONS_NO_SMACK);
128 }
129
130 bool umount(const std::string& path)
131 {
132     if (::umount(path.c_str()) != 0) {
133         LOGD("Umount failed for '" << path << "': " << strerror(errno));
134         return false;
135     }
136     return true;
137 }
138
139 bool isMountPoint(const std::string& path, bool& result)
140 {
141     std::string parentPath = dirName(path);
142     bool newResult;
143     bool ret = hasSameMountPoint(path, parentPath, newResult);
144
145     result = !newResult;
146     return ret;
147 }
148
149 bool hasSameMountPoint(const std::string& path1, const std::string& path2, bool& result)
150 {
151     struct stat s1, s2;
152
153     if (::stat(path1.c_str(), &s1)) {
154         LOGD("Failed to get stat of " << path1 << ": " << strerror(errno));
155         return false;
156     }
157
158     if (::stat(path2.c_str(), &s2)) {
159         LOGD("Failed to get stat of " << path2 << ": " << strerror(errno));
160         return false;
161     }
162
163     result = (s1.st_dev == s2.st_dev);
164     return true;
165 }
166
167 bool moveFile(const std::string& src, const std::string& dst)
168 {
169     bool bResult;
170
171     namespace fs = boost::filesystem;
172     boost::system::error_code error;
173
174     // The destination has to be a full path (including a file name)
175     // so it doesn't exist yet, we need to check upper level dir instead.
176     if (!hasSameMountPoint(src, dirName(dst), bResult)) {
177         LOGE("Failed to check the files' mount points");
178         return false;
179     }
180
181     if (bResult) {
182         fs::rename(src, dst, error);
183         if (error) {
184             LOGE("Failed to rename the file: " << error);
185             return false;
186         }
187     } else {
188         fs::copy_file(src, dst, error);
189         if (error) {
190             LOGE("Failed to copy the file: " << error);
191             return false;
192         }
193         fs::remove(src, error);
194         if (error) {
195             LOGE("Failed to remove the file: " << error);
196             fs::remove(dst, error);
197             return false;
198         }
199     }
200
201     return true;
202 }
203
204 namespace {
205
206 bool copyDirContentsRec(const boost::filesystem::path& src, const boost::filesystem::path& dst)
207 {
208     namespace fs = boost::filesystem;
209
210     // TODO: Right now this function skips files which produce error when copying. Errors show up
211     // when:
212     //   a) fs::directory_iterator file(src) is created
213     //   b) fs::copy(...) is called
214     // In both cases lack of permissions is the issue.
215     //
216     // In a) case we can't do much - SCS won't be able to read the directory and its contents. Such
217     // directories are not common in the filesystem, so they *probably* can be skipped.
218     //
219     // In b) case multiple directories have too strict permissions to be directly copied. This
220     // is a problem for some files crucial to container launch (ex. we cannot copy
221     // /usr/lib/systemd/systemd because /usr/lib has 555 permissions).
222     // To fix b) issue, copying must be done in two steps:
223     //   1. Copy file contents without permissions (this probably can be achieved by opening two
224     //      files in-code with fstream and programatically copying data from one file to another).
225     //   2. Apply all available file attributes from source (permissions, owner UID/GID, xattrs...)
226
227     try {
228         for (fs::directory_iterator file(src);
229              file != fs::directory_iterator();
230              ++file) {
231             fs::path current(file->path());
232
233             boost::system::error_code ec;
234             fs::copy(current, dst / current.filename(), ec);
235             if(ec.value() != boost::system::errc::success) {
236                 LOGW("Failed to copy " << current << ": " << ec.message());
237             }
238
239             if (!fs::is_symlink(current) && fs::is_directory(current)) {
240                 if (!copyDirContentsRec(current, dst / current.filename())) {
241                     return false;
242                 }
243             }
244         }
245     } catch (fs::filesystem_error& e) {
246         LOGW(e.what());
247     }
248
249     return true;
250 }
251
252 } // namespace
253
254 bool copyDirContents(const std::string& src, const std::string& dst)
255 {
256     namespace fs = boost::filesystem;
257
258     return copyDirContentsRec(fs::path(src), fs::path(dst));
259 }
260
261 bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem::perms mode)
262 {
263     namespace fs = boost::filesystem;
264
265     fs::path dirPath(path);
266     boost::system::error_code ec;
267     bool runDirCreated = false;
268     if (!fs::exists(dirPath)) {
269         if (!fs::create_directory(dirPath, ec)) {
270             LOGE("Failed to create directory '" << path << "': "
271                  << ec.message());
272             return false;
273         }
274         runDirCreated = true;
275     } else if (!fs::is_directory(dirPath)) {
276         LOGE("Path '" << path << " already exists");
277         return false;
278     }
279
280     // set permissions
281     fs::permissions(dirPath, mode, ec);
282     if (fs::status(dirPath).permissions() != mode) {
283         LOGE("Failed to set permissions to '" << path << "': "
284              << ec.message());
285         return false;
286     }
287
288     // set owner
289     if (::chown(path.c_str(), uid, gid) != 0) {
290         // remove the directory only if it hadn't existed before
291         if (runDirCreated) {
292             fs::remove(dirPath);
293         }
294         LOGE("chown() failed for path '" << path << "': " << strerror(errno));
295         return false;
296     }
297
298     return true;
299 }
300
301 bool createEmptyDir(const std::string& path)
302 {
303     namespace fs = boost::filesystem;
304
305     fs::path dirPath(path);
306     boost::system::error_code ec;
307     bool cleanDirCreated = false;
308
309     if (!fs::exists(dirPath)) {
310         if (!fs::create_directory(dirPath, ec)) {
311             LOGE("Failed to create dir. Error: " << ec.message());
312             return false;
313         }
314         cleanDirCreated = true;
315     } else if (!fs::is_directory(dirPath)) {
316         LOGE("Provided path already exists and is not a dir, cannot create.");
317         return false;
318     }
319
320     if (!cleanDirCreated) {
321         // check if directory is empty if it was already created
322         if (!fs::is_empty(dirPath)) {
323             LOGE("Directory has some data inside, cannot be used.");
324             return false;
325         }
326     }
327
328     return true;
329 }
330
331 } // namespace utils
332 } // namespace security_containers