1 /*
2  * Copyright 2018 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 "updater.hpp"
18 
19 #include "tool_errors.hpp"
20 
21 #include <algorithm>
22 #include <blobs-ipmid/blobs.hpp>
23 #include <cstring>
24 #include <ipmiblob/blob_errors.hpp>
25 #include <memory>
26 #include <string>
27 
28 namespace host_tool
29 {
30 
31 void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler,
32                  const std::string& imagePath, const std::string& signaturePath)
33 {
34     /* TODO(venture): Add optional parameter to specify the flash type, default
35      * to legacy for now.
36      *
37      * TODO(venture): Move the strings from the FirmwareHandler object to a
38      * boring utils object so it will be more happly linked cleanly to both the
39      * BMC and host-side.
40      */
41     std::string goalFirmware = "/flash/image";
42     std::string hashFilename = "/flash/hash";
43     std::string verifyFilename = "/flash/verify";
44 
45     /* Get list of blob_ids, check for /flash/image, or /flash/tarball.
46      * TODO(venture) the mechanism doesn't care, but the caller of burn_my_bmc
47      * will have in mind which they're sending and we need to verify it's
48      * available and use it.
49      */
50     std::vector<std::string> blobs = blob->getBlobList();
51     auto blobInst = std::find_if(
52         blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) {
53             /* Running into weird scenarios where the string comparison doesn't
54              * work.  TODO: revisit.
55              */
56             return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
57                                      goalFirmware.length()));
58             // return (goalFirmware.compare(iter));
59         });
60     if (blobInst == blobs.end())
61     {
62         throw ToolException(goalFirmware + " not found");
63     }
64 
65     /* Call stat on /flash/image (or /flash/tarball) and check if data interface
66      * is supported.
67      */
68     ipmiblob::StatResponse stat;
69     try
70     {
71         stat = blob->getStat(goalFirmware);
72     }
73     catch (const ipmiblob::BlobException& b)
74     {
75         throw ToolException("blob exception received: " +
76                             std::string(b.what()));
77     }
78 
79     auto supported = handler->supportedType();
80     if ((stat.blob_state & supported) == 0)
81     {
82         throw ToolException("data interface selected not supported.");
83     }
84 
85     /* Yay, our data handler is supported. */
86 
87     /* Send over the firmware image. */
88     std::fprintf(stderr, "Sending over the firmware image.\n");
89     std::uint16_t session;
90     try
91     {
92         session = blob->openBlob(
93             goalFirmware,
94             static_cast<std::uint16_t>(supported) |
95                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
96     }
97     catch (const ipmiblob::BlobException& b)
98     {
99         throw ToolException("blob exception received: " +
100                             std::string(b.what()));
101     }
102 
103     if (!handler->sendContents(imagePath, session))
104     {
105         /* Need to close the session on failure, or it's stuck open (until the
106          * blob handler timeout is implemented, and even then, why make it wait.
107          */
108         blob->closeBlob(session);
109         throw ToolException("Failed to send contents of " + imagePath);
110     }
111 
112     blob->closeBlob(session);
113 
114     /* Send over the hash contents. */
115     std::fprintf(stderr, "Sending over the hash file.\n");
116     try
117     {
118         session = blob->openBlob(
119             hashFilename,
120             static_cast<std::uint16_t>(supported) |
121                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
122     }
123     catch (const ipmiblob::BlobException& b)
124     {
125         throw ToolException("blob exception received: " +
126                             std::string(b.what()));
127     }
128 
129     if (!handler->sendContents(signaturePath, session))
130     {
131         blob->closeBlob(session);
132         throw ToolException("Failed to send contents of " + signaturePath);
133     }
134 
135     blob->closeBlob(session);
136 
137     /* Trigger the verification by opening the verify file. */
138     std::fprintf(stderr, "Opening the verification file\n");
139     try
140     {
141         session = blob->openBlob(
142             verifyFilename,
143             static_cast<std::uint16_t>(supported) |
144                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
145     }
146     catch (const ipmiblob::BlobException& b)
147     {
148         throw ToolException("blob exception received: " +
149                             std::string(b.what()));
150     }
151 
152     std::fprintf(
153         stderr,
154         "Committing to verification file to trigger verification service\n");
155     try
156     {
157         blob->commit(session, {});
158     }
159     catch (const ipmiblob::BlobException& b)
160     {
161         throw ToolException("blob exception received: " +
162                             std::string(b.what()));
163     }
164 
165     /* TODO: Check the verification via stat(session). */
166 
167     /* DO NOT CLOSE the verification session until it's done. */
168     blob->closeBlob(session);
169 
170     return;
171 }
172 
173 } // namespace host_tool
174