1/*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
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 "plugin_watcher.h"
17
18#include <climits>
19#include <cstdio>
20#include <cstring>
21#include <dirent.h>
22#include <pthread.h>
23#include <sys/inotify.h>
24#include <unistd.h>
25
26#include "logging.h"
27#include "plugin_manager.h"
28#include "securec.h"
29
30namespace {
31constexpr uint32_t MAX_BUF_SIZE = 1024;
32} // namespace
33
34PluginWatcher::PluginWatcher(const PluginManagerPtr& pluginManager)
35    : inotifyFd_(-1), pluginManager_(pluginManager), runMonitor_(true)
36{
37    inotifyFd_ = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
38    if (inotifyFd_ < 0) {
39        PROFILER_LOG_INFO(LOG_CORE, "%s:inotify_init1 failed! inotifyFd_ : %d", __func__, inotifyFd_);
40    } else {
41        monitorThread_ = std::thread([this] { this->Monitor(); });
42    }
43}
44
45PluginWatcher::~PluginWatcher()
46{
47    runMonitor_ = false;
48    for (auto it = wdToDir_.begin(); it != wdToDir_.end(); ++it) {
49        inotify_rm_watch(inotifyFd_, it->first);
50    }
51
52    if (inotifyFd_ != -1) {
53        close(inotifyFd_);
54    }
55    monitorThread_.join();
56}
57
58bool PluginWatcher::ScanPlugins(const std::string& pluginDir)
59{
60    DIR* dir = nullptr;
61    struct dirent* entry = nullptr;
62    char fullpath[PATH_MAX + 1] = {0};
63    realpath(pluginDir.c_str(), fullpath);
64    PROFILER_LOG_INFO(LOG_CORE, "%s:scan plugin from directory %s", __func__, fullpath);
65    dir = opendir(fullpath);
66    if (dir == nullptr) {
67        return false;
68    }
69    while (true) {
70        entry = readdir(dir);
71        if (!entry) {
72            PROFILER_LOG_INFO(LOG_CORE, "%s:readdir finish!", __func__);
73            break;
74        }
75        std::string fileName = entry->d_name;
76        if (entry->d_type & DT_REG) {
77            size_t pos = fileName.rfind(".so");
78            if (pos != std::string::npos && (pos == fileName.length() - strlen(".so"))) {
79                OnPluginAdded(std::string(fullpath) + '/' + fileName);
80            }
81        }
82    }
83    closedir(dir);
84    return true;
85}
86
87bool PluginWatcher::WatchPlugins(const std::string& pluginDir)
88{
89    char fullpath[PATH_MAX + 1] = {0};
90    realpath(pluginDir.c_str(), fullpath);
91
92    int wd = inotify_add_watch(inotifyFd_, fullpath, IN_ALL_EVENTS);
93    if (wd < 0) {
94        PROFILER_LOG_INFO(LOG_CORE, "%s:inotify_add_watch add directory %s failed!", __func__, pluginDir.c_str());
95        return false;
96    }
97    PROFILER_LOG_INFO(LOG_CORE, "%s:inotify_add_watch add directory %s success!", __func__, fullpath);
98    std::lock_guard<std::mutex> guard(mtx_);
99    wdToDir_.insert(std::pair<int, std::string>(wd, std::string(fullpath)));
100    return true;
101}
102
103bool PluginWatcher::MonitorIsSet()
104{
105    const struct inotify_event* event = nullptr;
106    char buffer[MAX_BUF_SIZE] = {'\0'};
107    char* ptr = nullptr;
108
109    ssize_t readLength = read(inotifyFd_, buffer, MAX_BUF_SIZE);
110    if (readLength == -1) {
111        return false;
112    }
113    for (ptr = buffer; ptr < buffer + readLength; ptr += sizeof(struct inotify_event) + event->len) {
114        event = reinterpret_cast<const struct inotify_event*>(ptr);
115        std::unique_lock<std::mutex> guard(mtx_, std::adopt_lock);
116        const std::string& pluginDir = wdToDir_[event->wd];
117        guard.unlock();
118        if (event->mask & IN_ISDIR) {
119            continue;
120        }
121        std::string fileName = event->name;
122        size_t pos = fileName.rfind(".so");
123        if ((pos == std::string::npos) || (pos != fileName.length() - strlen(".so"))) {
124            continue;
125        }
126        switch (event->mask) {
127            case IN_CLOSE_WRITE:
128            case IN_MOVED_TO:
129                OnPluginAdded(pluginDir + '/' + fileName);
130                break;
131            case IN_DELETE:
132            case IN_MOVED_FROM:
133                OnPluginRemoved(pluginDir + '/' + fileName);
134                break;
135            default:
136                break;
137        }
138    }
139    if (memset_s(buffer, sizeof(buffer), 0, sizeof(buffer)) != 0) {
140        PROFILER_LOG_ERROR(LOG_CORE, "%s:memset_s error!", __func__);
141    }
142    return true;
143}
144
145void PluginWatcher::Monitor()
146{
147    struct timeval time;
148
149    pthread_setname_np(pthread_self(), "PluginWatcher");
150    while (runMonitor_) {
151        fd_set rFds;
152        FD_ZERO(&rFds);
153        FD_SET(inotifyFd_, &rFds);
154        time.tv_sec = 1;
155        time.tv_usec = 0;
156        int ret = select(inotifyFd_ + 1, &rFds, nullptr, nullptr, &time);
157        if (ret < 0) {
158            continue;
159        } else if (!ret) {
160            continue;
161        } else if (FD_ISSET(inotifyFd_, &rFds)) {
162            if (!MonitorIsSet()) {
163                continue;
164            }
165        }
166    }
167}
168
169void PluginWatcher::OnPluginAdded(const std::string& pluginPath)
170{
171    auto pluginManager = pluginManager_.lock();
172    if (pluginManager != nullptr) {
173        if (pluginManager->AddPlugin(pluginPath)) {
174            PROFILER_LOG_INFO(LOG_CORE, "%s:plugin %s add success!", __func__, pluginPath.c_str());
175        } else {
176            PROFILER_LOG_INFO(LOG_CORE, "%s:pluginPath %s add failed!", __func__, pluginPath.c_str());
177        }
178    } else {
179        PROFILER_LOG_INFO(LOG_CORE, "%s:weak_ptr pluginManager lock failed!", __func__);
180    }
181}
182
183void PluginWatcher::OnPluginRemoved(const std::string& pluginPath)
184{
185    auto pluginManager = pluginManager_.lock();
186    if (pluginManager != nullptr) {
187        if (pluginManager->RemovePlugin(pluginPath)) {
188            PROFILER_LOG_INFO(LOG_CORE, "%s:pluginPath %s remove success!", __func__, pluginPath.c_str());
189        } else {
190            PROFILER_LOG_INFO(LOG_CORE, "%s:pluginPath %s remove failed!", __func__, pluginPath.c_str());
191        }
192    } else {
193        PROFILER_LOG_INFO(LOG_CORE, "%s:weak_ptr pluginManager lock failed!", __func__);
194    }
195}