From 36d2505e1a4845cf2eeb4ce9bc2be5ce4d38ee80 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Wed, 23 Oct 2019 21:35:28 -0500 Subject: fetch: Add new executable --- fetch/CMakeLists.txt | 19 ++++++ fetch/fetch.cc | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 fetch/CMakeLists.txt create mode 100644 fetch/fetch.cc (limited to 'fetch') diff --git a/fetch/CMakeLists.txt b/fetch/CMakeLists.txt new file mode 100644 index 0000000..7f83a36 --- /dev/null +++ b/fetch/CMakeLists.txt @@ -0,0 +1,19 @@ +set(FETCH_SRCS + fetch.cc +) +add_executable(hscript-fetch ${FETCH_SRCS}) + +install(TARGETS hscript-fetch DESTINATION bin) + +IF(RSPEC_EXECUTABLE) +add_test(NAME "RSpecFetch" + COMMAND ${RSPEC_EXECUTABLE} spec/fetch_spec.rb + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests) +set_property(TEST "RSpecFetch" + PROPERTY ENVIRONMENT "PATH=$ENV{PATH}:${CMAKE_CURRENT_BINARY_DIR}") +ENDIF(RSPEC_EXECUTABLE) + +IF(VALGRIND) +add_test(NAME "ValgrindFetch" + COMMAND ${VALGRIND_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/hscript-fetch https://horizon.adelielinux.org/example.installfile) +ENDIF(VALGRIND) diff --git a/fetch/fetch.cc b/fetch/fetch.cc new file mode 100644 index 0000000..941051f --- /dev/null +++ b/fetch/fetch.cc @@ -0,0 +1,159 @@ +/* + * fetch.cc - Implementation of the HorizonScript Locator + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include /* transform */ +#include /* EXIT_* */ +#include +#include +#include +#include /* access */ +#include "util/output.hh" + +static const char *IFILE_PATH = "/etc/horizon/installfile"; +static const int SCRIPT_LINE_MAX = 512; + +bool pretty = true; + +/*! Process a local path and copy it to the proper location. + * @param path The local path. + * @returns An exit code. + */ +int process_local(const std::string &path) { + /* if we can't read it, don't even bother */ + if(access(path.c_str(), R_OK) != 0) { + output_error("process_local", + std::string(path) + " does not exist or is not readable"); + return EXIT_FAILURE; + } + + std::ofstream output(IFILE_PATH, std::ios_base::trunc); + if(!output) { + output_error("process_local", + std::string(IFILE_PATH) + + " could not be opened for writing"); + return EXIT_FAILURE; + } + std::ifstream input(path, std::ios_base::in); + if(!input) { + output_error("process_local", + path + " could not be opened for reading"); + return EXIT_FAILURE; + } + char buffer[SCRIPT_LINE_MAX]; + + while(input.getline(buffer, SCRIPT_LINE_MAX)) { + output << buffer << std::endl; + } + + if(input.fail() && !input.eof()) { + output_error("process_local", "line length error reading " + path); + return EXIT_FAILURE; + } + if(input.bad() && !input.eof()) { + output_error("process_local", "I/O error reading " + path); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + + +/*! Download an installfile using cURL. + * @param path The remote path to download. + */ +int process_curl(const std::string &path) { + return EXIT_FAILURE; +} + + +/*! The signature of a protocol dispatch function */ +typedef int (*proto_dispatch_fn)(const std::string &); + + +/*! Protocol handlers */ +static const std::map protos = { + {"http", process_curl}, + {"https", process_curl}, + {"tftp", process_curl} +}; + + +/*! Dispatch handling of +path+ to a protocol handler, or return failure. + * @param path The remote path to download to IFILE_PATH. + * @returns EXIT_SUCCESS if the file was downloaded; EXIT_FAILURE otherwise. + * @note Currently, it is ambiguous whether EXIT_FAILURE means invalid proto + * or error downloading from the URL. + */ +int process_maybe_remote(const std::string &path) { + std::string proto = path.substr(0, path.find_first_of(":")); + std::transform(proto.begin(), proto.end(), proto.begin(), ::tolower); + + if(protos.find(proto) != protos.end()) { + return (*protos.at(proto))(path); + } + + std::string support = "This build of Horizon supports: "; + for(auto &proto_pair : protos) { + support += proto_pair.first + " "; + } + + output_error("argv", proto + " is not a valid protocol", support); + return EXIT_FAILURE; +} + + +/* + * The goal of the Locator is to find the HorizonScript for this computer, + * which is the target, and copy it to the well-known path /etc/horizon + * as 'installfile'. + */ +int main(int argc, char *argv[]) { + std::string installfile; + + if(argc < 1 || argc > 2) { + std::cerr << "usage: " << std::endl; + std::cerr << "\thscript-fetch [path|url]" << std::endl; + return EXIT_FAILURE; + } + + bold_if_pretty(std::cout); + std::cout << "HorizonScript Locator version " << VERSTR; + reset_if_pretty(std::cout); + std::cout << std::endl; + std::cout << "Copyright (c) 2019 Adélie Linux and contributors. AGPL-3.0 license." << std::endl; + std::cout << std::endl; + + if(argc == 1) { + if(access(IFILE_PATH, F_OK) == 0) { + /* That was easy™ */ + std::cout << "HorizonScript already present at " << IFILE_PATH + << ". Quitting." << std::endl; + return EXIT_SUCCESS; + } + + output_error("internal", "Fully Remote HorizonScript downloading " + "is not yet implemented"); + return EXIT_FAILURE; + } + + std::string path(argv[1]); + + if(path[0] == '/') { + return process_local(path); + } + if(path.find("://") != std::string::npos) { + return process_maybe_remote(path); + } + + output_error("argv", "Unrecognised protocol or invalid URL", + "An absolute path or URL is required"); + return EXIT_FAILURE; +} -- cgit v1.2.3-60-g2f50