1/*
2 * Copyright (c) 2024 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
16#include "hibernate.h"
17
18#include <fstream>
19#include <sstream>
20#include <string>
21#include <vector>
22#include <cinttypes>
23#include <cstdio>
24#include <thread>
25#include <fcntl.h>
26#include <securec.h>
27#include <sys/stat.h>
28#include <sys/random.h>
29#include <sys/swap.h>
30#include <sys/sysinfo.h>
31#include <linux/fs.h>
32#include <linux/fiemap.h>
33#include <unistd.h>
34#include <sys/ioctl.h>
35#include <mntent.h>
36
37#include <unique_fd.h>
38#include <hdf_base.h>
39#include <hdf_log.h>
40#include <file_ex.h>
41
42
43namespace OHOS {
44namespace HDI {
45namespace Power {
46namespace V1_2 {
47
48#ifndef HMFS_IOCTL_MAGIC
49#define HMFS_IOCTL_MAGIC 0xf5
50#endif
51
52#define HMFS_IOC_SWAPFILE_PREALLOC _IOWR(HMFS_IOCTL_MAGIC, 32, uint32_t)
53
54constexpr int32_t SWAP_HEADER_INFO_VERSION = 1;
55constexpr int32_t SWAP_HEADER_INFO_UUID_OFFSET = 3;
56constexpr int32_t SWAP_HEADER_MAGIC_SIZE = 10;
57constexpr int32_t SWAP_HEADER_SIZE = 129;
58constexpr int32_t SWAP_HEADER_INFO_BOOTBITS_SIZE = 1024;
59constexpr int32_t SWAP_HEADER_BUF_LEN = 1024;
60constexpr int32_t FILE_MAP_BUF_LEN = 2048;
61// The swap file size, which can be configured in subsequent version.
62constexpr uint64_t SWAP_FILE_SIZE = 17179869184; // 16G
63constexpr uint32_t SWAP_FILE_MODE = 0600;
64
65constexpr int32_t UUID_VERSION_OFFSET = 6;
66constexpr int32_t UUID_CLOCK_OFFSET = 8;
67constexpr int32_t UUID_BUF_LEN = 16;
68constexpr unsigned char UUID_VERSION_OPERAND1 = 0x0F;
69constexpr unsigned char UUID_VERSION_OPERAND2 = 0x40;
70constexpr unsigned char UUID_CLOCK_OPERAND1 = 0x3F;
71constexpr unsigned char UUID_CLOCK_OPERAND2 = 0x80;
72
73constexpr const char * const SWAP_FILE_PATH = "/data/power/swapfile";
74constexpr const char * const SWAP_DIR_PATH = "/data/power";
75constexpr const char * const HIBERNATE_RESUME = "/sys/hibernate/resume";
76constexpr const char * const SYS_POWER_RESUME = "/sys/power/resume";
77constexpr const char * const SYS_POWER_RESUME_OFFSET = "/sys/power/resume_offset";
78constexpr const char * const HIBERNATE_STATE_PATH = "/sys/power/state";
79constexpr const char * const HIBERNATE_STATE = "disk";
80
81struct SwapfileCfg {
82    unsigned long long len;
83};
84
85static int UlongLen(unsigned long arg)
86{
87    int l = 0;
88    arg >>= 1;
89    while (arg) {
90        l++;
91        arg >>= 1;
92    }
93    return l;
94}
95
96void Hibernate::Init()
97{
98    HDF_LOGI("hibernate init begin.");
99    auto myThread = std::thread([this] { this->InitSwap(); });
100    myThread.detach();
101}
102
103int32_t Hibernate::GetResumeInfo(std::string &resumeInfo)
104{
105    FILE *fp;
106    struct mntent *me;
107    int32_t ret = HDF_FAILURE;
108    if (!(fp = setmntent("/proc/mounts", "r"))) {
109        HDF_LOGE("open file failed, errno = %{public}d", errno);
110        return ret;
111    }
112    while ((me = getmntent(fp))) {
113        if (strcmp(me->mnt_dir, "/data") == 0) {
114            char resolvedPath[PATH_MAX] = {0};
115            if (realpath(me->mnt_fsname, resolvedPath) == nullptr) {
116                HDF_LOGE("realpath error, errno = %{public}d", errno);
117                break;
118            }
119            std::string fileSystemInfo = resolvedPath;
120            auto index = fileSystemInfo.find_last_of('/');
121            if (index == std::string::npos) {
122                HDF_LOGE("file system info error");
123                break;
124            }
125            auto partitionNum = fileSystemInfo.substr(index + 1);
126            HDF_LOGI("partition num: %{public}s", partitionNum.c_str());
127            resumeInfo = "/dev/" + partitionNum;
128            ret = HDF_SUCCESS;
129            break;
130        }
131    }
132    endmntent(fp);
133    return ret;
134}
135
136void Hibernate::InitSwap()
137{
138    std::lock_guard<std::mutex> lock(initMutex_);
139    if (swapFileReady_) {
140        HDF_LOGI("swap file is ready, do nothing.");
141        return;
142    }
143    bool needToCreateSwapFile;
144    auto ret = CheckSwapFile(needToCreateSwapFile);
145    if (ret != HDF_SUCCESS) {
146        return;
147    }
148
149    if (needToCreateSwapFile) {
150        ret = CreateSwapFile();
151        if (ret != HDF_SUCCESS) {
152            return;
153        }
154        ret = MkSwap();
155        if (ret != HDF_SUCCESS) {
156            HDF_LOGI("init swap failed");
157            RemoveSwapFile();
158            return;
159        }
160    }
161
162    ret = WriteOffsetAndResume();
163    if (ret != HDF_SUCCESS) {
164        return;
165    }
166    swapFileReady_ = true;
167}
168
169int32_t Hibernate::MkSwap()
170{
171    int fd = open(SWAP_FILE_PATH, O_RDWR);
172    if (fd < 0) {
173        HDF_LOGE("open swap file failed when mkswap");
174        return HDF_FAILURE;
175    }
176    int32_t retvalue = HDF_FAILURE;
177    do {
178        int pagesize = sysconf(_SC_PAGE_SIZE);
179        if (pagesize == 0) {
180            break;
181        }
182        unsigned int pages = (SWAP_FILE_SIZE / static_cast<unsigned int>(pagesize)) - 1;
183        char buff[SWAP_HEADER_BUF_LEN];
184        uint32_t *swap = reinterpret_cast<uint32_t *>(buff);
185
186        swap[0] = SWAP_HEADER_INFO_VERSION;
187        swap[1] = pages;
188        if (lseek(fd, SWAP_HEADER_INFO_BOOTBITS_SIZE, SEEK_SET) < 0) {
189            HDF_LOGE("skip bootbits failed when mkswap.");
190            break;
191        }
192
193        char *uuid = reinterpret_cast<char *>(swap + SWAP_HEADER_INFO_UUID_OFFSET);
194        if (getrandom(uuid, UUID_BUF_LEN, GRND_RANDOM) != UUID_BUF_LEN) {
195            HDF_LOGE("create uuid failed when mkswap.");
196            break;
197        }
198        uuid[UUID_VERSION_OFFSET] = (uuid[UUID_VERSION_OFFSET] & UUID_VERSION_OPERAND1) | UUID_VERSION_OPERAND2;
199        uuid[UUID_CLOCK_OFFSET] = (uuid[UUID_CLOCK_OFFSET] & UUID_CLOCK_OPERAND1) | UUID_CLOCK_OPERAND2;
200        size_t len = SWAP_HEADER_SIZE * sizeof(uint32_t);
201        auto ret = write(fd, swap, len);
202        if (ret < 0 || static_cast<size_t>(ret) != len) {
203            HDF_LOGE("write swap header info failed when mkswap.");
204            break;
205        }
206        if (lseek(fd, pagesize - SWAP_HEADER_MAGIC_SIZE, SEEK_SET) < 0) {
207            HDF_LOGE("seek magic of swap failed when mkswap");
208            break;
209        }
210        if (write(fd, "SWAPSPACE2", SWAP_HEADER_MAGIC_SIZE) != SWAP_HEADER_MAGIC_SIZE) {
211            HDF_LOGE("write magic of swap failed when mkswap");
212            break;
213        }
214        fsync(fd);
215        retvalue = HDF_SUCCESS;
216        HDF_LOGI("mkswap success");
217    } while (0);
218    close(fd);
219    return retvalue;
220}
221
222int32_t Hibernate::CheckSwapFile(bool &needToCreateSwapFile)
223{
224    needToCreateSwapFile = false;
225    if (!IsSwapFileExist()) {
226        needToCreateSwapFile = true;
227        HDF_LOGI("CheckSwapFile, need to create swap file.");
228        return HDF_SUCCESS;
229    }
230    bool isRightSize;
231    if (CheckSwapFileSize(isRightSize) != HDF_SUCCESS) {
232        return HDF_FAILURE;
233    }
234    if (!isRightSize) {
235        needToCreateSwapFile = true;
236        HDF_LOGI("swapfile size was changed, will remove old swapfile.");
237        if (RemoveSwapFile() != HDF_SUCCESS) {
238            return HDF_FAILURE;
239        }
240    }
241    HDF_LOGI("CheckSwapFile end.");
242    return HDF_SUCCESS;
243}
244
245bool Hibernate::IsSwapFileExist()
246{
247    return access(SWAP_FILE_PATH, F_OK) == 0;
248}
249
250int32_t Hibernate::CheckSwapFileSize(bool &isRightSize)
251{
252    HDF_LOGI("CheckSwapFileSize begin.");
253    struct stat swapFileStat;
254    auto ret = stat(SWAP_FILE_PATH, &swapFileStat);
255    if (ret != 0) {
256        HDF_LOGE("stat swap file failed, errno=%{public}d", errno);
257        return HDF_FAILURE;
258    }
259
260    isRightSize = true;
261    if (swapFileStat.st_size != SWAP_FILE_SIZE) {
262        HDF_LOGE("swap file size error, actual_size=%{public}lld expected_size=%{public}lld",
263            static_cast<long long>(swapFileStat.st_size), static_cast<long long>(SWAP_FILE_SIZE));
264        isRightSize = false;
265    }
266    HDF_LOGI("CheckSwapFileSize end.");
267    return HDF_SUCCESS;
268}
269
270int32_t Hibernate::CreateSwapFile()
271{
272    HDF_LOGI("CreateSwapFile begin.");
273    if (access(SWAP_DIR_PATH, F_OK) != 0) {
274        HDF_LOGE("the swap dir not exist.");
275        return HDF_FAILURE;
276    }
277
278    struct SwapfileCfg cfg;
279    cfg.len = SWAP_FILE_SIZE;
280
281    int fd = open(SWAP_FILE_PATH, O_RDONLY | O_LARGEFILE | O_EXCL | O_CREAT, SWAP_FILE_MODE);
282    if (fd == -1) {
283        HDF_LOGE("open swap file failed, errno=%{public}d", errno);
284        return HDF_FAILURE;
285    }
286    int ret = ioctl(fd, HMFS_IOC_SWAPFILE_PREALLOC, &cfg);
287    if (ret != 0) {
288        HDF_LOGE("ioctl failed, ret=%{public}d", ret);
289        close(fd);
290        return HDF_FAILURE;
291    }
292    close(fd);
293    HDF_LOGI("CreateSwapFile success.");
294    return HDF_SUCCESS;
295}
296
297int32_t Hibernate::RemoveSwapFile()
298{
299    if (swapoff(SWAP_FILE_PATH) != 0) {
300        HDF_LOGE("swap off failed when remove swap file, errno=%{public}d", errno);
301    }
302
303    if (remove(SWAP_FILE_PATH) != 0) {
304        HDF_LOGE("remove swap file failed, errno=%{public}d", errno);
305        return HDF_FAILURE;
306    }
307
308    HDF_LOGI("remove swap file success.");
309    return HDF_SUCCESS;
310}
311
312int32_t Hibernate::EnableSwap()
313{
314    HDF_LOGI("swapon begin.");
315    int ret = swapon(SWAP_FILE_PATH, 0);
316    if (ret < 0) {
317        HDF_LOGE("swapon failed, errno=%{public}d", errno);
318        return HDF_FAILURE;
319    }
320    HDF_LOGI("swapon success.");
321    return HDF_SUCCESS;
322}
323
324int32_t Hibernate::WriteOffsetAndResume()
325{
326    uint64_t resumeOffset;
327    auto status = GetResumeOffset(resumeOffset);
328    if (status != HDF_SUCCESS) {
329        return HDF_FAILURE;
330    }
331    UniqueFd fd(TEMP_FAILURE_RETRY(open(HIBERNATE_RESUME, O_RDWR | O_CLOEXEC)));
332    if (fd < 0) {
333        HDF_LOGE("write offset and resume error, fd < 0, errno=%{public}d", errno);
334        return HDF_FAILURE;
335    }
336    std::string resumeInfo;
337    if (GetResumeInfo(resumeInfo) != HDF_SUCCESS) {
338        return HDF_FAILURE;
339    }
340    std::string offsetResume = std::to_string(resumeOffset) + ":" + resumeInfo;
341
342    bool ret = SaveStringToFd(fd, offsetResume.c_str());
343    if (!ret) {
344        HDF_LOGE("WriteOffsetAndResume fail");
345        return HDF_FAILURE;
346    }
347    HDF_LOGI("WriteOffsetAndResume end");
348    return HDF_SUCCESS;
349}
350
351int32_t Hibernate::WriteOffset()
352{
353    uint64_t resumeOffset;
354    auto status = GetResumeOffset(resumeOffset);
355    if (status != HDF_SUCCESS) {
356        return HDF_FAILURE;
357    }
358    UniqueFd fd(TEMP_FAILURE_RETRY(open(SYS_POWER_RESUME_OFFSET, O_RDWR | O_CLOEXEC)));
359    if (fd < 0) {
360        HDF_LOGE("write offset error, fd < 0, errno=%{public}d", errno);
361        return HDF_FAILURE;
362    }
363
364    std::string offset = std::to_string(resumeOffset);
365
366    bool ret = SaveStringToFd(fd, offset.c_str());
367    if (!ret) {
368        HDF_LOGE("WriteOffset fail");
369        return HDF_FAILURE;
370    }
371    HDF_LOGI("WriteOffset end");
372    return HDF_SUCCESS;
373}
374
375int32_t Hibernate::WriteResume()
376{
377    UniqueFd fd(TEMP_FAILURE_RETRY(open(SYS_POWER_RESUME, O_RDWR | O_CLOEXEC)));
378    if (fd < 0) {
379        HDF_LOGE("write resume error, fd < 0, errno=%{public}d", errno);
380        return HDF_FAILURE;
381    }
382
383    std::string resumeInfo;
384    if (GetResumeInfo(resumeInfo) != HDF_SUCCESS) {
385        return HDF_FAILURE;
386    }
387
388    bool ret = SaveStringToFd(fd, resumeInfo);
389    if (!ret) {
390        HDF_LOGE("WriteResume fail");
391        return HDF_FAILURE;
392    }
393
394    HDF_LOGI("WriteResume end");
395    return HDF_SUCCESS;
396}
397
398int32_t Hibernate::WritePowerState()
399{
400    UniqueFd fd(TEMP_FAILURE_RETRY(open(HIBERNATE_STATE_PATH, O_RDWR | O_CLOEXEC)));
401    if (fd < 0) {
402        HDF_LOGE("WritePowerState error, fd < 0, errno=%{public}d", errno);
403        return HDF_FAILURE;
404    }
405
406    bool ret = SaveStringToFd(fd, HIBERNATE_STATE);
407    if (!ret) {
408        HDF_LOGE("WritePowerState fail");
409        return HDF_FAILURE;
410    }
411
412    HDF_LOGE("WritePowerState end");
413    return HDF_SUCCESS;
414}
415
416int32_t Hibernate::DoHibernate()
417{
418    InitSwap();
419    if (EnableSwap() != HDF_SUCCESS) {
420        return HDF_FAILURE;
421    }
422    int32_t ret = HDF_SUCCESS;
423    do {
424        if (WriteResume() != HDF_SUCCESS) {
425            ret = HDF_FAILURE;
426            break;
427        }
428        if (WriteOffset() != HDF_SUCCESS) {
429            ret = HDF_FAILURE;
430            break;
431        }
432        if (WritePowerState() != HDF_SUCCESS) {
433            ret = HDF_FAILURE;
434            break;
435        }
436    } while (0);
437    if (swapoff(SWAP_FILE_PATH) != 0) {
438        HDF_LOGE("swap off failed, errno=%{public}d", errno);
439    }
440    return ret;
441}
442
443int32_t Hibernate::GetResumeOffset(uint64_t &resumeOffset)
444{
445    int fd = open(SWAP_FILE_PATH, O_RDONLY);
446    if (fd < 0) {
447        HDF_LOGE("open swap file failed, errno=%{public}d", errno);
448        return HDF_FAILURE;
449    }
450
451    struct stat fileStat;
452    int rc = stat(SWAP_FILE_PATH, &fileStat);
453    if (rc != 0) {
454        HDF_LOGE("stat swap file failed, errno=%{public}d", errno);
455        close(fd);
456        return HDF_FAILURE;
457    }
458
459    __u64 buf[FILE_MAP_BUF_LEN];
460    unsigned long flags = 0;
461    struct fiemap *swapFileFiemap = reinterpret_cast<struct fiemap *>(buf);
462    struct fiemap_extent *swapFileFmExt = &swapFileFiemap->fm_extents[0];
463    unsigned int count = (sizeof(buf) - sizeof(*swapFileFiemap)) / sizeof(struct fiemap_extent);
464
465    if (memset_s(swapFileFiemap, sizeof(buf), 0, sizeof(struct fiemap)) != EOK) {
466        close(fd);
467        return HDF_FAILURE;
468    }
469
470    swapFileFiemap->fm_length = ~0ULL;
471    swapFileFiemap->fm_flags = flags;
472    swapFileFiemap->fm_extent_count = count;
473
474    rc = ioctl(fd, FS_IOC_FIEMAP, reinterpret_cast<unsigned long>(swapFileFiemap));
475    if (rc != 0) {
476        HDF_LOGE("get swap file physical blk fail, rc=%{public}d", rc);
477        close(fd);
478        return HDF_FAILURE;
479    }
480
481    resumeOffset = swapFileFmExt[0].fe_physical >> UlongLen(fileStat.st_blksize);
482    HDF_LOGI("resume offset size: %{public}lld", static_cast<long long>(resumeOffset));
483    close(fd);
484    return HDF_SUCCESS;
485}
486} // namespace V1_2
487} // namespace Power
488} // namespace HDI
489} // namespace OHOS
490