diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/Book.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/Book.hpp new file mode 100644 index 0000000..824b13c --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/Book.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct Book +{ + std::string name; + int count; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/CMakeLists.txt b/Teplov_Andrey 5030102.10201/Teplov_lab2/CMakeLists.txt new file mode 100644 index 0000000..75f3fce --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.5) + +project(DamirAsanov) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") + +find_package(Qt5 COMPONENTS Widgets REQUIRED) + +set(CMAKE_AUTOMOC ON) # for Q_OBJECT + +############ sockets ############ +set(SOURCES_SOCK + host_sock.cpp + HostUtils/HostWindow.cpp + ClientUtils/ClientWindow.cpp + conn_sock.cpp + semaphore.cpp +) + +set(HEADERS_SOCK + HostUtils/HostWindow.hpp + ClientUtils/ClientWindow.hpp + conn_sock.hpp + semaphore.hpp + logger.hpp +) + +add_executable(host_sock ${SOURCES_SOCK} ${HEADERS_SOCK}) +target_link_libraries(host_sock Qt5::Widgets) +target_include_directories(host_sock PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +############ queue ############ +set(SOURCES_MQ + host_mq.cpp + HostUtils/HostWindow.cpp + ClientUtils/ClientWindow.cpp + conn_mq.cpp + semaphore.cpp +) + +set(HEADERS_MQ + HostUtils/HostWindow.hpp + ClientUtils/ClientWindow.hpp + conn_mq.hpp + semaphore.hpp + logger.hpp +) + +add_executable(host_mq ${SOURCES_MQ} ${HEADERS_MQ}) +target_link_libraries(host_mq Qt5::Widgets) +target_include_directories(host_mq PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +############ fifo ############ +set(SOURCES_FIFO + host_fifo.cpp + HostUtils/HostWindow.cpp + ClientUtils/ClientWindow.cpp + conn_fifo.cpp + semaphore.cpp +) + +set(HEADERS_FIFO + HostUtils/HostWindow.hpp + ClientUtils/ClientWindow.hpp + conn_fifo.hpp + semaphore.hpp + logger.hpp +) + +add_executable(host_fifo ${SOURCES_FIFO} ${HEADERS_FIFO}) +target_link_libraries(host_fifo Qt5::Widgets) +target_include_directories(host_fifo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.cpp new file mode 100644 index 0000000..e1df01e --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.cpp @@ -0,0 +1,119 @@ +#include "ClientWindow.hpp" +#include +#include +#include +#include +#include "logger.hpp" + +ClientWindow::ClientWindow(const std::vector& books, QWidget* parent) + : QMainWindow(parent) { + stackedWidget = new QStackedWidget(this); + + createBookView(books); + createReadingView(); + createHistoryView(); + + setCentralWidget(stackedWidget); + setWindowTitle("Client Window"); + resize(400, 300); + + // Set started window + stackedWidget->setCurrentIndex(0); +} + +ClientWindow::~ClientWindow() {} + +void ClientWindow::createBookView(const std::vector& books) { + QWidget* bookView = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(bookView); + + bookList = new QListWidget(this); + for (const auto& book : books) { + bookList->addItem(QString::fromStdString(book.name) + " - " + QString::number(book.count) + " copies"); + } + layout->addWidget(bookList); + + selectButton = new QPushButton("Select Book", this); + selectButton->setEnabled(false); + layout->addWidget(selectButton); + + //terminateClientButton = new QPushButton("Terminate Client", this); + //layout->addWidget(terminateClientButton); + + // Enable button if book is choosed + connect(bookList, &QListWidget::itemSelectionChanged, [this]() { + selectButton->setEnabled(bookList->currentItem() != nullptr); + }); + connect(selectButton, &QPushButton::clicked, this, &ClientWindow::selectBook); + //connect(terminateClientButton, &QPushButton::clicked, this, &ClientWindow::terminateClient); + + stackedWidget->addWidget(bookView); +} + +void ClientWindow::createReadingView() { + QWidget* readingView = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(readingView); + + readingLabel = new QLabel("Reading book: ", this); + layout->addWidget(readingLabel); + + cancelReadingButton = new QPushButton("Cancel Reading", this); + layout->addWidget(cancelReadingButton); + + connect(cancelReadingButton, &QPushButton::clicked, this, &ClientWindow::cancelReading); + + stackedWidget->addWidget(readingView); +} + +void ClientWindow::createHistoryView() { + historyList = new QListWidget(this); + historyList->setFixedWidth(350); + auto* widget = new QDockWidget("History", this); + widget->setWidget(historyList); + addDockWidget(Qt::LeftDockWidgetArea, widget); +} + +void ClientWindow::selectBook() { + if (bookList->currentItem()) { + QString bookName = bookList->currentItem()->text().split(" - ").first(); + readingLabel->setText("Reading book: " + bookName); + + emit bookSelected(bookName); + } +} + +void ClientWindow::cancelReading() { + QString bookName = readingLabel->text().split(": ").last(); + emit bookReturned(bookName); + + // Set started window + stackedWidget->setCurrentIndex(0); + + addHistory("Cancelled reading: ", bookName, true); +} + +void ClientWindow::addHistory(const QString& action, const QString& bookName, bool success) { + QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); + QString status = success ? "SUCCESS" : "FAIL"; + historyList->addItem(QString("[%1] %2 \"%3\": %4").arg(timestamp, action, bookName, status)); +} + +void ClientWindow::onSuccessTakeBook() { + // Set reading window + stackedWidget->setCurrentIndex(1); + + addHistory("TAKE book: ", bookList->currentItem()->text().split(" - ").first(), true); +} + +void ClientWindow::onFailedTakeBook() { + // Cannot display this window.... + // QMessageBox::warning(nullptr, "FAIL", "Failed to take book"); + LoggerClient::get_instance().log(Status::ERROR, "Failed to take book"); + + addHistory("TAKE book: ", bookList->currentItem()->text().split(" - ").first(), false); +} + +void ClientWindow::terminateClient() { + QMessageBox::information(this, "Terminate Client", "Client terminated."); + std::exit(0); // TODO: exit only qapp, but don't whole process +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.hpp new file mode 100644 index 0000000..5ade49b --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/ClientWindow.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Book.hpp" + +class ClientWindow : public QMainWindow { + Q_OBJECT + +public: + ClientWindow(const std::vector& books, QWidget* parent = nullptr); + ~ClientWindow(); + + void onSuccessTakeBook(); + void onFailedTakeBook(); + void addHistory(const QString& action, const QString& bookName, bool success); + +signals: + void bookSelected(const QString& bookName); + void bookReturned(const QString& bookName); + +private slots: + void selectBook(); + void cancelReading(); + void terminateClient(); + +private: + void createBookView(const std::vector& books); + void createReadingView(); + void createHistoryView(); + + QStackedWidget* stackedWidget; // For switch windows (started <-> reading) + + QListWidget* historyList; + QListWidget* bookList; + QPushButton* selectButton; + + QLabel* readingLabel; + QPushButton* cancelReadingButton; + + // QPushButton* terminateClientButton; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/clientProcessing.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/clientProcessing.hpp new file mode 100644 index 0000000..a199881 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/ClientUtils/clientProcessing.hpp @@ -0,0 +1,81 @@ +#include +#include "ClientWindow.hpp" +#include "conn_sock.hpp" +#include "logger.hpp" +#include "semaphore.hpp" +#include +#include +#include + +void listenForHostMessages(conn& conn, Semaphore& semaphore, ClientWindow& window, std::atomic& is_ranning) { + auto& logger = LoggerClient::get_instance(); + + while (is_ranning) { + + semaphore.Wait(); // Start critical section + char buffer[64] = {0}; + if (conn.Read(buffer, sizeof(buffer))) { + std::string response(buffer); + + if (response == "YES") { + window.onSuccessTakeBook(); + logger.log(Status::INFO, "Host gives the book"); + } + else if (response == "NO") { + window.onFailedTakeBook(); + logger.log(Status::INFO, "Host rejected the request: no such book"); + } + else { + logger.log(Status::ERROR, "Unexpected response from host: " + response); + } + } + + semaphore.Post(); // End critical section + sleep(0.01); + } +} + +int processClient(Semaphore& semaphore, conn& conn, const std::vector& books) { + auto& logger = LoggerClient::get_instance(); + + int argc = 0; + char** argv = nullptr; + QApplication app(argc, argv); + ClientWindow window(books); + + std::atomic is_running(true); + + // Start thread with listenning socket + std::thread listenerThread(listenForHostMessages, std::ref(conn), std::ref(semaphore), std::ref(window), std::ref(is_running)); + + QObject::connect(&window, &ClientWindow::bookSelected, [&semaphore, &conn, &logger](const QString& bookName) { + std::string request = "TAKE " + bookName.toStdString(); + if (conn.Write(request.c_str(), request.size())) { + logger.log(Status::INFO, "Requested book: " + bookName.toStdString()); + } + else { + logger.log(Status::ERROR, "Failed to request the book: " + bookName.toStdString()); + } + }); + + QObject::connect(&window, &ClientWindow::bookReturned, [&conn, &logger](const QString& bookName) { + std::string request = "RETURN " + bookName.toStdString(); + if (conn.Write(request.c_str(), request.size())) { + logger.log(Status::INFO, "Returned book: " + bookName.toStdString()); + } + else { + logger.log(Status::ERROR, "Failed to return the book: " + bookName.toStdString()); + } + }); + + window.show(); + int result = app.exec(); + + // Complete thread with listenning socket + is_running = false; + if (listenerThread.joinable()) { + listenerThread.join(); + } + + return result; +} \ No newline at end of file diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.cpp new file mode 100644 index 0000000..a682d2b --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.cpp @@ -0,0 +1,110 @@ +#include "HostWindow.hpp" +#include "logger.hpp" +#include +#include +#include +#include +#include +#include + +static int secondsLeft = 5; + +HostWindow::HostWindow(const std::string& hostTitle, const std::vector& books, QWidget* parent) + : QMainWindow(parent) { + + QWidget* centralWidget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(centralWidget); + + portLabel = new QLabel(QString::fromStdString(hostTitle), this); + layout->addWidget(portLabel); + + bookList = new QListWidget(this); + for (const auto& book : books) { + bookList->addItem(QString::fromStdString(book.name) + " - " + QString::number(book.count) + " copies"); + } + layout->addWidget(bookList); + + terminateClientButton = new QPushButton("Terminate Client", this); + layout->addWidget(terminateClientButton); + + terminateHostButton = new QPushButton("Terminate Host", this); + layout->addWidget(terminateHostButton); + + timerLabel = new QLabel("Time left: 5 seconds", this); + layout->addWidget(timerLabel); + + clientTimer = new QTimer(this); + clientTimer->setInterval(1000); + connect(clientTimer, &QTimer::timeout, this, [this]() { + if (!clientTimer->isActive()) return; + + if (--secondsLeft <= 0) { + terminateClient(); + } else { + timerLabel->setText(QString("Time left: %1 seconds").arg(secondsLeft)); + } + }); + + connect(terminateClientButton, &QPushButton::clicked, this, &HostWindow::terminateClient); + connect(terminateHostButton, &QPushButton::clicked, this, &HostWindow::terminateHost); + connect(this, &HostWindow::resetSignalTimer, this, &HostWindow::resetTimer); + connect(this, &HostWindow::stopSignalTimer, this, [this]() { + timerLabel->setText("Client reading"); + clientTimer->stop(); + }); + + historyList = new QListWidget(this); + QDockWidget* historyDock = new QDockWidget("History", this); + historyDock->setWidget(historyList); + addDockWidget(Qt::RightDockWidgetArea, historyDock); + + setCentralWidget(centralWidget); + setWindowTitle("Host Window"); + resize(600, 450); + + clientTimer->start(); +} + +HostWindow::~HostWindow() {} + +void HostWindow::updateBooks(const std::vector& books) { + bookList->clear(); + for (const auto& book : books) { + bookList->addItem(QString::fromStdString(book.name) + " - " + QString::number(book.count) + " copies"); + } +} + +void HostWindow::addHistory(const QString& action, const QString& bookName, bool success) { + QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); + QString status = success ? "SUCCESS" : "FAIL"; + historyList->addItem(QString("[%1] Client ID: 1, %2 \"%3\": %4").arg(timestamp, action, bookName, status)); +} + +void HostWindow::terminateClient() { + clientTimer->stop(); + timerLabel->setText("Client terminated"); + QMessageBox::information(this, "Terminate Client", "Client terminated."); + LoggerHost::get_instance().log(Status::INFO, "Terminate Client"); + kill(clientPid, SIGKILL); +} + +void HostWindow::terminateHost() { + QMessageBox::information(this, "Terminate Host", "Host terminated."); + LoggerHost::get_instance().log(Status::INFO, "Terminate Host"); + kill(clientPid, SIGKILL); // and kill client too + std::exit(0); +} + +void HostWindow::resetTimer() { + secondsLeft = 5; + timerLabel->setText("Time left: 5 seconds"); + clientTimer->start(); +} + +void HostWindow::signalResetTimer() { + emit resetSignalTimer(); +} + +void HostWindow::signalStopTimer() { + emit stopSignalTimer(); +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.hpp new file mode 100644 index 0000000..fdf0083 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/HostWindow.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Book.hpp" + +class HostWindow : public QMainWindow { + Q_OBJECT + +public: + HostWindow(const std::string& hostTitle, const std::vector& books, QWidget* parent = nullptr); + virtual ~HostWindow(); + + void updateBooks(const std::vector& books); + void signalResetTimer(); + void signalStopTimer(); + + void addHistory(const QString& action, const QString& bookName, bool success); + + pid_t clientPid; // for kill + +signals: + void resetSignalTimer(); + void stopSignalTimer(); + +private slots: + void terminateClient(); + void terminateHost(); + void resetTimer(); + +private: + QLabel* portLabel; + QListWidget* bookList; + QListWidget* historyList; + QPushButton* terminateClientButton; + QPushButton* terminateHostButton; + + QTimer* clientTimer; + QLabel* timerLabel; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/hostProcessing.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/hostProcessing.hpp new file mode 100644 index 0000000..9ff5353 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/HostUtils/hostProcessing.hpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include "HostWindow.hpp" +#include "conn_sock.hpp" +#include "semaphore.hpp" +#include "logger.hpp" +#include "Book.hpp" +#include + +bool takeBook(std::vector& books, const std::string& bookName, LoggerHost& logger) { + for (auto& book : books) { + if (book.name == bookName) { + if (book.count > 0) { + book.count--; + logger.log(Status::INFO, "Book taken: " + bookName); + return true; + } else { + logger.log(Status::INFO, "Book not available: " + bookName); + return false; + } + } + } + + logger.log(Status::INFO, "No such Book in library: " + bookName); + return false; +} + +bool returnBook(std::vector& books, const std::string& bookName, LoggerHost& logger) { + for (auto& book : books) { + if (book.name == bookName) { + book.count++; + logger.log(Status::INFO, "Book returned: " + bookName); + return true; + } + } + + logger.log(Status::INFO, "No such Book in library: " + bookName); + return false; +} + +void listenForClientMessages(conn& conn, Semaphore& semaphore, std::vector& books, HostWindow& window, std::atomic& is_running) { + auto& logger = LoggerHost::get_instance(); + + while (is_running) { + semaphore.Wait(); // Start critical section + + char buffer[1024] = {0}; + if (conn.Read(buffer, sizeof(buffer))) { + std::string request(buffer); + logger.log(Status::INFO, "Request is recieved: " + request); + + if (request.rfind("TAKE ", 0) == 0) { + std::string bookName = request.substr(5); + bool res = takeBook(books, bookName, logger); + if (res) + { + window.signalStopTimer(); + std::string response = "YES"; + if (conn.Write(response.c_str(), response.size())) { + logger.log(Status::INFO, "Host response successfully"); + } + else { + logger.log(Status::ERROR, "Failed to response"); + } + } + else + { + window.signalResetTimer(); + std::string response = "NO"; + if (conn.Write(response.c_str(), response.size())) { + logger.log(Status::INFO, "Host response successfully"); + } + else { + logger.log(Status::ERROR, "Failed to response"); + } + } + + window.addHistory("TAKE book: ", QString::fromStdString(bookName), res); + } else if (request.rfind("RETURN ", 0) == 0) { + window.signalResetTimer(); + std::string bookName = request.substr(7); + bool res = returnBook(books, bookName, logger); // client unexpected any response on this request + + window.addHistory("RETURN book: ", QString::fromStdString(bookName), res); + } + + window.updateBooks(books); + } + + semaphore.Post(); // End critical section + sleep(0.01); + } +} + +int processHost(const std::string& hostTitle, Semaphore& semaphore, conn& conn, std::vector books, QApplication& app, pid_t pid) { + LoggerHost::get_instance().log(Status::INFO, "Host is running"); + std::atomic is_running(true); + HostWindow window(hostTitle, books); + window.clientPid = pid; + + // Start host's listenning in new thread + std::thread listenerThread(listenForClientMessages, std::ref(conn), std::ref(semaphore), std::ref(books), std::ref(window), std::ref(is_running)); + + window.show(); + int result = app.exec(); // Start window in this thread + + // Complete thread with listenning socket + is_running = false; + if (listenerThread.joinable()) { + listenerThread.join(); + } + + return result; +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/README.md b/Teplov_Andrey 5030102.10201/Teplov_lab2/README.md new file mode 100644 index 0000000..6075944 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/README.md @@ -0,0 +1,31 @@ +# Проект: Demon + +# Автор + +Теплов Андрей Сергеевич 5030102/10201 Лабораторная работа №2 вариант №23: + +## Описание + +Родственные. Один к одному. 4, 6, 7 + +Библиотека. Хост - библиотека, клиенты - читатели. Хост по запросу выдает книги (при наличии их в библиотеке), либо говорит что +книга занята/отсутсвует и получает их обратно. Клиенты запрашивают интересующие их книги, читают их какое-то время и возвращают +в библиотеку (скорость чтения случайна и линейна во времени). Статус всех книг библиотеки (название, сколько доступно свободных +копий, кем и когда взята) отражаются в графическом интерфейсе. В интерфейсе клиентов их история запросов (когда и какие книги +они брали и возвращали). + +4. Очереди сообщений (mq_open). TYPE_CODE - mq. Работа с очередью должна производиться в каждом процессе уже после создания + дочернего (так, будто это несвязанные процессы). + +5. Именованные каналы (mkfifo). TYPE_CODE - fifo. Работа с именованным каналом должна производиться в каждом процессе уже + после создания дочернего (так, будто это несвязанные процессы). После окончания работы родительский процесс должен удалять + файл именованного канала. + +6. Сокеты (socket). TYPE_CODE – sock + +## Запуск + +введите команду `bash build.sh`, чтобы собрать проект +введите команду `bash runHostSock.sh`, чтобы запустить сокетное взаимодействие +введите команду `bash runHostMq.sh`, чтобы запустить очередь сообщений взаимодействие +введите команду `bash runHostFifo.sh`, чтобы запустить именнованный канал взаимодействие diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/build.sh b/Teplov_Andrey 5030102.10201/Teplov_lab2/build.sh new file mode 100644 index 0000000..a90ec84 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +mkdir -p build +cd build +cmake .. +make + +find . -type f -name '*.o' -delete +find . -type f -name '*.cmake' -delete +find . -type f -name 'CMakeCache.txt' -delete +find . -type f -name 'Makefile' -delete +rm -rf CMakeFiles +rm -rf host_sock_autogen +rm -rf host_mq_autogen +rm -rf host_fifo_autogen +rm -rf ClientUtils +rm -rf HostUtils diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn.hpp new file mode 100644 index 0000000..306846a --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn.hpp @@ -0,0 +1,11 @@ +#pragma once + +class conn { +public: + virtual ~conn() {}; + + virtual bool Read(void* buf, size_t count) = 0; + virtual bool Write(const void* buf, size_t count) = 0; + + virtual bool IsInitialized() const = 0; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.cpp new file mode 100644 index 0000000..69f2bcb --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.cpp @@ -0,0 +1,83 @@ +#include "conn_fifo.hpp" + +ConnFifo::ConnFifo(const std::string& fifoPath, LoggerHost& logger) : path(fifoPath), readFd(-1), writeFd(-1) { + + if (mkfifo(path.c_str(), 0666) == -1) { + if (errno != EEXIST) { + logger.log(Status::ERROR, "Failed to create FIFO"); + return; + } + } + + readFd = open(path.c_str(), O_RDONLY | O_NONBLOCK); // open for read + if (readFd == -1) { + logger.log(Status::ERROR, "Failed to open FIFO for reading"); + return; + } + + writeFd = open(path.c_str(), O_WRONLY); // open for write + if (writeFd == -1) { + logger.log(Status::ERROR, "Failed to open FIFO for writing"); + close(readFd); + return; + } +} + +ConnFifo::ConnFifo(const std::string& fifoPath, LoggerClient& logger) : path(fifoPath), readFd(-1), writeFd(-1) { + + readFd = open(path.c_str(), O_RDONLY | O_NONBLOCK); // open for read + if (readFd == -1) { + logger.log(Status::ERROR, "Failed to open FIFO for reading"); + return; + } + + writeFd = open(path.c_str(), O_WRONLY); // open for write + if (writeFd == -1) { + logger.log(Status::ERROR, "Failed to open FIFO for writing"); + close(readFd); + return; + } +} + +bool ConnFifo::IsInitialized() const { + return writeFd != -1 && readFd != -1; +} + +ConnFifo::~ConnFifo() { + if (readFd != -1) { + close(readFd); + } + if (writeFd != -1) { + close(writeFd); + } + + if (!path.empty()) { + unlink(path.c_str()); + } +} + +bool ConnFifo::Read(void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + ssize_t bytesRead = read(readFd, buf, count); + if (bytesRead == -1) { + return false; + } + + return bytesRead > 0; +} + +bool ConnFifo::Write(const void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + ssize_t bytesWritten = write(writeFd, buf, count); + if (bytesWritten == -1) { + return false; + } + + return static_cast(bytesWritten) == count; +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.hpp new file mode 100644 index 0000000..113011a --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_fifo.hpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include "conn.hpp" +#include "logger.hpp" + +class ConnFifo : public conn { +public: + ConnFifo(const std::string& fifoPath, LoggerHost& logger); + ConnFifo(const std::string& fifoPath, LoggerClient& logger); + ~ConnFifo() override; + + bool Read(void* buf, size_t count) override; + bool Write(const void* buf, size_t count) override; + + bool IsInitialized() const override; + +private: + std::string path; + int readFd; + int writeFd; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.cpp new file mode 100644 index 0000000..89e7d07 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.cpp @@ -0,0 +1,76 @@ +#include "conn_mq.hpp" +#include +#include +#include + +ConnMq::ConnMq(key_t key, LoggerHost& logger) : queueKey(key), isHost(true) +{ + int flags = IPC_CREAT | 0666; + queueId = msgget(key, flags); + + if (queueId == -1) { + logger.log(Status::ERROR, "Failed to open message queue"); + } +} + +ConnMq::ConnMq(key_t key, LoggerClient& logger) : queueKey(key), isHost(false) +{ + int flags = 0666; + queueId = msgget(key, flags); + + if (queueId == -1) { + logger.log(Status::ERROR, "Failed to open message queue"); + } +} + +ConnMq::~ConnMq() { + if (IsInitialized()) { + msgctl(queueId, IPC_RMID, nullptr); // TODO: some check + } +} + +bool ConnMq::Read(void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + struct msgbuf { + long mtype; + char mtext[1024]; + } message; + + ssize_t bytesRead = msgrcv(queueId, &message, sizeof(message.mtext), !isHost + 1, 0); // !isHost: if host -> read client else read host + if (bytesRead == -1) { + return false; + } + + std::memcpy(buf, message.mtext, std::min(count, static_cast(bytesRead))); + return true; +} + +bool ConnMq::Write(const void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + struct msgbuf { + long mtype; + char mtext[1024]; + } message; + + message.mtype = isHost + 1; // if host -> write as host (+ 1 because mtype > 0) + if (count > sizeof(message.mtext)) { + return false; + } + + std::memcpy(message.mtext, buf, count); + if (msgsnd(queueId, &message, count, 0) == -1) { + return false; + } + + return true; +} + +bool ConnMq::IsInitialized() const { + return (queueId != -1); +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.hpp new file mode 100644 index 0000000..115b74c --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_mq.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include "conn.hpp" +#include "logger.hpp" + +class ConnMq : public conn { +public: + ConnMq(key_t key, LoggerHost& logger); + ConnMq(key_t key, LoggerClient& logger); + ~ConnMq() override; + + bool Read(void* buf, size_t count) override; + bool Write(const void* buf, size_t count) override; + + bool IsInitialized() const override; + +private: + key_t queueKey; + int queueId; + bool isHost; // for read/write msg type +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.cpp new file mode 100644 index 0000000..172ae76 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.cpp @@ -0,0 +1,109 @@ +#include "conn_sock.hpp" +#include +#include +#include +#include + +ConnSock::ConnSock(int hostPort, LoggerHost& logger) : sock_fd(-1) { + logger.log(Status::INFO, "Creating socket for Host"); + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + if (sock_fd == -1) { + logger.log(Status::ERROR, "Socket creation failed"); + return; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(hostPort); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + logger.log(Status::ERROR, "Bind failed"); + close(sock_fd); + sock_fd = -1; + return; + } + + if (listen(sock_fd, 1) == -1) { + logger.log(Status::ERROR, "Listen failed"); + close(sock_fd); + sock_fd = -1; + return; + } + + logger.log(Status::INFO, "Socket successfully created and listening"); +} + +ConnSock::ConnSock(int hostPort, LoggerClient& logger) : sock_fd(-1) { + logger.log(Status::INFO, "Creating socket for Client"); + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + if (sock_fd == -1) { + logger.log(Status::ERROR, "Socket creation failed"); + return; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(hostPort); + + if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) <= 0) { + logger.log(Status::ERROR, "Invalid host IP address"); + close(sock_fd); + sock_fd = -1; + return; + } + + if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + logger.log(Status::ERROR, "Connect failed"); + close(sock_fd); + sock_fd = -1; + return; + } + + logger.log(Status::INFO, "Connected to host successfully"); +} + +ConnSock* ConnSock::Accept(LoggerHost& logger) { + struct sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + int client_fd = accept(sock_fd, (struct sockaddr*)&client_addr, &client_len); + if (client_fd == -1) { + logger.log(Status::ERROR, "Accept failed"); + return nullptr; + } + + logger.log(Status::INFO, "Client connection accepted"); + // make new socket for communication with client + ConnSock* conn = new ConnSock(); + conn->sock_fd = client_fd; + conn->addr = client_addr; + return conn; +} + +bool ConnSock::Read(void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + ssize_t bytes_read = recv(sock_fd, buf, count, 0); + if (bytes_read <= 0) { + return false; + } + return true; +} + +bool ConnSock::Write(const void* buf, size_t count) { + if (!IsInitialized()) { + return false; + } + + ssize_t bytes_sent = send(sock_fd, buf, count, 0); + if (bytes_sent <= 0) { + return false; + } + return true; +} + +ConnSock::~ConnSock() { + if (sock_fd != -1) { + close(sock_fd); + } +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.hpp new file mode 100644 index 0000000..eedfbdf --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/conn_sock.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include "logger.hpp" +#include "conn.hpp" + +class ConnSock : public conn { +public: + ConnSock(int hostPort, LoggerHost& logger); + ConnSock(int hostPort, LoggerClient& logger); + ~ConnSock(); + + ConnSock* Accept(LoggerHost& logger); // only for host + + bool Read(void* buf, size_t count); + bool Write(const void* buf, size_t count); + + bool IsInitialized() const { + return sock_fd != -1; + } + +private: + int sock_fd; // Socket file Descriptor + struct sockaddr_in addr; // socket address + + ConnSock() = default; // for accept +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/host_fifo.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_fifo.cpp new file mode 100644 index 0000000..c30a84b --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_fifo.cpp @@ -0,0 +1,53 @@ +#include +#include "HostUtils/HostWindow.hpp" +#include "HostUtils/hostProcessing.hpp" +#include "ClientUtils/clientProcessing.hpp" +#include "conn_fifo.hpp" +#include "semaphore.hpp" +#include "logger.hpp" + +int main(int argc, char* argv[]) { + + // initialize some stuff + std::vector books = { + {"Book 1", 10}, + {"Book 2", 5}, + {"Book 3", 20}, + {"Book 4", 0} + }; + Semaphore semaphore(1); + + auto& logger = LoggerHost::get_instance(); + + ConnFifo hostFifo("/tmp/my_fifo", logger); + if (!hostFifo.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize host queue"); + return EXIT_FAILURE; + } + + // Make child process + pid_t pid = fork(); + if (pid == -1) { + + // Error + logger.log(Status::ERROR, "Failed to fork client"); + return EXIT_FAILURE; + + } else if (pid == 0) { + + // this is child process -> start client + ConnFifo clientFifo("/tmp/my_fifo", LoggerClient::get_instance()); + if (!clientFifo.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize client queue"); + return EXIT_FAILURE; + } + return processClient(semaphore, clientFifo, books); + + } else { + + // this is main process -> start host + QApplication app(argc, argv); + return processHost("Communication by queue", semaphore, hostFifo, books, app, pid); + + } +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/host_mq.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_mq.cpp new file mode 100644 index 0000000..84aa3b2 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_mq.cpp @@ -0,0 +1,56 @@ +#include +#include "HostUtils/HostWindow.hpp" +#include "HostUtils/hostProcessing.hpp" +#include "ClientUtils/clientProcessing.hpp" +#include "conn_mq.hpp" +#include "semaphore.hpp" +#include "logger.hpp" +#include +#include + +int main(int argc, char* argv[]) { + + // initialize some stuff + key_t key = ftok("LALALA", 65); + std::vector books = { + {"Book 1", 10}, + {"Book 2", 5}, + {"Book 3", 20}, + {"Book 4", 0} + }; + Semaphore semaphore(1); + + auto& logger = LoggerHost::get_instance(); + + ConnMq hostMq(key, logger); + if (!hostMq.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize host queue"); + return EXIT_FAILURE; + } + + // Make child process + pid_t pid = fork(); + if (pid == -1) { + + // Error + logger.log(Status::ERROR, "Failed to fork client"); + return EXIT_FAILURE; + + } else if (pid == 0) { + + // this is child process -> start client + ConnMq clientMq(key, LoggerClient::get_instance()); + if (!clientMq.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize client queue"); + return EXIT_FAILURE; + } + return processClient(semaphore, clientMq, books); + + } else { + + // this is main process -> start host + QApplication app(argc, argv); + return processHost("Communication by queue", semaphore, hostMq, books, app, pid); + + } +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/host_sock.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_sock.cpp new file mode 100644 index 0000000..d89150a --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/host_sock.cpp @@ -0,0 +1,72 @@ +#include +#include "HostUtils/HostWindow.hpp" +#include "HostUtils/hostProcessing.hpp" +#include "ClientUtils/clientProcessing.hpp" +#include "conn_sock.hpp" +#include "semaphore.hpp" +#include "logger.hpp" +#include +#include + +int main(int argc, char* argv[]) { + + // take port + if (argc != 2) { + std::cerr << "Usage: ./host " << std::endl; + return EXIT_FAILURE; + } + + // initialize some stuff + int port = std::stoi(argv[1]); + std::vector books = { + {"Book 1", 10}, + {"Book 2", 5}, + {"Book 3", 20}, + {"Book 4", 0} + }; + Semaphore semaphore(1); + + auto& logger = LoggerHost::get_instance(); + + // Create hostSocket for incomming connection requests from client + ConnSock hostSocket(port, logger); + if (!hostSocket.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize host socket"); + return EXIT_FAILURE; + } + + // Make child process + pid_t pid = fork(); + if (pid == -1) { + + // Error + logger.log(Status::ERROR, "Failed to fork client"); + return EXIT_FAILURE; + + } else if (pid == 0) { + + // this is child process -> start client + ConnSock clientSocket(port, LoggerClient::get_instance()); + if (!clientSocket.IsInitialized()) { + logger.log(Status::ERROR, "Failed to initialize client socket"); + return EXIT_FAILURE; + } + return processClient(semaphore, clientSocket, books); + + } else { + + // this is main process -> start host + ConnSock* conn = hostSocket.Accept(logger); + if (!conn) + { + logger.log(Status::ERROR, "Failed to accept connection"); + return EXIT_FAILURE; + } + + QApplication app(argc, argv); + int res = processHost("Host Port: " + std::string(argv[1]), semaphore, *conn, books, app, pid); + delete conn; + return res; + + } +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/logger.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/logger.hpp new file mode 100644 index 0000000..65d2e86 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/logger.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +enum class Status : bool +{ +INFO, +ERROR +}; + +class LoggerClient +{ +public: + static LoggerClient& get_instance() + { + static LoggerClient instance; + return instance; + } + + void log(Status status, const std::string& message) + { + if (status == Status::INFO) + std::cout << "[CLIENT][INFO] " << message << std::endl; + else + std::cout << "[CLIENT][ERROR] " << message << std::endl; + } + +private: + LoggerClient() = default; + LoggerClient(const LoggerClient&) = delete; + LoggerClient& operator=(const LoggerClient&) = delete; +}; + +class LoggerHost +{ +public: + static LoggerHost& get_instance() + { + static LoggerHost instance; + return instance; + } + + void log(Status status, const std::string& message) + { + if (status == Status::INFO) + std::cout << "[HOST][INFO] " << message << std::endl; + else + std::cout << "[HOST][ERROR] " << message << std::endl; + } + +private: + LoggerHost() = default; + LoggerHost(const LoggerHost&) = delete; + LoggerHost& operator=(const LoggerHost&) = delete; +}; diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostFifo.sh b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostFifo.sh new file mode 100644 index 0000000..d69c25f --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostFifo.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./build/host_fifo diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostMq.sh b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostMq.sh new file mode 100644 index 0000000..e21d592 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostMq.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./build/host_mq diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostSock.sh b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostSock.sh new file mode 100644 index 0000000..303f51c --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/runHostSock.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./build/host_sock 10107 diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.cpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.cpp new file mode 100644 index 0000000..9277f64 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.cpp @@ -0,0 +1,32 @@ +#include "semaphore.hpp" +#include "logger.hpp" +#include +#include + +Semaphore::Semaphore(unsigned int value) { + if (sem_init(&semaphore, 0, value) == -1) { + LoggerHost::get_instance().log(Status::ERROR, "Semaphore initialization failed"); + } +} + +bool Semaphore::Wait() { + if (sem_wait(&semaphore) == -1) { + LoggerHost::get_instance().log(Status::ERROR, "Semaphore wait failed"); + return false; + } + return true; +} + +bool Semaphore::Post() { + if (sem_post(&semaphore) == -1) { + LoggerHost::get_instance().log(Status::ERROR, "Semaphore post failed"); + return false; + } + return true; +} + +Semaphore::~Semaphore() { + if (sem_destroy(&semaphore) == -1) { + LoggerHost::get_instance().log(Status::ERROR, "Semaphore destruction failed"); + } +} diff --git a/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.hpp b/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.hpp new file mode 100644 index 0000000..38cc089 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/Teplov_lab2/semaphore.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +class Semaphore { +public: + Semaphore(unsigned int value); + bool Wait(); + bool Post(); + ~Semaphore(); + +private: + sem_t semaphore; +}; diff --git a/Teplov_Andrey 5030102.10201/lab1 _variant22/config.txt b/Teplov_Andrey 5030102.10201/lab1 _variant22/config.txt new file mode 100644 index 0000000..af17430 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/lab1 _variant22/config.txt @@ -0,0 +1 @@ +src_folder dest_folder txt TEXTFILES diff --git a/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.cpp b/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.cpp new file mode 100644 index 0000000..443f5f5 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.cpp @@ -0,0 +1,255 @@ +#include "daemon.hpp" + +// Constants +const char* PID_FILE = "/var/run/os-lab-daemon.pid"; +const mode_t DIR_PERMISSIONS = 0777; +const int PID_STR_SIZE = 10; + +void Daemon::run(const std::string& config_path, int interval = 5) { + // Store the current working directory + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + syslog( + LOG_ERR, + "Could not get current working directory: %s", + strerror(errno) + ); + exit(EXIT_FAILURE); + } + this->current_dir = std::string(cwd); + + this->config_path = config_path; + this->interval = interval; + + openlog("os-lab-daemon", LOG_PID | LOG_CONS, LOG_DAEMON); + syslog(LOG_INFO, "Daemon starting up"); + + daemonize(); + read_config(); + setup_signal_handlers(); + run_main_loop(); +}; + +void Daemon::daemonize() { + pid_t pid = fork(); + // Handle error + if (pid < 0) { + syslog(LOG_ERR, "Fork failed: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + // Parent process. Exit + if (pid > 0) { + exit(EXIT_SUCCESS); + } + std::cout << "pid: " << pid << std::endl; + + umask(0); + + pid_t sid = setsid(); + if (sid < 0) { + syslog(LOG_ERR, "setsid failed: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + std::cout << "sid: " << sid << std::endl; + + if (chdir("/") < 0) { + syslog(LOG_ERR, "chdir failed: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + std::cout << "chdir done" << std::endl; + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + create_pid_file(); +}; + +void Daemon::create_pid_file() { + int pid_file_handle = open(PID_FILE, O_RDWR | O_CREAT, 0600); + if (pid_file_handle == -1) { + syslog( + LOG_ERR, + "Could not open PID file %s: %s", + PID_FILE, + strerror(errno) + ); + exit(EXIT_FAILURE); + } + + if (lockf(pid_file_handle, F_TLOCK, 0) == -1) { + syslog( + LOG_ERR, + "Could not lock PID file %s: %s", + PID_FILE, + strerror(errno) + ); + exit(EXIT_FAILURE); + } + + char old_pid_str[PID_STR_SIZE]; + if (read(pid_file_handle, old_pid_str, sizeof(old_pid_str) - 1) > 0) { + int old_pid = atoi(old_pid_str); + + if (old_pid > 0 && kill(old_pid, 0) == 0) { + syslog( + LOG_INFO, + "Process with PID %d is already running, sending SIGTERM", + old_pid + ); + kill(old_pid, SIGTERM); + sleep(1); + } + } + + ftruncate(pid_file_handle, 0); + lseek(pid_file_handle, 0, SEEK_SET); + + char str[PID_STR_SIZE]; + sprintf(str, "%d\n", getpid()); + write(pid_file_handle, str, strlen(str)); + + syslog( + LOG_INFO, + "PID file %s created successfully with PID %d", + PID_FILE, + getpid() + ); + + close(pid_file_handle); +}; + +void Daemon::read_config() { + std::ifstream config_file(resolve_path(config_path)); + + if (!config_file.is_open()) { + syslog( + LOG_ERR, + "Could not open config file %s: %s", + config_path.c_str(), + strerror(errno) + ); + exit(EXIT_FAILURE); + } + + std::string line; + std::regex re(R"((\"[^\"]+\"|\S+)\s+(\"[^\"]+\"|\S+)\s+(\S+)\s+(\"[^\"]+\"|\S+))"); + while (std::getline(config_file, line)) { + std::smatch match; + if (std::regex_search(line, match, re) && match.size() == 5) { + std::string folder1 = match[1].str(); + std::string folder2 = match[2].str(); + std::string ext = match[3].str(); + std::string subfolder = match[4].str(); + + // Remove quotes if present + folder1 = remove_quotes(folder1); + folder2 = remove_quotes(folder2); + subfolder = remove_quotes(subfolder); + + // Resolve relative paths + folder1 = resolve_path(folder1); + folder2 = resolve_path(folder2); + + config[folder1] = {folder2, {ext, subfolder}}; + } + } + config_file.close(); +}; + +std::string Daemon::remove_quotes(const std::string& str) { + if (str.front() == '"' && str.back() == '"') { + return str.substr(1, str.size() - 2); + } + return str; +}; + +std::string Daemon::resolve_path(const std::string &path) { + if (path.front() != '/') { + return current_dir + "/" + path; + } + return path; +}; + +void signal_handler(int sig) { + switch (sig) { + case SIGHUP: + Daemon::get_instance().read_config(); + syslog(LOG_INFO, "Re-read config file"); + break; + case SIGTERM: + syslog(LOG_INFO, "Received SIGTERM, exiting"); + closelog(); + exit(EXIT_SUCCESS); + break; + } +}; + +void Daemon::setup_signal_handlers() { + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); +}; + +void Daemon::run_main_loop() { + while (true) { + for (const auto& entry : config) { + const std::string& folder1 = entry.first; + const std::string& folder2 = entry.second.first; + const std::string& ext = entry.second.second.first; + const std::string& subfolder = entry.second.second.second; + + process_folders(folder1, folder2, ext, subfolder); + } + + sleep(interval); + } +}; + +void Daemon::process_folders( + const std::string& folder1, + const std::string& folder2, + const std::string& ext, + const std::string& subfolder +) { + DIR* dir = opendir(folder1.c_str()); + if (dir == NULL) { + syslog( + LOG_ERR, + "Could not open directory %s: %s", + folder1.c_str(), + strerror(errno) + ); + return; + } + + struct dirent* ent; + + const std::string targetSubfolder = folder2 + "/" + subfolder; + const std::string targetOthers = folder2 + "/OTHERS"; + + mkdir(targetSubfolder.c_str(), DIR_PERMISSIONS); + mkdir(targetOthers.c_str(), DIR_PERMISSIONS); + + while ((ent = readdir(dir)) != NULL) { + if (ent->d_type != DT_REG) + continue; + + const std::string fileName = ent->d_name; + const std::string fileExt = fileName.substr(fileName.find_last_of(".") + 1); + const std::string targetFolder = (fileExt == ext) ? targetSubfolder : targetOthers; + const std::string sourceFile = folder1 + "/" + fileName; + const std::string targetFile = targetFolder + "/" + fileName; + + if (rename(sourceFile.c_str(), targetFile.c_str()) != 0) { + syslog( + LOG_ERR, + "Error moving file %s to %s: %s", + sourceFile.c_str(), + targetFile.c_str(), + strerror(errno) + ); + } + } + + closedir(dir); +}; diff --git a/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.hpp b/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.hpp new file mode 100644 index 0000000..8a6c724 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/lab1 _variant22/daemon.hpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Daemon { + public: + static Daemon& get_instance() { + static Daemon instance; + return instance; + }; + + void run(const std::string& config_path, int interval); + + private: + Daemon() = default; + ~Daemon() = default; + Daemon(const Daemon&) = delete; + Daemon& operator = (const Daemon&) = delete; + + std::string current_dir; + std::string config_path; + + std::map>> config; + int interval; + + void daemonize(); + void create_pid_file(); + void read_config(); + void setup_signal_handlers(); + friend void signal_handler(int sig); + void run_main_loop(); + std::string remove_quotes(const std::string& str); + std::string resolve_path(const std::string& path); + + void process_folders( + const std::string& folder1, + const std::string& folder2, + const std::string& ext, + const std::string& subfolder + ); +}; diff --git a/Teplov_Andrey 5030102.10201/lab1 _variant22/main.cpp b/Teplov_Andrey 5030102.10201/lab1 _variant22/main.cpp new file mode 100644 index 0000000..ad9ff43 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/lab1 _variant22/main.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "daemon.cpp" + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Incorrect parameters. Usage: " << argv[0] << " " << std::endl; + return EXIT_FAILURE; + } + + const char* config_file_path = argv[1]; + + if (access(config_file_path, R_OK) != 0) { + std::cerr << "Error: Cannot access config file " << config_file_path << ": " << strerror(errno) << std::endl; + return EXIT_FAILURE; + } + + try { + std::cout << "Starting daemon with config file: " << config_file_path << std::endl; + Daemon::get_instance().run(config_file_path); + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}; diff --git a/Teplov_Andrey 5030102.10201/lab1 _variant22/run.sh b/Teplov_Andrey 5030102.10201/lab1 _variant22/run.sh new file mode 100644 index 0000000..5652308 --- /dev/null +++ b/Teplov_Andrey 5030102.10201/lab1 _variant22/run.sh @@ -0,0 +1,40 @@ +# Minimum required g++ version +REQUIRED_GPP_VERSION=10 + +# Check if g++ is installed +if ! command -v g++ > /dev/null 2>&1; then + echo "g++ is not installed. You can install it using:" + echo "sudo apt update && sudo apt install g++" + exit 1 +fi + +# Get the installed g++ version +GPP_VERSION=$(g++ -dumpversion | cut -d. -f1) + +# Check if the installed g++ version is sufficient +# if [[ $GPP_VERSION -ge $REQUIRED_GPP_VERSION ]]; then +# echo "g++ version $GPP_VERSION is installed." +# else +# echo "g++ version $GPP_VERSION is installed, but version $REQUIRED_GPP_VERSION or higher is required." +# echo "You can update g++ using:" +# echo "sudo apt update && sudo apt install g++-$REQUIRED_GPP_VERSION" +# exit 1 +# fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Compile the program +g++ -std=c++20 "$SCRIPT_DIR/main.cpp" -o "$SCRIPT_DIR/main" + +if [ $? -ne 0 ]; then + echo "Compilation failed." + exit 1 +else + +echo "Compilation successful. Running the program..." + +# Run the executable +"$SCRIPT_DIR/main" "$SCRIPT_DIR/config.txt" + +# Remove the executable after execution +rm "$SCRIPT_DIR/main"