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