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
16#include "module_loop.h"
17#include <dirent.h>
18#include <fcntl.h>
19#include <filesystem>
20#include <libgen.h>
21#include <mutex>
22#include <sys/ioctl.h>
23#include <sys/stat.h>
24#include <sys/statfs.h>
25#include <sys/mount.h>
26#include <sys/types.h>
27#include <vector>
28#include <linux/fs.h>
29#include <linux/loop.h>
30#include <linux/magic.h>
31#include <cerrno>
32#include "securec.h"
33#include "string_ex.h"
34#include "log/log.h"
35#include "module_dm.h"
36#include "module_utils.h"
37#include "module_constants.h"
38#include "sys/sysmacros.h"
39#include "scope_guard.h"
40
41namespace OHOS {
42namespace SysInstaller {
43namespace Loop {
44using namespace Updater;
45using std::string;
46
47namespace {
48const int LOOP_LENGTH = 4;
49const int LOOP_CTL_LENGTH = 12;
50constexpr const char *LOOP_CTL_PATH = "/dev/loop-control";
51constexpr const char *BLOCK_DEV_PATH = "/dev/block";
52constexpr const char *LOOP_PREFIX = "loop";
53constexpr const char *DEVICE_PREFIX = "/dev/";
54constexpr const char *SYSTEM_BLOCK_PATH = "/sys/block/";
55constexpr const char *READ_AHEAD_NAME = "/queue/read_ahead_kb";
56constexpr const char *READ_AHEAD_KB = "128";
57constexpr const char *MODULE_LOOP_PREFIX = "module:";
58constexpr const char *LOOP_DEV_PATH = "/dev/loop";
59constexpr const char *LOOP_BLOCK_PATH = "/dev/block/loop";
60const size_t LOOP_DEVICE_RETRY_ATTEMPTS = 6u;
61const uint32_t LOOP_BLOCK_SIZE = 4096;
62const std::chrono::milliseconds WAIT_FOR_DEVICE_TIME(50);
63const std::chrono::seconds WAIT_FOR_LOOP_TIME(50);
64}
65
66static bool IsRealPath(std::string path)
67{
68    char buf[PATH_MAX] = { 0 };
69    if (realpath(path.c_str(), buf) == nullptr) {
70        return false;
71    }
72    std::string tmpPath = buf;
73    return (path == tmpPath);
74}
75
76void LoopbackDeviceUniqueFd::MaybeCloseBad() const
77{
78    if (deviceFd.Get() != -1) {
79        int ret = ioctl(deviceFd.Get(), LOOP_CLR_FD);
80        if (ret < 0) {
81            LOG(ERROR) << "Failed to clear fd for loopback device";
82        }
83    }
84}
85
86bool PreAllocateLoopDevices(const size_t num)
87{
88    if (!WaitForFile(LOOP_CTL_PATH, WAIT_FOR_LOOP_TIME)) {
89        LOG(ERROR) << "loop-control is not ready";
90        return false;
91    }
92    int fd = open(LOOP_CTL_PATH, O_RDWR | O_CLOEXEC);
93    if (fd == -1) {
94        LOG(ERROR) << "Failed to open loop-control";
95        return false;
96    }
97    UniqueFd ctlFd(fd);
98
99    bool found = false;
100    size_t startId = 0;
101    DIR *dir = opendir(BLOCK_DEV_PATH);
102    if (dir == nullptr) {
103        LOG(ERROR) << "Failed to open " << BLOCK_DEV_PATH;
104        return false;
105    }
106    struct dirent *ptr = nullptr;
107    while ((ptr = readdir(dir)) != nullptr) {
108        if (strncmp(ptr->d_name, LOOP_PREFIX, strlen(LOOP_PREFIX)) != 0) {
109            continue;
110        }
111        string idStr = ptr->d_name + strlen(LOOP_PREFIX);
112        int id = 0;
113        if (StrToInt(idStr, id)) {
114            size_t devId = static_cast<size_t>(id);
115            if (startId < devId) {
116                startId = devId;
117                found = true;
118            }
119        }
120    }
121    closedir(dir);
122    if (found) {
123        startId++;
124    }
125    LOG(INFO) << "start id is " << startId;
126
127    for (size_t id = startId; id < num + startId; ++id) {
128        int ret = ioctl(ctlFd.Get(), LOOP_CTL_ADD, id);
129        if (ret < 0 && errno != EEXIST) {
130            LOG(ERROR) << "Failed to add loop device";
131            return false;
132        }
133    }
134    LOG(INFO) << "Pre-allocated " << num << " loopback devices";
135    return true;
136}
137
138bool ConfigureReadAhead(const string &devicePath)
139{
140    if (!StartsWith(devicePath, DEVICE_PREFIX)) {
141        LOG(ERROR) << "invalid device path " << devicePath;
142        return false;
143    }
144    string path(devicePath);
145    string deviceName = basename(&path[0]);
146    string sysfsDevice = SYSTEM_BLOCK_PATH + deviceName + READ_AHEAD_NAME;
147    string realPath = GetRealPath(sysfsDevice);
148    if (realPath.empty()) {
149        LOG(ERROR) << "invalid device path " << sysfsDevice;
150        return false;
151    }
152
153    struct stat fileState;
154    if (stat(realPath.c_str(), &fileState) != 0) {
155        LOG(ERROR) << "Fail to Stat file: " << realPath << ", errno=" << errno;
156        return false;
157    }
158    ON_SCOPE_EXIT(recoveryMode) {
159        (void)chmod(realPath.c_str(), fileState.st_mode);
160    };
161    UniqueFd sysfsFd(open(realPath.c_str(), O_RDWR | O_CLOEXEC));
162    if (sysfsFd.Get() == -1) {
163        // 0644: give permission to write
164        if (chmod(realPath.c_str(), 0644) != 0) {
165            LOG(WARNING) << "Fail to chmod file: " << realPath << ", errno=" << errno;
166        }
167        sysfsFd = UniqueFd(open(realPath.c_str(), O_RDWR | O_CLOEXEC));
168        if (sysfsFd.Get() == -1) {
169            LOG(ERROR) << "Fail to open file: " << realPath << ", errno=" << errno;
170            return false;
171        }
172    }
173    int writeBytes = write(sysfsFd.Get(), READ_AHEAD_KB, strlen(READ_AHEAD_KB) + 1);
174    if (writeBytes < 0) {
175        LOG(ERROR) << "Failed to write to " << realPath;
176        return false;
177    }
178    return true;
179}
180
181bool CheckIfSupportLoopConfigure(const int deviceFd)
182{
183#ifdef LOOP_CONFIGURE
184    struct loop_config config;
185    (void)memset_s(&config, sizeof(config), 0, sizeof(config));
186    config.fd = -1;
187    return ioctl(deviceFd, LOOP_CONFIGURE, &config) == -1 && errno == EBADF;
188#else
189    return false;
190#endif
191}
192
193bool ConfigureLoopDevice(const int deviceFd, const int targetFd, struct loop_info64 li, const bool useBufferedIo)
194{
195#ifdef LOOP_CONFIGURE
196    struct loop_config config;
197    (void)memset_s(&config, sizeof(config), 0, sizeof(config));
198    config.fd = targetFd;
199    config.info = li;
200    config.block_size = LOOP_BLOCK_SIZE;
201    if (!useBufferedIo) {
202        li.lo_flags |= LO_FLAGS_DIRECT_IO;
203    }
204    int ret = ioctl(deviceFd, LOOP_CONFIGURE, &config);
205    if (ret < 0) {
206        LOG(ERROR) << "Failed to configure loop device err=" << errno;
207        return false;
208    }
209    return true;
210#else
211    return false;
212#endif
213}
214
215bool SetLoopDeviceStatus(const int deviceFd, const int targetFd, const struct loop_info64 *li)
216{
217    int ret = ioctl(deviceFd, LOOP_SET_FD, targetFd);
218    if (ret < 0) {
219        LOG(ERROR) << "Failed to set loop fd err=" << errno;
220        return false;
221    }
222    ret = ioctl(deviceFd, LOOP_SET_STATUS64, li);
223    if (ret < 0) {
224        LOG(ERROR) << "Failed to set loop status err=" << errno;
225        return false;
226    }
227    ret = ioctl(deviceFd, BLKFLSBUF, 0);
228    if (ret < 0) {
229        LOG(WARNING) << "Failed to flush buffers on the loop device err=" << errno;
230    }
231    ret = ioctl(deviceFd, LOOP_SET_BLOCK_SIZE, LOOP_BLOCK_SIZE);
232    if (ret < 0) {
233        LOG(WARNING) << "Failed to set block size err=" << errno;
234    }
235    return true;
236}
237
238bool SetUpLoopDevice(const int deviceFd, const string &target, const uint32_t imageOffset, const uint32_t imageSize)
239{
240    static bool useLoopConfigure = CheckIfSupportLoopConfigure(deviceFd);
241    bool useBufferedIo = false;
242    string realPath = GetRealPath(target);
243    if (realPath.empty()) {
244        LOG(ERROR) << "invalid target " << target;
245        return false;
246    }
247    UniqueFd targetFd(open(realPath.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
248    if (targetFd.Get() == -1) {
249        struct statfs stbuf;
250        int savedErrno = errno;
251        if (statfs(realPath.c_str(), &stbuf) != 0 ||
252            (stbuf.f_type != EROFS_SUPER_MAGIC_V1 &&
253             stbuf.f_type != SQUASHFS_MAGIC &&
254             stbuf.f_type != OVERLAYFS_SUPER_MAGIC &&
255             stbuf.f_type != EXT4_SUPER_MAGIC)) {
256            LOG(ERROR) << "Failed to open " << realPath << " errno=" << savedErrno;
257            return false;
258        }
259        LOG(WARNING) << "Fallback to buffered I/O for " << realPath;
260        useBufferedIo = true;
261        targetFd = UniqueFd(open(realPath.c_str(), O_RDONLY | O_CLOEXEC));
262        if (targetFd.Get() == -1) {
263            LOG(ERROR) << "Failed to open " << realPath;
264            return false;
265        }
266    }
267
268    struct loop_info64 li;
269    (void)memset_s(&li, sizeof(li), 0, sizeof(li));
270    errno_t ret = strcpy_s(reinterpret_cast<char*>(li.lo_crypt_name), LO_NAME_SIZE, MODULE_LOOP_PREFIX);
271    if (ret != EOK) {
272        LOG(ERROR) << "Failed to copy loop prefix " << MODULE_LOOP_PREFIX;
273        return false;
274    }
275    li.lo_offset = imageOffset;
276    li.lo_sizelimit = imageSize;
277    li.lo_flags |= LO_FLAGS_AUTOCLEAR;
278    return useLoopConfigure ? ConfigureLoopDevice(deviceFd, targetFd.Get(), li, useBufferedIo)
279        : SetLoopDeviceStatus(deviceFd, targetFd.Get(), &li);
280}
281
282std::unique_ptr<LoopbackDeviceUniqueFd> WaitForDevice(const int num)
283{
284    const std::vector<string> candidateDevices = {
285        LOOP_BLOCK_PATH + std::to_string(num),
286        LOOP_DEV_PATH + std::to_string(num),
287    };
288
289    UniqueFd sysfsFd;
290    for (size_t i = 0; i < LOOP_DEVICE_RETRY_ATTEMPTS; ++i) {
291        for (const auto &device : candidateDevices) {
292            string realPath = GetRealPath(device);
293            if (realPath.empty()) {
294                continue;
295            }
296            sysfsFd = UniqueFd(open(realPath.c_str(), O_RDWR | O_CLOEXEC));
297            if (sysfsFd.Get() != -1) {
298                return std::make_unique<LoopbackDeviceUniqueFd>(std::move(sysfsFd), realPath);
299            }
300        }
301        LOG(WARNING) << "Loopback device " << num << " not ready. Waiting 50ms...";
302        usleep(std::chrono::duration_cast<std::chrono::microseconds>(WAIT_FOR_DEVICE_TIME).count());
303    }
304    LOG(ERROR) << "Failed to open loopback device " << num;
305    return nullptr;
306}
307
308std::unique_ptr<LoopbackDeviceUniqueFd> CreateLoopDevice(
309    const string &target, const uint32_t imageOffset, const uint32_t imageSize)
310{
311    UniqueFd ctlFd(open(LOOP_CTL_PATH, O_RDWR | O_CLOEXEC));
312    if (ctlFd.Get() == -1) {
313        LOG(ERROR) << "Failed to open loop-control";
314        return nullptr;
315    }
316    static std::mutex mlock;
317    std::lock_guard lock(mlock);
318    int num = ioctl(ctlFd.Get(), LOOP_CTL_GET_FREE);
319    if (num < 0) {
320        LOG(ERROR) << "Failed to get free loop device err=" << errno;
321        return nullptr;
322    }
323    LOG(INFO) << "Get free loop device num " << num;
324    std::unique_ptr<LoopbackDeviceUniqueFd> loopDevice = WaitForDevice(num);
325    if (loopDevice == nullptr) {
326        LOG(ERROR) << "Failed to create loop device " << num;
327        return nullptr;
328    }
329    if (!SetUpLoopDevice(loopDevice->deviceFd.Get(), target, imageOffset, imageSize)) {
330        LOG(ERROR) << "Failed to configure device";
331        return nullptr;
332    }
333    if (!ConfigureReadAhead(loopDevice->name)) {
334        LOG(ERROR) << "Failed to configure read ahead";
335        return nullptr;
336    }
337    return loopDevice;
338}
339
340bool RemoveDmLoopDevice(const std::string &mountPoint, const std::string &imagePath)
341{
342    struct dirent *ent = nullptr;
343    DIR *dir = nullptr;
344
345    if ((dir = opendir(BLOCK_DEV_PATH)) == nullptr) {
346        LOG(ERROR) << "Failed to open loop dir";
347        return false;
348    }
349    bool ret = false;
350    std::string loopDevPath = "";
351    while ((ent = readdir(dir)) != nullptr) {
352        if (strncmp(ent->d_name, "loop", LOOP_LENGTH) || !strncmp(ent->d_name, "loop-control", LOOP_CTL_LENGTH)) {
353            continue;
354        }
355
356        loopDevPath = std::string(BLOCK_DEV_PATH) + "/" + std::string(ent->d_name);
357        if (!IsRealPath(loopDevPath)) {
358            LOG(ERROR) << "Dev is not exist, loopDevPath=" << loopDevPath;
359            loopDevPath = "";
360            continue;
361        }
362        if (!IsLoopDevMatchedImg(loopDevPath, imagePath)) {
363            loopDevPath = "";
364            continue;
365        }
366        if (umount(mountPoint.c_str()) != 0) {
367            LOG(WARNING) << "Could not umount " << mountPoint << " errno: " << errno;
368        }
369        bool clearDm = (imagePath.find(UPDATE_ACTIVE_DIR) != std::string::npos);
370        ret = ClearDmLoopDevice(loopDevPath, clearDm);
371        break;
372    }
373    closedir(dir);
374    return ret;
375}
376
377bool RemoveDmLoopDevice(const std::string &loopDevPath)
378{
379#ifndef USER_DEBUG_MODE
380    if (!RemoveDmDevice(loopDevPath)) {
381        LOG(ERROR) << "Close dm error, loopDevPath=" << loopDevPath.c_str() << ", errno=" << errno;
382        return false;
383    }
384#endif
385    if (!CloseLoopDev(loopDevPath)) {
386        LOG(ERROR) << "Close loop error, loopDevPath=" << loopDevPath.c_str() << ", errno=" << errno;
387        return false;
388    }
389    return true;
390}
391
392bool ClearDmLoopDevice(const std::string &loopDevPath, const bool clearDm)
393{
394#ifndef USER_DEBUG_MODE
395    if (clearDm) {
396        if (!RemoveDmDevice(loopDevPath)) {
397            LOG(ERROR) << "Close dm error, loopDevPath=" << loopDevPath.c_str() << ", errno=" << errno;
398            return false;
399        }
400    }
401#endif
402    if (!CloseLoopDev(loopDevPath)) {
403        LOG(ERROR) << "Close loop error, loopDevPath=" << loopDevPath.c_str() << ", errno=" << errno;
404        return false;
405    }
406    return true;
407}
408
409bool IsLoopDevMatchedImg(const std::string &loopPath, const std::string &imgFilePath)
410{
411    struct loop_info64 info;
412    if (memset_s(&info, sizeof(struct loop_info64), 0, sizeof(struct loop_info64)) != EOK) {
413        LOG(ERROR) << "memset_s failed";
414        return false;
415    }
416
417    int fd = open(loopPath.c_str(), O_RDWR | O_CLOEXEC);
418    if (fd == -1) {
419        LOG(ERROR) << "Open failed, loopPath=" << loopPath.c_str() << ", errno=" << errno;
420        return false;
421    }
422
423    if (ioctl(fd, LOOP_GET_STATUS64, &info) < 0) {
424        close(fd);
425        return false;
426    }
427    close(fd);
428    return (imgFilePath == std::string(reinterpret_cast<char *>(info.lo_file_name)));
429}
430
431bool CloseLoopDev(const std::string &loopPath)
432{
433    struct stat st;
434    if (stat(loopPath.c_str(), &st)) {
435        LOG(INFO) << "Stat error, loopPath=" << loopPath.c_str() << ", errno=" << errno;
436        return false;
437    }
438
439    int userFd = open(loopPath.c_str(), O_RDWR);
440    if (userFd < 0) {
441        LOG(ERROR) << "Open error, loopPath=" << loopPath.c_str() << ", errno=" << errno;
442        return false;
443    }
444
445    int ret = ioctl(userFd, LOOP_CLR_FD);
446    close(userFd);
447    if (ret != 0) {
448        LOG(ERROR) << "Clear error, loopPath=" << loopPath.c_str() << ", errno=" << errno;
449        return false;
450    }
451    return true;
452}
453} // Loop
454} // SysInstaller
455} // namespace OHOS