1 /*
2  * Copyright 2019 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "general_systemd.hpp"
18 
19 #include "status.hpp"
20 
21 #include <sdbusplus/bus.hpp>
22 
23 #include <fstream>
24 #include <memory>
25 #include <string>
26 #include <vector>
27 
28 namespace ipmi_flash
29 {
30 
31 static constexpr auto systemdService = "org.freedesktop.systemd1";
32 static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
33 static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
34 static constexpr auto jobInterface = "org.freedesktop.systemd1.Job";
35 
36 bool SystemdNoFile::trigger()
37 {
38     if (job)
39     {
40         std::fprintf(stderr, "Job alreading running %s: %s\n",
41                      triggerService.c_str(), job->c_str());
42         return false;
43     }
44 
45     try
46     {
47         jobMonitor.emplace(bus,
48                            "type='signal',"
49                            "sender='org.freedesktop.systemd1',"
50                            "path='/org/freedesktop/systemd1',"
51                            "interface='org.freedesktop.systemd1.Manager',"
52                            "member='JobRemoved',",
53                            [&](sdbusplus::message_t& m) { this->match(m); });
54 
55         auto method = bus.new_method_call(systemdService, systemdRoot,
56                                           systemdInterface, "StartUnit");
57         method.append(triggerService);
58         method.append(mode);
59 
60         sdbusplus::message::object_path obj_path;
61         bus.call(method).read(obj_path);
62         job = std::move(obj_path);
63         std::fprintf(stderr, "Triggered %s mode %s: %s\n",
64                      triggerService.c_str(), mode.c_str(), job->c_str());
65         currentStatus = ActionStatus::running;
66         return true;
67     }
68     catch (const std::exception& e)
69     {
70         job = std::nullopt;
71         jobMonitor = std::nullopt;
72         currentStatus = ActionStatus::failed;
73         std::fprintf(stderr, "Failed to trigger %s mode %s: %s\n",
74                      triggerService.c_str(), mode.c_str(), e.what());
75         return false;
76     }
77 }
78 
79 void SystemdNoFile::abort()
80 {
81     if (!job)
82     {
83         return;
84     }
85 
86     // Cancel the job
87     auto cancel_req = bus.new_method_call(systemdService, job->c_str(),
88                                           jobInterface, "Cancel");
89     try
90     {
91         bus.call_noreply(cancel_req);
92         std::fprintf(stderr, "Canceled %s: %s\n", triggerService.c_str(),
93                      job->c_str());
94     }
95     catch (const sdbusplus::exception_t& ex)
96     {
97         std::fprintf(stderr, "Failed to cancel job %s %s: %s\n",
98                      triggerService.c_str(), job->c_str(), ex.what());
99     }
100 }
101 
102 ActionStatus SystemdNoFile::status()
103 {
104     return currentStatus;
105 }
106 
107 const std::string& SystemdNoFile::getMode() const
108 {
109     return mode;
110 }
111 
112 void SystemdNoFile::match(sdbusplus::message_t& m)
113 {
114     if (!job)
115     {
116         std::fprintf(stderr, "No running job %s\n", triggerService.c_str());
117         return;
118     }
119 
120     uint32_t job_id;
121     sdbusplus::message::object_path job_path;
122     std::string unit;
123     std::string result;
124     try
125     {
126         m.read(job_id, job_path, unit, result);
127     }
128     catch (const sdbusplus::exception_t& e)
129     {
130         std::fprintf(stderr, "Bad JobRemoved signal %s: %s\n",
131                      triggerService.c_str(), e.what());
132         return;
133     }
134 
135     if (*job != job_path.str)
136     {
137         return;
138     }
139 
140     std::fprintf(stderr, "Job Finished %s %s: %s\n", triggerService.c_str(),
141                  job->c_str(), result.c_str());
142     jobMonitor = std::nullopt;
143     job = std::nullopt;
144     currentStatus =
145         result == "done" ? ActionStatus::success : ActionStatus::failed;
146 
147     if (cb)
148     {
149         cb(*this);
150     }
151 }
152 
153 std::unique_ptr<TriggerableActionInterface> SystemdNoFile::CreateSystemdNoFile(
154     sdbusplus::bus_t&& bus, const std::string& service, const std::string& mode)
155 {
156     return std::make_unique<SystemdNoFile>(std::move(bus), service, mode);
157 }
158 
159 std::unique_ptr<TriggerableActionInterface>
160     SystemdWithStatusFile::CreateSystemdWithStatusFile(
161         sdbusplus::bus_t&& bus, const std::string& path,
162         const std::string& service, const std::string& mode)
163 {
164     return std::make_unique<SystemdWithStatusFile>(std::move(bus), path,
165                                                    service, mode);
166 }
167 
168 bool SystemdWithStatusFile::trigger()
169 {
170     if (SystemdNoFile::status() != ActionStatus::running)
171     {
172         try
173         {
174             std::ofstream ofs;
175             ofs.open(checkPath);
176             ofs << "unknown";
177         }
178         catch (const std::exception& e)
179         {
180             return false;
181         }
182     }
183     return SystemdNoFile::trigger();
184 }
185 
186 ActionStatus SystemdWithStatusFile::status()
187 {
188     // Assume a status based on job execution if there is no file
189     ActionStatus result = SystemdNoFile::status() == ActionStatus::running
190                               ? ActionStatus::running
191                               : ActionStatus::failed;
192 
193     std::ifstream ifs;
194     ifs.open(checkPath);
195     if (ifs.good())
196     {
197         /*
198          * Check for the contents of the file, accepting:
199          * running, success, or failed.
200          */
201         std::string status;
202         ifs >> status;
203         if (status == "running")
204         {
205             result = ActionStatus::running;
206         }
207         else if (status == "success")
208         {
209             result = ActionStatus::success;
210         }
211         else if (status == "failed")
212         {
213             result = ActionStatus::failed;
214         }
215         else
216         {
217             result = ActionStatus::unknown;
218         }
219     }
220 
221     return result;
222 }
223 
224 } // namespace ipmi_flash
225