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(
48             bus,
49             "type='signal',"
50             "sender='org.freedesktop.systemd1',"
51             "path='/org/freedesktop/systemd1',"
52             "interface='org.freedesktop.systemd1.Manager',"
53             "member='JobRemoved',",
54             [&](sdbusplus::message::message& m) { this->match(m); });
55 
56         auto method = bus.new_method_call(systemdService, systemdRoot,
57                                           systemdInterface, "StartUnit");
58         method.append(triggerService);
59         method.append(mode);
60 
61         sdbusplus::message::object_path obj_path;
62         bus.call(method).read(obj_path);
63         job = std::move(obj_path);
64         std::fprintf(stderr, "Triggered %s mode %s: %s\n",
65                      triggerService.c_str(), mode.c_str(), job->c_str());
66         currentStatus = ActionStatus::running;
67         return true;
68     }
69     catch (const std::exception& e)
70     {
71         job = std::nullopt;
72         jobMonitor = std::nullopt;
73         currentStatus = ActionStatus::failed;
74         std::fprintf(stderr, "Failed to trigger %s mode %s: %s\n",
75                      triggerService.c_str(), mode.c_str(), e.what());
76         return false;
77     }
78 }
79 
80 void SystemdNoFile::abort()
81 {
82     if (!job)
83     {
84         std::fprintf(stderr, "No running job %s\n", triggerService.c_str());
85         return;
86     }
87 
88     // Cancel the job
89     auto cancel_req = bus.new_method_call(systemdService, job->c_str(),
90                                           jobInterface, "Cancel");
91     try
92     {
93         bus.call_noreply(cancel_req);
94         std::fprintf(stderr, "Canceled %s: %s\n", triggerService.c_str(),
95                      job->c_str());
96     }
97     catch (const sdbusplus::exception::SdBusError& ex)
98     {
99         std::fprintf(stderr, "Failed to cancel job %s %s: %s\n",
100                      triggerService.c_str(), job->c_str(), ex.what());
101     }
102 }
103 
104 ActionStatus SystemdNoFile::status()
105 {
106     return currentStatus;
107 }
108 
109 const std::string& SystemdNoFile::getMode() const
110 {
111     return mode;
112 }
113 
114 void SystemdNoFile::match(sdbusplus::message::message& m)
115 {
116     if (!job)
117     {
118         std::fprintf(stderr, "No running job %s\n", triggerService.c_str());
119         return;
120     }
121 
122     uint32_t job_id;
123     sdbusplus::message::object_path job_path;
124     std::string unit;
125     std::string result;
126     try
127     {
128         m.read(job_id, job_path, unit, result);
129     }
130     catch (const sdbusplus::exception::SdBusError& e)
131     {
132         std::fprintf(stderr, "Bad JobRemoved signal %s: %s\n",
133                      triggerService.c_str(), e.what());
134         return;
135     }
136 
137     if (*job != job_path.str)
138     {
139         return;
140     }
141 
142     std::fprintf(stderr, "Job Finished %s %s: %s\n", triggerService.c_str(),
143                  job->c_str(), result.c_str());
144     jobMonitor = std::nullopt;
145     job = std::nullopt;
146     currentStatus =
147         result == "done" ? ActionStatus::success : ActionStatus::failed;
148 }
149 
150 std::unique_ptr<TriggerableActionInterface>
151     SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::bus&& bus,
152                                        const std::string& service,
153                                        const std::string& mode)
154 {
155     return std::make_unique<SystemdNoFile>(std::move(bus), service, mode);
156 }
157 
158 std::unique_ptr<TriggerableActionInterface>
159     SystemdWithStatusFile::CreateSystemdWithStatusFile(
160         sdbusplus::bus::bus&& bus, const std::string& path,
161         const std::string& service, const std::string& mode)
162 {
163     return std::make_unique<SystemdWithStatusFile>(std::move(bus), path,
164                                                    service, mode);
165 }
166 
167 bool SystemdWithStatusFile::trigger()
168 {
169     if (SystemdNoFile::status() != ActionStatus::running)
170     {
171         try
172         {
173             std::ofstream ofs;
174             ofs.open(checkPath);
175             ofs << "unknown";
176         }
177         catch (const std::exception& e)
178         {
179             return false;
180         }
181     }
182     return SystemdNoFile::trigger();
183 }
184 
185 ActionStatus SystemdWithStatusFile::status()
186 {
187     // Assume a status based on job execution if there is no file
188     ActionStatus result = SystemdNoFile::status() == ActionStatus::running
189                               ? ActionStatus::running
190                               : ActionStatus::failed;
191 
192     std::ifstream ifs;
193     ifs.open(checkPath);
194     if (ifs.good())
195     {
196         /*
197          * Check for the contents of the file, accepting:
198          * running, success, or failed.
199          */
200         std::string status;
201         ifs >> status;
202         if (status == "running")
203         {
204             result = ActionStatus::running;
205         }
206         else if (status == "success")
207         {
208             result = ActionStatus::success;
209         }
210         else if (status == "failed")
211         {
212             result = ActionStatus::failed;
213         }
214         else
215         {
216             result = ActionStatus::unknown;
217         }
218     }
219 
220     return result;
221 }
222 
223 } // namespace ipmi_flash
224