xref: /trafficserver/src/tscore/ts_file.cc (revision 4cfd5a73)
1 /** @file
2 
3     Minimalist version of std::filesystem.
4 
5     @section license License
6 
7     Licensed to the Apache Software Foundation (ASF) under one or more contributor license
8     agreements.  See the NOTICE file distributed with this work for additional information regarding
9     copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
10     (the "License"); you may not use this file except in compliance with the License.  You may
11     obtain a copy of the License at
12 
13     http://www.apache.org/licenses/LICENSE-2.0
14 
15     Unless required by applicable law or agreed to in writing, software distributed under the
16     License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
17     express or implied. See the License for the specific language governing permissions and
18     limitations under the License.
19  */
20 
21 #include "tscore/ts_file.h"
22 #include <fcntl.h>
23 #include <sys/types.h>
24 #include <dirent.h>
25 
26 namespace ts
27 {
28 namespace file
29 {
30   path &
operator /=(std::string_view that)31   path::operator/=(std::string_view that)
32   {
33     if (!that.empty()) { // don't waste time appending nothing.
34       if (that.front() == preferred_separator || _path.empty()) {
35         _path.assign(that);
36       } else {
37         if (_path.back() == preferred_separator) {
38           _path.reserve(_path.size() + that.size());
39         } else {
40           _path.reserve(_path.size() + that.size() + 1);
41           _path.push_back(preferred_separator);
42         }
43         _path.append(that);
44       }
45     }
46     return *this;
47   }
48 
49   file_status
status(const path & p,std::error_code & ec)50   status(const path &p, std::error_code &ec) noexcept
51   {
52     file_status zret;
53     if (::stat(p.c_str(), &zret._stat) >= 0) {
54       ec.clear();
55     } else {
56       ec = std::error_code(errno, std::system_category());
57     }
58     return zret;
59   }
60 
61   path
temp_directory_path()62   temp_directory_path()
63   {
64     /* ISO/IEC 9945 (POSIX): The path supplied by the first environment variable found in the list TMPDIR, TMP, TEMP, TEMPDIR.
65      * If none of these are found, "/tmp" */
66     char const *folder = nullptr;
67     if ((nullptr == (folder = getenv("TMPDIR"))) && (nullptr == (folder = getenv("TMP"))) &&
68         (nullptr == (folder = getenv("TEMPDIR")))) {
69       folder = "/tmp";
70     }
71     return path(folder);
72   }
73 
74   path
current_path()75   current_path()
76   {
77     char cwd[PATH_MAX];
78     if (::getcwd(cwd, sizeof(cwd)) != nullptr) {
79       return path(cwd);
80     }
81     return path();
82   }
83 
84   path
canonical(const path & p,std::error_code & ec)85   canonical(const path &p, std::error_code &ec)
86   {
87     if (p.empty()) {
88       ec = std::error_code(EINVAL, std::system_category());
89       return path();
90     }
91 
92     char buf[PATH_MAX + 1];
93     char *res = ::realpath(p.c_str(), buf);
94     if (res) {
95       ec = std::error_code();
96       return path(res);
97     }
98 
99     ec = std::error_code(errno, std::system_category());
100     return path();
101   }
102 
103   bool
exists(const path & p)104   exists(const path &p)
105   {
106     std::error_code ec;
107     status(p, ec);
108     return !(ec && ENOENT == ec.value());
109   }
110 
111   static bool
do_mkdir(const path & p,std::error_code & ec,mode_t mode)112   do_mkdir(const path &p, std::error_code &ec, mode_t mode)
113   {
114     struct stat st;
115     if (stat(p.c_str(), &st) != 0) {
116       if (mkdir(p.c_str(), mode) != 0 && errno != EEXIST) {
117         ec = std::error_code(errno, std::system_category());
118         return false;
119       }
120     } else if (!S_ISDIR(st.st_mode)) {
121       ec = std::error_code(ENOTDIR, std::system_category());
122       return false;
123     }
124     return true;
125   }
126 
127   bool
create_directories(const path & p,std::error_code & ec,mode_t mode)128   create_directories(const path &p, std::error_code &ec, mode_t mode) noexcept
129   {
130     if (p.empty()) {
131       ec = std::error_code(EINVAL, std::system_category());
132       return false;
133     }
134 
135     bool result = false;
136     ec          = std::error_code();
137 
138     size_t pos = 0;
139     std::string token;
140     while ((pos = p.string().find_first_of(p.preferred_separator, pos)) != std::string::npos) {
141       token = p.string().substr(0, pos);
142       if (!token.empty()) {
143         result = do_mkdir(path(token), ec, mode);
144       }
145       pos = pos + sizeof(p.preferred_separator);
146     }
147 
148     if (result) {
149       result = do_mkdir(p, ec, mode);
150     }
151     return result;
152   }
153 
154   bool
copy(const path & from,const path & to,std::error_code & ec)155   copy(const path &from, const path &to, std::error_code &ec)
156   {
157     static int BUF_SIZE = 65536;
158     FILE *src, *dst;
159     char buf[BUF_SIZE];
160     int bufsize = BUF_SIZE;
161 
162     if (from.empty() || to.empty()) {
163       ec = std::error_code(EINVAL, std::system_category());
164       return false;
165     }
166 
167     ec = std::error_code();
168 
169     std::error_code err;
170     path final_to;
171     file_status s = status(to, err);
172     if (!(err && ENOENT == err.value()) && is_dir(s)) {
173       const size_t last_slash_idx = from.string().find_last_of(from.preferred_separator);
174       std::string filename        = from.string().substr(last_slash_idx + 1);
175       final_to                    = to / filename;
176     } else {
177       final_to = to;
178     }
179 
180     if (nullptr == (src = fopen(from.c_str(), "r"))) {
181       ec = std::error_code(errno, std::system_category());
182       return false;
183     }
184     if (nullptr == (dst = fopen(final_to.c_str(), "w"))) {
185       ec = std::error_code(errno, std::system_category());
186       fclose(src);
187       return false;
188     }
189 
190     while (true) {
191       size_t in = fread(buf, 1, bufsize, src);
192       if (0 == in) {
193         break;
194       }
195       size_t out = fwrite(buf, 1, in, dst);
196       if (0 == out) {
197         break;
198       }
199     }
200 
201     fclose(src);
202     fclose(dst);
203 
204     return true;
205   }
206 
207   static bool
remove_path(const path & p,std::error_code & ec)208   remove_path(const path &p, std::error_code &ec)
209   {
210     DIR *dir;
211     struct dirent *entry;
212     bool res = true;
213     std::error_code err;
214 
215     file_status s = status(p, err);
216     if (err && ENOENT == err.value()) {
217       // file/dir does not exist
218       return false;
219     } else if (is_regular_file(s)) {
220       // regular file, try to remove it!
221       if (unlink(p.c_str()) != 0) {
222         ec  = std::error_code(errno, std::system_category());
223         res = false;
224       }
225       return res;
226     } else if (!is_dir(s)) {
227       // not a directory
228       ec = std::error_code(ENOTDIR, std::system_category());
229       return false;
230     }
231 
232     // recursively remove nested files and directories
233     if (nullptr == (dir = opendir(p.c_str()))) {
234       ec = std::error_code(errno, std::system_category());
235       return false;
236     }
237 
238     while (nullptr != (entry = readdir(dir))) {
239       if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
240         continue;
241       }
242 
243       remove_path(p / entry->d_name, ec);
244     }
245 
246     if (0 != rmdir(p.c_str())) {
247       ec = std::error_code(errno, std::system_category());
248     }
249 
250     closedir(dir);
251     return true;
252   }
253 
254   bool
remove(const path & p,std::error_code & ec)255   remove(const path &p, std::error_code &ec)
256   {
257     if (p.empty()) {
258       ec = std::error_code(EINVAL, std::system_category());
259       return false;
260     }
261 
262     ec = std::error_code();
263     return remove_path(p, ec);
264   } // namespace file
265 
266   int
file_type(const file_status & fs)267   file_type(const file_status &fs)
268   {
269     return fs._stat.st_mode & S_IFMT;
270   }
271 
272   time_t
modification_time(const file_status & fs)273   modification_time(const file_status &fs)
274   {
275     return fs._stat.st_mtime;
276   }
277   uintmax_t
file_size(const file_status & fs)278   file_size(const file_status &fs)
279   {
280     return fs._stat.st_size;
281   }
282 
283   bool
is_char_device(const file_status & fs)284   is_char_device(const file_status &fs)
285   {
286     return file_type(fs) == S_IFCHR;
287   }
288 
289   bool
is_block_device(const file_status & fs)290   is_block_device(const file_status &fs)
291   {
292     return file_type(fs) == S_IFBLK;
293   }
294 
295   bool
is_regular_file(const file_status & fs)296   is_regular_file(const file_status &fs)
297   {
298     return file_type(fs) == S_IFREG;
299   }
300 
301   bool
is_dir(const file_status & fs)302   is_dir(const file_status &fs)
303   {
304     return file_type(fs) == S_IFDIR;
305   }
306 
307   bool
is_readable(const path & p)308   is_readable(const path &p)
309   {
310     return 0 == access(p.c_str(), R_OK);
311   }
312 
313   std::string
load(const path & p,std::error_code & ec)314   load(const path &p, std::error_code &ec)
315   {
316     std::string zret;
317     ats_scoped_fd fd(::open(p.c_str(), O_RDONLY));
318     ec.clear();
319     if (fd < 0) {
320       ec = std::error_code(errno, std::system_category());
321     } else {
322       struct stat info;
323       if (0 != ::fstat(fd, &info)) {
324         ec = std::error_code(errno, std::system_category());
325       } else {
326         int n = info.st_size;
327         zret.resize(n);
328         auto read_len = ::read(fd, const_cast<char *>(zret.data()), n);
329         if (read_len < n) {
330           ec = std::error_code(errno, std::system_category());
331         }
332       }
333     }
334     return zret;
335   }
336 
337 } // namespace file
338 } // namespace ts
339