1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "mapped_file.h"
16
17#include <fcntl.h>
18#include <sys/stat.h>
19#include <sys/mman.h>
20#include <unistd.h>
21#include "common_mapped_file_errors.h"
22#include "errors.h"
23#include "file_ex.h"
24#include "utils_log.h"
25
26namespace OHOS {
27namespace Utils {
28off_t MappedFile::pageSize_ = static_cast<off_t>(sysconf(_SC_PAGESIZE));
29
30MappedFile::MappedFile(std::string& path, MapMode mode, off_t offset, off_t size, const char *hint)
31    :path_(path), size_(size), offset_(offset), mode_(mode), hint_(hint) {}
32
33bool MappedFile::ValidMappedSize(off_t& targetSize, const struct stat& stb)
34{
35    off_t max = RoundSize(stb.st_size) - offset_; // Avoid mapped size excessing
36                                                  // that of the file more than a page,
37    if (max > 0) {                                // since write operation on it may raise signal 7.
38        targetSize = targetSize > max ? max : targetSize;
39    } else {
40        return false;
41    }
42
43    return true;
44}
45bool MappedFile::NormalizePath()
46{
47    char canonicalPath[PATH_MAX];
48    if (realpath(path_.c_str(), canonicalPath) == nullptr) {
49        if (errno != ENOENT) {
50            UTILS_LOGE("%{public}s get realpath failed.", __FUNCTION__);
51            return false;
52        }
53    } else {
54        path_ = canonicalPath;
55    }
56
57    return true;
58}
59
60bool MappedFile::NormalizeSize()
61{
62    if (size_ == 0 || size_ < DEFAULT_LENGTH) {
63        UTILS_LOGE("%{public}s: Failed. Invalid mapping size: %{public}lld",
64                   __FUNCTION__, static_cast<long long>(size_));
65        return false;
66    }
67
68    errno = 0;
69    if (!NormalizePath()) {
70        UTILS_LOGE("%{public}s normalize path failed. %{public}s", __FUNCTION__, strerror(errno));
71        return false;
72    }
73    if (!FileExists(path_)) {
74        if ((mode_ & MapMode::CREATE_IF_ABSENT) == MapMode::DEFAULT) {
75            UTILS_LOGE("%{public}s: Failed. %{public}s", __FUNCTION__, strerror(errno));
76            return false;
77        }
78
79        if (size_ == DEFAULT_LENGTH) {
80            size_ = PageSize();
81        }
82
83        isNewFile_ = true;
84    } else {
85        struct stat stb = {0};
86        // Calculate specified mapping size
87        if (stat(path_.c_str(), &stb) != 0) {
88            UTILS_LOGW("%{public}s: Failed. Get file size failed! Mapped size will be that of a page.", __FUNCTION__);
89            size_ = PageSize();
90        }
91
92        if (size_ == DEFAULT_LENGTH) {
93            size_ = stb.st_size;
94        }
95
96        // Get valid size
97        if (!ValidMappedSize(size_, stb)) {
98            UTILS_LOGE("%{public}s: Failed. Invalid params. Specified size: %{public}lld, File size: %{public}lld", \
99                       __FUNCTION__, static_cast<long long>(size_), static_cast<long long>(stb.st_size));
100            return false;
101        }
102    }
103
104    return true;
105}
106
107void MappedFile::NormalizeMode()
108{
109    mode_ &= (MapMode::PRIVATE | MapMode::READ_ONLY | MapMode::CREATE_IF_ABSENT);
110
111    openFlag_ = O_CLOEXEC;
112    if (mode_ == MapMode::DEFAULT) {
113        mapFlag_ = MAP_SHARED;
114        mapProt_ = PROT_READ | PROT_WRITE;
115        openFlag_ |= O_RDWR;
116    } else {
117        if ((mode_ & MapMode::PRIVATE) != MapMode::DEFAULT) {
118            mapFlag_ = MAP_PRIVATE;
119        } else {
120            mapFlag_ = MAP_SHARED;
121        }
122
123        if ((mode_ & MapMode::READ_ONLY) != MapMode::DEFAULT) {
124            mapProt_ = PROT_READ;
125            openFlag_ |= O_RDONLY;
126        } else {
127            mapProt_ = PROT_READ | PROT_WRITE;
128            openFlag_ |= O_RDWR;
129        }
130
131        if ((mode_ & MapMode::CREATE_IF_ABSENT) != MapMode::DEFAULT) {
132            openFlag_ |= O_CREAT;
133        }
134    }
135}
136
137ErrCode MappedFile::Normalize()
138{
139    if (isNormed_) {
140        UTILS_LOGD("%{public}s: Already normalized.", __FUNCTION__);
141        return ERR_INVALID_OPERATION;
142    }
143
144    // resolve params for mapping region
145    // offset
146    if (offset_ < 0 || (offset_ % PageSize() != 0)) {
147        UTILS_LOGE("%{public}s: Failed. Invalid offset: %{public}lld", __FUNCTION__, static_cast<long long>(offset_));
148        return ERR_INVALID_VALUE;
149    }
150
151    // size
152    if (!NormalizeSize()) {
153        UTILS_LOGE("%{public}s: Failed. Cannot normalize size.", __FUNCTION__);
154        return ERR_INVALID_VALUE;
155    }
156
157    // Set open flags, mapping types and protections
158    NormalizeMode();
159
160    isNormed_ = true;
161    return MAPPED_FILE_ERR_OK;
162}
163
164bool MappedFile::OpenFile()
165{
166    int fd = open(path_.c_str(), openFlag_, S_IRWXU | S_IRGRP | S_IROTH);
167    if (fd == -1) {
168        UTILS_LOGE("%{public}s: Failed. Cannot open file - %{public}s.", __FUNCTION__, strerror(errno));
169        return false;
170    }
171
172    if (isNewFile_) {
173        if (!NormalizePath()) {
174            UTILS_LOGE("%{public}s normalize path failed. %{public}s", __FUNCTION__, strerror(errno));
175            return false;
176        }
177        if (ftruncate(fd, EndOffset() + 1) == -1) {
178            UTILS_LOGD("%{public}s: Failed. Cannot change file size: %{public}s.", __FUNCTION__, strerror(errno));
179            if (close(fd) == -1) {
180                UTILS_LOGW("%{public}s: Failed. Cannot close the file: %{public}s.", \
181                           __FUNCTION__, strerror(errno));
182            }
183            if (unlink(path_.c_str()) == -1) {
184                UTILS_LOGW("%{public}s: Failed. Cannot unlink the file: %{public}s.", \
185                           __FUNCTION__, strerror(errno));
186            }
187            return false;
188        }
189        isNewFile_ = false;
190    }
191
192    fd_ = fd;
193    return true;
194}
195
196ErrCode MappedFile::Map()
197{
198    if (isMapped_) {
199        UTILS_LOGD("%{public}s: Failed. Already mapped.", __FUNCTION__);
200        return ERR_INVALID_OPERATION;
201    }
202
203    // Normalize params
204    ErrCode res = Normalize();
205    if (res != MAPPED_FILE_ERR_OK && res != ERR_INVALID_OPERATION) {
206        UTILS_LOGD("%{public}s: Normalize Failed.", __FUNCTION__);
207        return res;
208    }
209
210    // Open file to get its fd
211    if (fd_ == -1) {
212        if (!OpenFile()) {
213            UTILS_LOGD("%{public}s: Open Failed.", __FUNCTION__);
214            return MAPPED_FILE_ERR_FAILED;
215        }
216    }
217
218    // Try map
219    void* data = MAP_FAILED;
220    do {
221        data = mmap(reinterpret_cast<void*>(const_cast<char *>(hint_)),
222                    static_cast<size_t>(size_),
223                    mapProt_,
224                    mapFlag_,
225                    fd_,
226                    offset_);
227        if (data == MAP_FAILED && hint_ != nullptr) {
228            UTILS_LOGW("%{public}s: Mapping Failed. %{public}s, retry with a null hint.", \
229                       __FUNCTION__, strerror(errno));
230            hint_ = nullptr;
231        } else {
232            break;
233        }
234    } while (true);
235
236    if (data == MAP_FAILED) {
237        UTILS_LOGE("%{public}s: Mapping Failed. %{public}s", __FUNCTION__, strerror(errno));
238        return MAPPED_FILE_ERR_FAILED;
239    }
240
241    rStart_ = reinterpret_cast<char*>(data);
242    // set region boundary.
243    rEnd_ = rStart_ + (RoundSize(size_) - 1LL);
244    // set segment start
245    data_ = rStart_;
246    isMapped_ = true;
247
248    return MAPPED_FILE_ERR_OK;
249}
250
251ErrCode MappedFile::Unmap()
252{
253    if (!isMapped_) {
254        UTILS_LOGD("%{public}s: Failed. Already unmapped.", __FUNCTION__);
255        return ERR_INVALID_OPERATION;
256    }
257
258    if (!isNormed_) {
259        UTILS_LOGW("%{public}s. Try unmapping with params changed.", __FUNCTION__);
260    }
261
262    if (munmap(rStart_, static_cast<size_t>(size_)) == -1) {
263        UTILS_LOGD("%{public}s: Failed. %{public}s.", __FUNCTION__, strerror(errno));
264        return MAPPED_FILE_ERR_FAILED;
265    }
266
267    rStart_ = nullptr;
268    rEnd_ = nullptr;
269    data_ = nullptr;
270    isMapped_ = false;
271    return MAPPED_FILE_ERR_OK;
272}
273
274bool MappedFile::SyncFileSize(off_t newSize)
275{
276    if (newSize > size_) {
277        struct stat stb = {0};
278        if (stat(path_.c_str(), &stb) != 0) {
279            UTILS_LOGD("%{public}s: Failed. Cannot get file size: %{public}s.", __FUNCTION__, strerror(errno));
280            return false;
281        } else if (offset_ + newSize <= stb.st_size) {
282            UTILS_LOGW("%{public}s: Failed. Unextend file size, no need to sync.", __FUNCTION__);
283        } else {
284            if (ftruncate(fd_, offset_ + newSize) == -1) {
285                UTILS_LOGD("%{public}s: Failed. Cannot extend file size: %{public}s.", __FUNCTION__, strerror(errno));
286                return false;
287            }
288        }
289    }
290
291    return true;
292}
293
294ErrCode MappedFile::Resize(off_t newSize, bool sync)
295{
296    if (newSize == DEFAULT_LENGTH) {
297        struct stat stb = {0};
298        if (stat(path_.c_str(), &stb) != 0) {
299            UTILS_LOGW("%{public}s: Failed. Get file size failed! Mapped size will be that of a page.", __FUNCTION__);
300            newSize = PageSize();
301        }
302
303        if (newSize == DEFAULT_LENGTH) {
304            newSize = stb.st_size;
305        }
306    }
307
308    if (newSize == 0 || newSize < DEFAULT_LENGTH || newSize == size_) {
309        UTILS_LOGD("%{public}s: Failed. Cannot remap with the same /negative size.", __FUNCTION__);
310        return ERR_INVALID_OPERATION;
311    }
312
313    if (!isMapped_) {
314        UTILS_LOGD("%{public}s: Failed. Invalid status. mapped:%{public}d, normed:%{public}d", \
315                   __FUNCTION__, isMapped_, isNormed_);
316        return ERR_INVALID_OPERATION;
317    }
318
319    if (sync) {
320        if (!SyncFileSize(newSize)) {
321            UTILS_LOGD("%{public}s: Sync Failed.", __FUNCTION__);
322            return MAPPED_FILE_ERR_FAILED;
323        }
324    } else {
325        struct stat stb = {0};
326        if (stat(path_.c_str(), &stb) != 0) {
327            UTILS_LOGW("%{public}s: Failed. Cannot get file size: %{public}s.", __FUNCTION__, strerror(errno));
328            return ERR_INVALID_OPERATION;
329        }
330
331        if (!ValidMappedSize(newSize, stb)) {
332            UTILS_LOGD("%{public}s: Failed. Invalid params.", __FUNCTION__);
333            return ERR_INVALID_VALUE;
334        }
335    }
336
337    void* newData = mremap(rStart_, static_cast<size_t>(size_), static_cast<size_t>(newSize), MREMAP_MAYMOVE);
338    if (newData == MAP_FAILED) {
339        UTILS_LOGD("%{public}s: Failed. %{public}s", __FUNCTION__, strerror(errno));
340        return MAPPED_FILE_ERR_FAILED;
341    }
342
343    rStart_ = reinterpret_cast<char*>(newData);
344    size_ = newSize;
345    // set region boundary.
346    rEnd_ = rStart_ + RoundSize(size_) - 1;
347    // set segment start.
348    data_ = rStart_;
349
350    return MAPPED_FILE_ERR_OK;
351}
352
353ErrCode MappedFile::Resize()
354{
355    if (isMapped_) {
356        UTILS_LOGD("%{public}s: Failed. No param changes detected or unmapping required before resize.", __FUNCTION__);
357        return ERR_INVALID_OPERATION;
358    }
359
360    int res = Map();
361    if (res != MAPPED_FILE_ERR_OK) {
362        UTILS_LOGD("%{public}s: Failed. Remapping failed.", __FUNCTION__);
363        return res;
364    }
365
366    return MAPPED_FILE_ERR_OK;
367}
368
369ErrCode MappedFile::TurnNext()
370{
371    if (!isNormed_) {
372        UTILS_LOGD("%{public}s: Failed. Cannot turnNext with params changed.", __FUNCTION__);
373        return ERR_INVALID_OPERATION;
374    }
375
376    struct stat stb = {0};
377    int ret = stat(path_.c_str(), &stb);
378    if (ret != 0) {
379        UTILS_LOGD("%{public}s: Failed. Get file size failed.", __FUNCTION__);
380        return MAPPED_FILE_ERR_FAILED;
381    }
382    if (EndOffset() + 1 >= stb.st_size) {
383        UTILS_LOGD("%{public}s: Failed. No contents remained.", __FUNCTION__);
384        return ERR_INVALID_OPERATION;
385    }
386
387    // save original params
388    off_t oldSize = size_;
389    off_t oldOff = offset_;
390    const char* oldHint = hint_;
391
392    // if mapped, rStart_ and rEnd_ are viable
393    if (isMapped_) {
394        char* curEnd = End();
395        // case 1: remap needed
396        if (curEnd == rEnd_) {
397            // check if larger than exact file size.
398            if (EndOffset() + 1 + size_ > stb.st_size) {
399                size_ = stb.st_size - EndOffset() - 1;
400            }
401            isNormed_ = false;
402            hint_ = rStart_;
403            offset_ += oldSize;
404
405            ErrCode res = Unmap();
406            if (res == MAPPED_FILE_ERR_OK) {
407                res = Resize();
408            }
409            if (res != MAPPED_FILE_ERR_OK) {
410                UTILS_LOGE("%{public}s Failed. Fail to UnMap/Resize.", __FUNCTION__);
411                // restore
412                offset_ = oldOff;
413                size_ = oldSize;
414                hint_ = oldHint;
415                isNormed_ = true;
416            }
417            return res;
418        }
419
420        // case 2: no need to remap, but to adjust boundary.
421        if (curEnd + oldSize > rEnd_) { // otherwise keep original "size_"
422            size_ = rEnd_ - curEnd;
423        }
424        data_ += oldSize;
425        offset_ += oldSize;
426        return MAPPED_FILE_ERR_OK;
427    }
428
429    // if not mapped, turnNext() will set offset to next page of PageSize()
430    offset_ += PageSize();
431    isNormed_ = false;
432
433    return MAPPED_FILE_ERR_OK;
434}
435
436void MappedFile::Reset()
437{
438    isNormed_ = false;
439    isMapped_ = false;
440    isNewFile_ = false;
441
442    rStart_ = nullptr;
443    rEnd_ = nullptr;
444    data_ = nullptr;
445    path_ = "";
446    size_ = DEFAULT_LENGTH;
447    offset_ = 0;
448    mode_ = MapMode::DEFAULT;
449    fd_ = -1;
450    mapProt_ = 0;
451    mapFlag_ = 0;
452    openFlag_ = 0;
453    hint_ = nullptr;
454}
455
456ErrCode MappedFile::Clear(bool force)
457{
458    if (isMapped_) {
459        ErrCode res = Unmap();
460        if (!force && res != MAPPED_FILE_ERR_OK) {
461            UTILS_LOGD("%{public}s failed. UnMapping Failed.", __FUNCTION__);
462            return res;
463        }
464    }
465
466    if (fd_ != -1 && close(fd_) == -1) {
467        UTILS_LOGD("%{public}s: Failed. Cannot close the file: %{public}s.", \
468                   __FUNCTION__, strerror(errno));
469        return MAPPED_FILE_ERR_FAILED;
470    }
471    Reset();
472    return MAPPED_FILE_ERR_OK;
473}
474
475MappedFile::~MappedFile()
476{
477    if (isMapped_) {
478        ErrCode res = Unmap();
479        if (res != MAPPED_FILE_ERR_OK) {
480            UTILS_LOGW("%{public}s: File unmapping failed, it will be automatically  \
481                       unmapped when the process is terminated.", __FUNCTION__);
482        }
483    }
484
485    if (fd_ != -1 && close(fd_) == -1) {
486        UTILS_LOGE("%{public}s: Failed. Cannot close the file: %{public}s.", \
487                   __FUNCTION__, strerror(errno));
488    }
489}
490
491MappedFile::MappedFile(MappedFile&& other) noexcept
492    : data_(other.data_), rStart_(other.rStart_), rEnd_(other.rEnd_), isMapped_(other.isMapped_),
493    isNormed_(other.isNormed_), isNewFile_(other.isNewFile_), path_(std::move(other.path_)), size_(other.size_),
494    offset_(other.offset_), mode_(other.mode_), fd_(other.fd_), mapProt_(other.mapProt_), mapFlag_(other.mapFlag_),
495    openFlag_(other.openFlag_), hint_(other.hint_)
496{
497    other.Reset();
498}
499
500MappedFile& MappedFile::operator=(MappedFile&& other) noexcept
501{
502    Clear(true);
503
504    data_ = other.data_;
505    rStart_ = other.rStart_;
506    rEnd_ = other.rEnd_;
507    isMapped_ = other.isMapped_;
508    isNormed_ = other.isNormed_;
509    isNewFile_ = other.isNewFile_;
510    path_ = other.path_;
511    size_ = other.size_;
512    offset_ = other.offset_;
513    mode_ = other.mode_;
514    fd_ = other.fd_;
515    mapProt_ = other.mapProt_;
516    mapFlag_ = other.mapFlag_;
517    openFlag_ = other.openFlag_;
518    hint_ = other.hint_;
519
520    other.Reset();
521
522    return *this;
523}
524
525bool MappedFile::ChangeOffset(off_t val)
526{
527    if (offset_ != val) {
528        if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) {
529            offset_ = val;
530            isNormed_ = false;
531
532            return true;
533        }
534
535        UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__);
536    }
537    return false;
538}
539
540bool MappedFile::ChangeSize(off_t val)
541{
542    if ((val > 0 || val == DEFAULT_LENGTH) && size_ != val) {
543        if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) {
544            size_ = val;
545            isNormed_ = false;
546
547            return true;
548        }
549
550        UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__);
551    }
552    return false;
553}
554
555bool MappedFile::ChangePath(const std::string& val)
556{
557    if (path_ != val) {
558        if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) {
559            if (fd_ != -1 && close(fd_) == -1) {
560                UTILS_LOGW("%{public}s: Failed. Cannot close the file: %{public}s.", \
561                           __FUNCTION__, strerror(errno));
562                return false;
563            }
564            path_ = val;
565            isNormed_ = false;
566            fd_ = -1;
567
568            return true;
569        } else {
570            UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__);
571        }
572    }
573    return false;
574}
575
576bool MappedFile::ChangeHint(const char* val)
577{
578    if (hint_ != val) {
579        if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) {
580            hint_ = val;
581            isNormed_ = false;
582
583            return true;
584        } else {
585            UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__);
586        }
587    }
588    return false;
589}
590
591bool MappedFile::ChangeMode(MapMode val)
592{
593    if (mode_ != val) {
594        if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) {
595            mode_ = val;
596            isNormed_ = false;
597
598            return true;
599        } else {
600            UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__);
601        }
602    }
603    return false;
604}
605
606} // namespace Utils
607} // namespace OHOS