Files
op-pedal/src/AdminInstallUpdate.cpp
T
2024-11-11 19:31:43 -05:00

192 lines
5.6 KiB
C++

// Copyright (c) 2024Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "AdminInstallUpdate.hpp"
#include <fstream>
#include <stdexcept>
#include "ss.hpp"
#include <vector>
#include <unistd.h>
#include <fcntl.h>
#include "Lv2Log.hpp"
#include <string.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <signal.h>
#include "UpdateResults.hpp"
#include "Updater.cpp"
using namespace pipedal;
namespace fs = std::filesystem;
static fs::path ROOT_INSTALL_DIRECTORY = "/var/pipedal/updates";
static fs::path INSTALLER_LOG_FILE_PATH = ROOT_INSTALL_DIRECTORY / "install.log";
static fs::path UPDATE_DIRECTORY = ROOT_INSTALL_DIRECTORY / "downloads";
static void removeSignalHandler(int signal)
{
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signal, &sa, NULL);
}
static int exec(const std::string &command)
{
std::string buffer = command;
std::vector<char *> argv;
char *p = const_cast<char *>(buffer.c_str());
while (*p)
{
char *start = p;
while (*p != ' ' && *p != '\0')
{
++p;
}
argv.push_back(start);
while (*p == ' ')
{
*p++ = '\0';
}
}
argv.push_back(nullptr);
pid_t pid;
if ((pid = fork()) == 0)
{
execv(argv[0], (char *const *)argv.data());
auto _ = write(1,"!\n",2);
exit(EXIT_FAILURE);
}
else
{
if (pid == -1)
{
auto _ = write(1,"*",1);
perror("execv");
return EXIT_FAILURE;
}
int returnValue;
waitpid(pid, &returnValue, 0);
int exitStatus = WEXITSTATUS(returnValue);
return exitStatus;
}
}
void updateLog(const std::string &message)
{
auto _ = write(1,message.c_str(),message.length());
_ = write(1,"\n",1);
}
static std::string getVersion(const fs::path path)
{
std::string fileNameOnly = path.filename();
std::vector<std::string> segments = split(path,'_');
if (segments.size() != 3) return "Unknown";
return SS("v" << segments[1]);
}
void pipedal::AdminInstallUpdate(const std::filesystem::path path)
{
UpdateResults updateResults;
updateResults.updated_ = true;
updateResults.updateVersion_ = getVersion(path);
try
{
// client needs a disconnect/reconnect even if the update doesn't happen.
exec("/usr/bin/systemctl stop pipedald");
fs:create_directories(ROOT_INSTALL_DIRECTORY);
Lv2Log::info(SS("Installing " << path));
if (!path.string().starts_with(UPDATE_DIRECTORY.string()))
{
throw std::runtime_error("Update file path is incorrect.");
}
if (!fs::exists(path))
{
throw std::runtime_error("File does not exist.");
}
std::stringstream ssCmd;
ssCmd << "/usr/bin/apt-get -q -y install " << path.string();
std::string cmd = ssCmd.str();
// direct standard outputs to a log file.
close(0);
close(1);
close(2);
int fd = open(INSTALLER_LOG_FILE_PATH.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1); // to stdout
dup2(fd, 2); // to stderr
close(fd);
fd = -1;
// disconnect std input from parent's stdin.
int fdNull = open("/dev/null", O_RDONLY, 0644);
dup2(fdNull, 0);
close(fdNull);
// verify the signature (again), now that we're running as root and could do real damage
// if we install somebody else's package.
Updater::ValidateSignature(path, SS(path.string() << ".asc")); // errors are thrown.
updateLog("** Installing update");
int retcode = exec(cmd);
if (retcode != EXIT_SUCCESS)
{
updateResults.updateSuccessful_ = false;
updateResults.updateMessage_ = SS("Update failed. See " << INSTALLER_LOG_FILE_PATH.c_str() << " for further details.");
} else {
updateResults.updateSuccessful_ = true;
updateResults.updateMessage_ = SS("Successfully updated to version " << updateResults.updateVersion_ << ".");
}
// in case we didn't actually update for some reason.
}
catch (const std::exception &e)
{
updateLog(SS("Error: " << e.what()));
updateResults.updated_ = false;
updateResults.updateSuccessful_ = false;
updateResults.updateMessage_ = e.what();
}
updateResults.Save();
// restart pipedal to get the client to remove the "Updating..." message.
exec("/usr/bin/systemctl start pipedald");
}