xref: /openbmc/phosphor-ipmi-flash/tools/net.cpp (revision 9bb21e3e)
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 "net.hpp"
18 
19 #include "data.hpp"
20 #include "flags.hpp"
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <netdb.h>
25 #include <sys/sendfile.h>
26 #include <sys/socket.h>
27 #include <sys/types.h>
28 
29 #include <ipmiblob/blob_errors.hpp>
30 #include <stdplus/handle/managed.hpp>
31 #include <stdplus/util/cexec.hpp>
32 
33 #include <cstdint>
34 #include <cstring>
35 #include <optional>
36 #include <vector>
37 
38 namespace
39 {
40 
41 void closefd(int&& fd, const internal::Sys*& sys)
42 {
43     sys->close(fd);
44 }
45 using Fd = stdplus::Managed<int, const internal::Sys*>::Handle<closefd>;
46 
47 } // namespace
48 
49 namespace host_tool
50 {
51 
52 bool NetDataHandler::sendContents(const std::string& input,
53                                   std::uint16_t session)
54 {
55     constexpr size_t blockSize = 64 * 1024;
56     Fd inputFd(std::nullopt, sys);
57 
58     inputFd.reset(sys->open(input.c_str(), O_RDONLY));
59     if (*inputFd < 0)
60     {
61         (void)inputFd.release();
62         std::fprintf(stderr, "Unable to open file: '%s'\n", input.c_str());
63         return false;
64     }
65 
66     std::int64_t fileSize = sys->getSize(input.c_str());
67     Fd connFd(std::nullopt, sys);
68 
69     {
70         struct addrinfo hints;
71         std::memset(&hints, 0, sizeof(hints));
72         hints.ai_flags = AI_NUMERICHOST;
73         hints.ai_family = AF_UNSPEC;
74         hints.ai_socktype = SOCK_STREAM;
75 
76         struct addrinfo *addrs, *addr;
77         int ret = sys->getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs);
78         if (ret < 0)
79         {
80             std::fprintf(stderr, "Couldn't parse address %s with port %s: %s\n",
81                          host.c_str(), port.c_str(), gai_strerror(ret));
82             return false;
83         }
84 
85         for (addr = addrs; addr != nullptr; addr = addr->ai_next)
86         {
87             connFd.reset(sys->socket(addr->ai_family, addr->ai_socktype,
88                                      addr->ai_protocol));
89             if (*connFd == -1)
90                 continue;
91 
92             if (sys->connect(*connFd, addr->ai_addr, addr->ai_addrlen) != -1)
93                 break;
94         }
95 
96         // TODO: use stdplus Managed for the addrinfo structs
97         sys->freeaddrinfo(addrs);
98 
99         if (addr == nullptr)
100         {
101             std::fprintf(stderr, "Failed to connect\n");
102             return false;
103         }
104     }
105 
106     try
107     {
108         int bytesSent = 0;
109         off_t offset = 0;
110 
111         progress->start(fileSize);
112         auto confirmSend = [&]() {
113             if (bytesSent == 0)
114             {
115                 return;
116             }
117             struct ipmi_flash::ExtChunkHdr chunk;
118             chunk.length = bytesSent;
119             std::vector<uint8_t> chunkBytes(sizeof(chunk));
120             std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
121             /* This doesn't return anything on success. */
122             blob->writeBytes(session, offset - bytesSent, chunkBytes);
123             progress->updateProgress(bytesSent);
124         };
125 
126         do
127         {
128             bytesSent = sys->sendfile(*connFd, *inputFd, &offset, blockSize);
129             if (bytesSent < 0)
130             {
131                 // Not all input files support sendfile, fall back to a simple
132                 // buffered mechanism if unsupported.
133                 if (errno != EINVAL)
134                 {
135                     CHECK_ERRNO(-1, "Sending data to BMC");
136                 }
137                 std::array<uint8_t, 4096> buf;
138                 size_t left = 0;
139                 do
140                 {
141                     left += CHECK_ERRNO(sys->read(*inputFd, buf.data() + left,
142                                                   buf.size() - left),
143                                         "Reading data for BMC");
144                     bytesSent =
145                         CHECK_ERRNO(sys->send(*connFd, buf.data(), left, 0),
146                                     "Sending data to BMC");
147                     std::memmove(buf.data(), buf.data() + bytesSent,
148                                  left - bytesSent);
149                     left -= bytesSent;
150                     offset += bytesSent;
151                     confirmSend();
152                 } while (bytesSent > 0);
153                 break;
154             }
155             confirmSend();
156         } while (bytesSent > 0);
157     }
158     catch (const std::system_error& e)
159     {
160         std::fprintf(stderr, "%s\n", e.what());
161         progress->abort();
162         return false;
163     }
164     catch (const ipmiblob::BlobException& b)
165     {
166         progress->abort();
167         return false;
168     }
169 
170     progress->finish();
171     return true;
172 }
173 
174 } // namespace host_tool
175