/*
* 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 <algorithm> /* transform */
#include <cstdlib> /* EXIT_* */
#include <fstream>
#include <map>
#include <string>
#include <unistd.h> /* 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<std::string, proto_dispatch_fn> 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;
}