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 32 #include <cstdint> 33 #include <cstring> 34 #include <memory> 35 #include <optional> 36 #include <string> 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 { 60 inputFd.reset(sys->open(input.c_str(), O_RDONLY)); 61 if (*inputFd < 0) 62 { 63 (void)inputFd.release(); 64 std::fprintf(stderr, "Unable to open file: '%s'\n", input.c_str()); 65 return false; 66 } 67 68 std::int64_t fileSize = sys->getSize(input.c_str()); 69 if (fileSize == 0) 70 { 71 std::fprintf(stderr, 72 "Zero-length file, or other file access error\n"); 73 return false; 74 } 75 76 progress->start(fileSize); 77 } 78 79 Fd connFd(std::nullopt, sys); 80 81 { 82 struct addrinfo hints; 83 std::memset(&hints, 0, sizeof(hints)); 84 hints.ai_flags = AI_NUMERICHOST; 85 hints.ai_family = AF_UNSPEC; 86 hints.ai_socktype = SOCK_STREAM; 87 88 struct addrinfo *addrs, *addr; 89 int ret = sys->getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs); 90 if (ret < 0) 91 { 92 std::fprintf(stderr, "Couldn't parse address %s with port %s: %s\n", 93 host.c_str(), port.c_str(), gai_strerror(ret)); 94 progress->abort(); 95 return false; 96 } 97 98 for (addr = addrs; addr != nullptr; addr = addr->ai_next) 99 { 100 connFd.reset(sys->socket(addr->ai_family, addr->ai_socktype, 101 addr->ai_protocol)); 102 if (*connFd == -1) 103 continue; 104 105 if (sys->connect(*connFd, addr->ai_addr, addr->ai_addrlen) != -1) 106 break; 107 } 108 109 // TODO: use stdplus Managed for the addrinfo structs 110 sys->freeaddrinfo(addrs); 111 112 if (addr == nullptr) 113 { 114 std::fprintf(stderr, "Failed to connect\n"); 115 progress->abort(); 116 return false; 117 } 118 } 119 120 try 121 { 122 int bytesSent = 0; 123 off_t offset = 0; 124 125 do 126 { 127 bytesSent = sys->sendfile(*connFd, *inputFd, &offset, blockSize); 128 if (bytesSent < 0) 129 { 130 std::fprintf(stderr, "Failed to send data to BMC: %s\n", 131 strerror(errno)); 132 progress->abort(); 133 return false; 134 } 135 else if (bytesSent > 0) 136 { 137 /* Ok, so the data is staged, now send the blob write with 138 * the details. 139 */ 140 struct ipmi_flash::ExtChunkHdr chunk; 141 chunk.length = bytesSent; 142 std::vector<std::uint8_t> chunkBytes(sizeof(chunk)); 143 std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk)); 144 145 /* This doesn't return anything on success. */ 146 blob->writeBytes(session, offset - bytesSent, chunkBytes); 147 progress->updateProgress(bytesSent); 148 } 149 } while (bytesSent > 0); 150 } 151 catch (const ipmiblob::BlobException& b) 152 { 153 progress->abort(); 154 return false; 155 } 156 157 progress->finish(); 158 return true; 159 } 160 161 } // namespace host_tool 162