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 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 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