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