diff --git a/Khamidullin.Ilsaf/lab1/.gitignore b/Khamidullin.Ilsaf/lab1/.gitignore new file mode 100644 index 0000000..ed4a29f --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/.gitignore @@ -0,0 +1,1045 @@ +##### Windows +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +##### Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +##### MacOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +##### Android +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +##### Backup +*.bak +*.gho +*.ori +*.orig +*.tmp + +##### GPG +secring.* + +##### Dropbox +# Dropbox settings and caches +.dropbox +.dropbox.attr +.dropbox.cache + +##### SynopsysVCS +# Waveform formats +*.vcd +*.vpd +*.evcd +*.fsdb + +# Default name of the simulation executable. A different name can be +# specified with this switch (the associated daidir database name is +# also taken from here): -o / +simv + +# Generated for Verilog and VHDL top configs +simv.daidir/ +simv.db.dir/ + +# Infrastructure necessary to co-simulate SystemC models with +# Verilog/VHDL models. An alternate directory may be specified with this +# switch: -Mdir= +csrc/ + +# Log file - the following switch allows to specify the file that will be +# used to write all messages from simulation: -l +*.log + +# Coverage results (generated with urg) and database location. The +# following switch can also be used: urg -dir .vdb +simv.vdb/ +urgReport/ + +# DVE and UCLI related files. +DVEfiles/ +ucli.key + +# When the design is elaborated for DirectC, the following file is created +# with declarations for C/C++ functions. +vc_hdrs.h + +##### SVN +.svn/ + +##### Mercurial +.hg/ +.hgignore +.hgsigs +.hgsub +.hgsubstate +.hgtags + +##### Bazaar +.bzr/ +.bzrignore + +##### CVS +/CVS/* +**/CVS/* +.cvsignore +*/.cvsignore + +##### TortoiseGit +# Project-level settings +/.tgitconfig + +##### PuTTY +# Private key +*.ppk + +##### Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +##### Emacs +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + +##### SublimeText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json +sftp-config-alt*.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +##### Notepad++ +# Notepad++ backups # +*.bak + +##### TextMate +*.tmproj +*.tmproject +tmtags + +##### VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +##### NetBeans +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +##### JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +##### Eclipse +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +##### Qt +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +##### VisualStudio +##### VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +##### Gradle +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +##### CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +##### C++ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# C/C++ binary extension file +*.bin + +##### C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Raspberry Pi Pico Object file +*.uf2 +# Raspberry Pi Pico disassembler file +*.dis \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/CMakeLists.txt b/Khamidullin.Ilsaf/lab1/CMakeLists.txt new file mode 100644 index 0000000..8050ded --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0.0) +project(lab1) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "-Wall -Werror") + +add_executable(lab1 main.cpp daemon.cpp) \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/build.sh b/Khamidullin.Ilsaf/lab1/build.sh new file mode 100755 index 0000000..4f9715b --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/build.sh @@ -0,0 +1,12 @@ +DIR_NAME="build_tmp" +EXE_NAME="lab1" +PID_PATH="/var/run/deep_deletion_daemon.pid" + +sudo touch $PID_PATH; sudo chmod 0666 $PID_PATH + +mkdir $DIR_NAME +cd $DIR_NAME +cmake ..; make +cd .. +cp $DIR_NAME/$EXE_NAME . +rm -r $DIR_NAME \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/config.txt b/Khamidullin.Ilsaf/lab1/config.txt new file mode 100644 index 0000000..ca1c5b6 --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/config.txt @@ -0,0 +1,2 @@ +/home/ilsaf/repos/Operating-Systems-labs-2024/Khamidullin.Ilsaf/lab1/dir +5 \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/daemon.cpp b/Khamidullin.Ilsaf/lab1/daemon.cpp new file mode 100644 index 0000000..23d175f --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/daemon.cpp @@ -0,0 +1,191 @@ +#include "daemon.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void handleSignal(int signal) +{ + switch (signal) + { + case SIGTERM: + syslog(LOG_INFO, "Daemon process terminated"); + DeepCleanerDaemon::Get().Terminate(); + break; + case SIGHUP: + syslog(LOG_INFO, "Reloading configuration"); + DeepCleanerDaemon::Get().ReloadConfig(); + break; + default: + break; + } +} + +void deleteDirectoriesByDepth(const std::string& rootPathStr, int depth) +{ + std::filesystem::path rootPath(rootPathStr); + + if (depth < 0) { + syslog(LOG_ERR, "Error: depth cannot be negative."); + return; + } + + for (const auto& entry : std::filesystem::directory_iterator(rootPath)) { + if (std::filesystem::is_directory(entry)) { + if (depth > 0) { + deleteDirectoriesByDepth(entry.path().string(), depth - 1); + } + syslog(LOG_INFO, "Removing directory: %s", entry.path().c_str()); + std::filesystem::remove_all(entry.path()); + } + } +} + +void DeepCleanerDaemon::KillExistingDaemon() +{ + syslog(LOG_INFO, "Checking for any existing daemon processes"); + std::ifstream pidFile(pidPath); + if (pidFile.is_open()) + { + int runningDaemonPid = 0; + if (pidFile >> runningDaemonPid && kill(runningDaemonPid, 0) == 0) + { + syslog(LOG_WARNING, "Stopping the existing daemon with PID: %i", runningDaemonPid); + kill(runningDaemonPid, SIGTERM); + } + } +} + +void DeepCleanerDaemon::WritePid() +{ + syslog(LOG_INFO, "Writing PID to file"); + std::ofstream pidFile(pidPath.c_str()); + if (!pidFile.is_open()) + { + syslog(LOG_ERR, "Error writing PID to file"); + exit(EXIT_FAILURE); + } + pidFile << getpid(); + syslog(LOG_INFO, "PID written successfully"); +} + +void DeepCleanerDaemon::Daemonize() +{ + syslog(LOG_INFO, "Starting daemonization process"); + + pid_t processId = fork(); + syslog(LOG_INFO, "Forked process with PID: %i", processId); + if (processId < 0) + { + syslog(LOG_ERR, "Error during fork"); + exit(EXIT_FAILURE); + } + if (processId > 0) + { + exit(EXIT_SUCCESS); + } + + umask(0); + if (setsid() < 0) + { + syslog(LOG_ERR, "Error setting group ID"); + exit(EXIT_FAILURE); + } + if (chdir("/") < 0) + { + syslog(LOG_ERR, "Error switching to root directory"); + exit(EXIT_FAILURE); + } + for (int x = sysconf(_SC_OPEN_MAX); x >= 0; --x) + { + close(x); + } + + int devNull = open("/dev/null", O_RDWR); + dup2(devNull, STDIN_FILENO); + dup2(devNull, STDOUT_FILENO); + dup2(devNull, STDERR_FILENO); + syslog(LOG_INFO, "Daemonization process completed"); +} + +void DeepCleanerDaemon::Initialize(const std::string& configLocalPath) +{ + configAbsPath = std::filesystem::absolute(configLocalPath); + + openlog("DeepCleanerDaemon", LOG_NDELAY | LOG_PID | LOG_PERROR, LOG_USER); + syslog(LOG_INFO, "Initializing daemon"); + + KillExistingDaemon(); + Daemonize(); + + syslog(LOG_INFO, "Setting up signal handlers"); + std::signal(SIGHUP, handleSignal); + std::signal(SIGTERM, handleSignal); + + WritePid(); + + ReloadConfig(); + syslog(LOG_INFO, "Daemon initialization finished"); +} + +void DeepCleanerDaemon::Run() +{ + while (!isTerminateReceived) + { + if (!std::filesystem::is_directory(targetDirPath)) + { + syslog(LOG_WARNING, "Warning: Target directory path from config does not exist, continuing to wait"); + } + else + { + deleteDirectoriesByDepth(targetDirPath, depth); + } + std::this_thread::sleep_for(sleepDuration); + } +} + +void DeepCleanerDaemon::Terminate() +{ + isTerminateReceived = true; + closelog(); + syslog(LOG_INFO, "Daemon termination initiated"); +} + +void DeepCleanerDaemon::ReloadConfig() +{ + std::ifstream configFile(configAbsPath); + if (!configFile.is_open()) + { + syslog(LOG_ERR, "Error: Invalid config file"); + exit(EXIT_FAILURE); + } + if (!std::getline(configFile, targetDirPath)) + { + syslog(LOG_ERR, "Error: Cannot read target directory path from config file"); + exit(EXIT_FAILURE); + } + syslog(LOG_INFO, "Target directory path set to \"%s\"", targetDirPath.c_str()); + + int sleepTimeSeconds = -1; + if (!(configFile >> sleepTimeSeconds)) + { + syslog(LOG_WARNING, "Warning: Cannot read sleep time from config file, using default value"); + sleepDuration = defaultSleepDuration; + } + else if (sleepTimeSeconds <= 0) + { + syslog(LOG_WARNING, "Warning: Sleep time from config file is not positive, using default value"); + sleepDuration = defaultSleepDuration; + } + else + { + sleepDuration = std::chrono::seconds(sleepTimeSeconds); + } + syslog(LOG_INFO, "Sleep time set to %li seconds", static_cast(sleepDuration.count())); +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/daemon.hpp b/Khamidullin.Ilsaf/lab1/daemon.hpp new file mode 100644 index 0000000..259fbc5 --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/daemon.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class DeepCleanerDaemon +{ +private: + const std::string pidPath = "/var/run/deep_deletion_daemon.pid"; + const int depth = 3; + const std::chrono::seconds defaultSleepDuration = std::chrono::seconds(10); + + std::string configAbsPath; + std::string targetDirPath; + std::chrono::seconds sleepDuration; + bool isTerminateReceived = false; + + DeepCleanerDaemon() {}; + DeepCleanerDaemon(const DeepCleanerDaemon &) = delete; + DeepCleanerDaemon &operator=(const DeepCleanerDaemon &) = delete; + + void WritePid(); + void Daemonize(); + void KillExistingDaemon(); + +public: + static DeepCleanerDaemon &Get() + { + static DeepCleanerDaemon instance; + return instance; + } + + void Initialize(const std::string& configLocalPath); + + void Terminate(); + + void ReloadConfig(); + + void Run(); +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab1/main.cpp b/Khamidullin.Ilsaf/lab1/main.cpp new file mode 100644 index 0000000..c37f41f --- /dev/null +++ b/Khamidullin.Ilsaf/lab1/main.cpp @@ -0,0 +1,16 @@ +#include "daemon.hpp" +#include + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return EXIT_FAILURE; + } + + DeepCleanerDaemon::Get().Initialize(argv[1]); + DeepCleanerDaemon::Get().Run(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/CMakeLists.txt b/Khamidullin.Ilsaf/lab2/CMakeLists.txt new file mode 100644 index 0000000..d900bfb --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.14) + +project(WolfAndGoats VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_compile_options(-Wall -Werror -DCMAKE_EXE_LINKER_FLAGS=-latomic) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick REQUIRED) +find_package (Threads) +find_library (LIBRT rt) + +set(CLIENT goat) +set(HOST wolf) +set(CONNECTION connections) +set(UTILS utils) + +foreach(CONN mq fifo sock) + add_executable(host_${CONN} ${HOST}/wolf.cpp ${HOST}/wolf.h ${HOST}/host.cpp + ${CLIENT}/goat.cpp ${CLIENT}/goat.h + ${UTILS}/configuration.h ${UTILS}/message.h + ${CONNECTION}/conn.h ${CONNECTION}/conn_${CONN}.cpp ${CONNECTION}/conn_${CONN}.h + gui/gui.cpp gui/gui.h + gui/qml.qrc) + target_link_libraries(host_${CONN} PUBLIC rt pthread Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick) +endforeach(CONN mq fifo sock) diff --git a/Khamidullin.Ilsaf/lab2/build.sh b/Khamidullin.Ilsaf/lab2/build.sh new file mode 100644 index 0000000..8f2bc57 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/build.sh @@ -0,0 +1,12 @@ +mkdir build +cd build + +apt install qml-module-qtquick-window2 +apt install qml-module-qtquick-layouts +apt install qml-module-qtquick-controls2 + +cmake .. +make -j2 +mv host_sock host_mq host_fifo .. +cd .. +rm -r build diff --git a/Khamidullin.Ilsaf/lab2/connections/conn.h b/Khamidullin.Ilsaf/lab2/connections/conn.h new file mode 100644 index 0000000..1fdf8b6 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn.h @@ -0,0 +1,18 @@ +#pragma once + +#include "sys/types.h" +#include "utils/message.h" +#include + +class Connection +{ +public: + virtual ~Connection() = default; + + static std::unique_ptr create(); + + virtual bool open(pid_t pid, bool isHost) = 0; + virtual bool read(Message &msg) const = 0; + virtual bool write(const Message &msg) = 0; + virtual bool close() = 0; +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_fifo.cpp b/Khamidullin.Ilsaf/lab2/connections/conn_fifo.cpp new file mode 100644 index 0000000..bac5a3a --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_fifo.cpp @@ -0,0 +1,73 @@ +#include "conn_fifo.h" + +#include +#include +#include +#include +#include +#include + +std::unique_ptr Connection::create() +{ + return std::make_unique(); +} + +bool Fifo::open(pid_t pid, bool isHost) +{ + name = std::string(FIFO_ROUTE + std::to_string(pid)); + this->isHost = isHost; + if (isHost) + { + if (mkfifo(name.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) < 0) + { + syslog(LOG_ERR, "ERROR: failed to create"); + return false; + } + } + descriptor = ::open(name.c_str(), O_RDWR); + if (descriptor < 0) + { + syslog(LOG_ERR, "ERROR: failed to open"); + return false; + } + + return true; +} + +bool Fifo::read(Message &msg) const +{ + if (::read(descriptor, &msg, sizeof(Message)) < 0) + { + syslog(LOG_ERR, "ERROR: failed to read"); + return false; + } + return true; +} + +bool Fifo::write(const Message &msg) +{ + if (::write(descriptor, &msg, sizeof(Message)) < 0) + { + syslog(LOG_ERR, "ERROR: failed to write"); + return false; + } + return true; +} + +bool Fifo::close() +{ + if (::close(descriptor) < 0) + { + syslog(LOG_ERR, "ERROR: failed to close"); + return false; + } + if (isHost) + { + if (unlink(name.c_str()) < 0) + { + syslog(LOG_ERR, "ERROR: failed to unlink"); + return false; + } + } + return true; +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_fifo.h b/Khamidullin.Ilsaf/lab2/connections/conn_fifo.h new file mode 100644 index 0000000..facebf2 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_fifo.h @@ -0,0 +1,24 @@ +#pragma once + +#include "conn.h" + +#include + +class Fifo : public Connection +{ +private: + const std::string FIFO_ROUTE = "/tmp/fifo_conn_"; + + bool isHost = false; + std::string name; + int descriptor = -1; + +public: + Fifo() = default; + ~Fifo() = default; + + bool open(pid_t pid, bool isHost) final; + bool read(Message &msg) const final; + bool write(const Message &msg) final; + bool close() final; +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_mq.cpp b/Khamidullin.Ilsaf/lab2/connections/conn_mq.cpp new file mode 100644 index 0000000..38cdbb7 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_mq.cpp @@ -0,0 +1,68 @@ +#include "conn_mq.h" + +#include +#include + +#include + +std::unique_ptr Connection::create() +{ + return std::make_unique(); +} + +bool MessageQueue::open(pid_t pid, bool isHost) +{ + this->isHost = isHost; + std::string name = std::string(MQ_ROUTE + std::to_string(pid)); + if (isHost) + { + struct mq_attr attr; + attr.mq_maxmsg = MAX_QUEUE_SIZE; + attr.mq_msgsize = sizeof(Message); + attr.mq_curmsgs = 0; + attr.mq_flags = 0; + descriptor = mq_open(name.c_str(), O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO, &attr); + } + else + { + descriptor = mq_open(name.c_str(), O_RDWR); + } + + if (descriptor < 0) + { + syslog(LOG_ERR, "ERROR: failed to create"); + return false; + } + + return true; +} + +bool MessageQueue::read(Message &msg) const +{ + if (mq_receive(descriptor, (char *)(&msg), sizeof(Message), nullptr) < 0) + { + syslog(LOG_ERR, "ERROR: failed to read"); + return false; + } + return true; +} + +bool MessageQueue::write(const Message &msg) +{ + if (mq_send(descriptor, (char *)(&msg), sizeof(Message), 0) < 0) + { + syslog(LOG_ERR, "ERROR: failed to write"); + return false; + } + return true; +} + +bool MessageQueue::close() +{ + if (isHost && mq_close(descriptor) < 0) + { + syslog(LOG_ERR, "ERROR: failed to close connection"); + return false; + } + return true; +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_mq.h b/Khamidullin.Ilsaf/lab2/connections/conn_mq.h new file mode 100644 index 0000000..ca5ec5e --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_mq.h @@ -0,0 +1,25 @@ +#pragma once + +#include "conn.h" + +#include +#include + +class MessageQueue : public Connection +{ +private: + const std::string MQ_ROUTE = "/mq_conn_"; + const size_t MAX_QUEUE_SIZE = 1; + + bool isHost = false; + mqd_t descriptor; + +public: + MessageQueue() = default; + ~MessageQueue() = default; + + bool open(pid_t pid, bool isHost) final; + bool read(Message &msg) const final; + bool write(const Message &msg) final; + bool close() final; +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_sock.cpp b/Khamidullin.Ilsaf/lab2/connections/conn_sock.cpp new file mode 100644 index 0000000..9f399fb --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_sock.cpp @@ -0,0 +1,122 @@ +#include "conn_sock.h" +#include "utils/configuration.h" + +#include +#include +#include +#include +#include + +std::unique_ptr Connection::create() +{ + return std::make_unique(); +} + +bool Socket::open(pid_t pid, bool isHost) +{ + struct sockaddr_un addr; + this->isHost = isHost; + name = SOCKET_ROUTE + std::to_string(pid); + addr.sun_family = AF_UNIX; + + std::strncpy(addr.sun_path, name.c_str(), sizeof(addr.sun_path) - 1); + + if (isHost) + { + if ((hostSocket = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) + { + syslog(LOG_ERR, "ERROR: can't create host socket"); + return false; + } + + if (bind(hostSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + syslog(LOG_ERR, "ERROR: can't bind host socket"); + return false; + } + + if ((listen(hostSocket, MAX_CLIENT_NUM)) < 0) + { + syslog(LOG_ERR, "ERROR: can't listen connection"); + return false; + } + + if ((clientSocket = accept(hostSocket, NULL, NULL)) < 0) + { + syslog(LOG_ERR, "ERROR: can't accept client socket"); + return false; + } + } + else + { + if ((clientSocket = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) + { + syslog(LOG_ERR, "ERROR: can't create client socket"); + return false; + } + if (connect(clientSocket, (const struct sockaddr *)&addr, sizeof(sockaddr_un)) < 0) + { + std::cout << strerror(errno) << std::endl; + syslog(LOG_ERR, "ERROR: can't connect sockets"); + return false; + } + } + + return true; +} + +bool Socket::read(Message &msg) const +{ + if (recv(clientSocket, &msg, sizeof(Message), 0) < 0) + { + syslog(LOG_ERR, "ERROR: failed to read from socket"); + return false; + } + + return true; +} + +bool Socket::write(const Message &msg) +{ + if (send(clientSocket, &msg, sizeof(Message), MSG_NOSIGNAL) < 0) + { + syslog(LOG_ERR, "ERROR: failed to write to socket"); + return false; + } + + return true; +} + +bool Socket::close() +{ + if (isHost) + { + if (::close(clientSocket) < 0) + { + syslog(LOG_ERR, "ERROR: failed to close client socket"); + return false; + } + + if (::close(hostSocket) < 0) + { + syslog(LOG_ERR, "ERROR: failed to close host socket"); + return false; + } + + if (unlink(name.c_str()) < 0) + { + syslog(LOG_ERR, "ERROR: failed to unlink socket"); + return false; + } + } + else + { + if (::close(clientSocket) < 0) + { + syslog(LOG_ERR, "ERROR: failed to close client socket"); + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/connections/conn_sock.h b/Khamidullin.Ilsaf/lab2/connections/conn_sock.h new file mode 100644 index 0000000..1479cb4 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/connections/conn_sock.h @@ -0,0 +1,28 @@ +#pragma once + +#include "conn.h" + +#include +#include + +class Socket : public Connection +{ +private: + const std::string SOCKET_ROUTE = "/tmp/socket_conn_"; + const size_t MAX_CLIENT_NUM = 1; + + socklen_t hostSocket; + socklen_t clientSocket; + + bool isHost = false; + std::string name; + +public: + Socket() = default; + ~Socket() = default; + + bool open(pid_t pid, bool isHost) final; + bool read(Message &msg) const final; + bool write(const Message &msg) final; + bool close() final; +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/goat/goat.cpp b/Khamidullin.Ilsaf/lab2/goat/goat.cpp new file mode 100644 index 0000000..53b1a70 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/goat/goat.cpp @@ -0,0 +1,182 @@ +#include "goat.h" +#include "utils/configuration.h" + +#include +#include +#include +#include +#include +#include + +Goat &Goat::getInstance() +{ + static Goat instance; + return instance; +} + +Goat::Goat() +{ + conn = Connection::create(); + + signal(SIGTERM, signalHandler); + signal(SIGUSR1, signalHandler); + signal(SIGINT, signalHandler); +} + +Goat::~Goat() +{ + if (clientSemaphore != SEM_FAILED) + { + sem_close(clientSemaphore); + } + if (hostSemaphore != SEM_FAILED) + { + sem_close(hostSemaphore); + } + if (!conn->close()) + { + syslog(LOG_ERR, "ERROR: failed close connection"); + } + kill(hostPid, SIGTERM); +} + +bool Goat::init(const pid_t &hostPid) +{ + syslog(LOG_INFO, "Client initialization"); + + this->hostPid = hostPid; + + if (!conn->open(hostPid, false)) + { + syslog(LOG_ERR, "ERROR: failed to open client connection"); + return false; + } + + if ((hostSemaphore = sem_open(Configuration::HOST_SEMAPHORE_NAME.c_str(), 0)) == SEM_FAILED) + { + syslog(LOG_ERR, "ERROR: failed to connect to host semaphore"); + return false; + } + + if ((clientSemaphore = sem_open(Configuration::CLIENT_SEMAPHORE_NAME.c_str(), 0)) == SEM_FAILED) + { + syslog(LOG_ERR, "ERROR: failed to connect to client semaphore"); + return false; + } + + isRun = true; + + syslog(LOG_INFO, "Client initialized"); + + return true; +} + +void Goat::run() +{ + syslog(LOG_INFO, "Client start running"); + + unsigned short goatNumber = getGoatNumber(GOAT_STATE::ALIVE); + if (!sendGoatMessage({goatNumber, GOAT_STATE::ALIVE})) + { + syslog(LOG_ERR, "ERROR: failed to send goat message"); + return; + } + + while (isRun) + { + if (!stopClient()) + { + syslog(LOG_ERR, "ERROR: failed to stop client"); + return; + } + Message wolfMessage; + if (!getWolfMessage(wolfMessage)) + { + syslog(LOG_ERR, "ERROR: failed to read wolf message"); + return; + } + goatNumber = getGoatNumber(wolfMessage.goatState); + if (!sendGoatMessage({goatNumber, wolfMessage.goatState})) + { + syslog(LOG_ERR, "ERROR: failed to send goat message"); + return; + } + isRun = (wolfMessage.goatState != GOAT_STATE::DEAD); + } + + syslog(LOG_INFO, "Client run end"); +} + +bool Goat::getWolfMessage(Message &msg) +{ + return conn->read(msg); +} + +size_t Goat::getGoatNumber(GOAT_STATE state) +{ + using namespace Configuration::Goat; + + std::random_device seeder; + std::mt19937 rng(seeder()); + std::uniform_int_distribution genAliveNum(MIN_NUMBER, MAX_ALIVE_NUMBER); + std::uniform_int_distribution genAlmostDeadNum(MIN_NUMBER, MAX_ALMOST_DEATH_NUMBER); + + size_t res = 0; + + switch (state) + { + case GOAT_STATE::ALIVE: + res = genAliveNum(rng); + break; + case GOAT_STATE::ALMOST_DEAD: + res = genAlmostDeadNum(rng); + break; + case GOAT_STATE::DEAD: + break; + } + + return res; +} + +bool Goat::sendGoatMessage(const Message &msg) +{ + return conn->write(msg); +} + +bool Goat::stopClient() +{ + if (sem_post(hostSemaphore) == -1) + { + syslog(LOG_ERR, "ERROR: host semaphore can't continue"); + return false; + } + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += Configuration::TIME_OUT; + if (sem_timedwait(clientSemaphore, &ts) == -1) + { + std::cout << strerror(errno) << std::endl; + syslog(LOG_ERR, "ERROR: client semaphore can't wait"); + return false; + } + return true; +} + +void Goat::signalHandler(int signal) +{ + switch (signal) + { + case SIGTERM: + Goat::getInstance().isRun = false; + exit(EXIT_SUCCESS); + case SIGINT: + syslog(LOG_INFO, "INFO: receive delete client request"); + exit(EXIT_SUCCESS); + case SIGUSR1: + syslog(LOG_INFO, "INFO: client end game"); + kill(Goat::getInstance().hostPid, SIGTERM); + exit(EXIT_SUCCESS); + default: + syslog(LOG_INFO, "INFO: unknow command"); + } +} diff --git a/Khamidullin.Ilsaf/lab2/goat/goat.h b/Khamidullin.Ilsaf/lab2/goat/goat.h new file mode 100644 index 0000000..5777343 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/goat/goat.h @@ -0,0 +1,32 @@ +#pragma once + +#include "connections/conn.h" +#include + +class Goat +{ +private: + bool isRun = false; + pid_t hostPid = 0; + sem_t *hostSemaphore; + sem_t *clientSemaphore; + std::unique_ptr conn; + +public: + static Goat &getInstance(); + ~Goat(); + + bool init(const pid_t &hostPid); + void run(); + +private: + Goat(); + Goat(Goat &w) = delete; + Goat &operator=(const Goat &w) = delete; + + static void signalHandler(int signal); + bool getWolfMessage(Message &msg); + size_t getGoatNumber(GOAT_STATE goatState); + bool sendGoatMessage(const Message &msg); + bool stopClient(); +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/gui/gui.cpp b/Khamidullin.Ilsaf/lab2/gui/gui.cpp new file mode 100644 index 0000000..81b01ea --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/gui/gui.cpp @@ -0,0 +1,119 @@ +#include "gui.h" +#include "utils/configuration.h" + +#include +#include +#include +#include +#include + +Gui &Gui::getInstance() +{ + static Gui instance; + return instance; +} + +void Gui::init(isRunningCallback isRunning, stopRunningCallback stopRunning, sendMessageCallback sendMessage) +{ + this->isRunning = isRunning; + this->stopRunning = stopRunning; + this->sendMessage = sendMessage; + + QObject::connect(&gameHistory, &GameHistory::addMessage, + &gameHistory, &GameHistory::onMessageAdded); +} + +int Gui::run() +{ + char *argv[] = {(char *)"gui"}; + int argc = 1; + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) + { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + + engine.rootContext()->setContextProperty(QStringLiteral("MAX_WOLF_NUMBER"), QVariant::fromValue(Configuration::Wolf::MAX_NUMBER)); + engine.rootContext()->setContextProperty("gui", this); + engine.rootContext()->setContextProperty("gameHistory", &gameHistory); + + engine.load(url); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [=]() + { sendNewWolfMessage(generateWolfNumber()); }); + timer->start(Configuration::ROUND_TIME_IN_MS); + + int val = app.exec(); + + stopRunning(); + + return val; +} + +void Gui::getMessageFromHost(const std::string& msg) +{ + + emit Gui::getInstance().gameHistory.addMessage(QString::fromStdString(msg)); +} + +void Gui::sendNewWolfMessage(const unsigned int &newNum) +{ + timer->stop(); + + if (isRunning()) + { + sendMessage(newNum); + + timer->start(Configuration::ROUND_TIME_IN_MS); + } + else + { + gameHistory.addMessage("Game Over!"); + } +} + +unsigned int Gui::generateWolfNumber() +{ + std::random_device seeder; + + std::mt19937 rng(seeder()); + std::uniform_int_distribution gen(Configuration::Wolf::MIN_NUMBER, Configuration::Wolf::MAX_NUMBER); + + return gen(rng); +} + +int GameHistory::rowCount(const QModelIndex &p) const +{ + return messages.size(); +} + +QVariant GameHistory::data(const QModelIndex &index, int role) const +{ + if (role == MESSAGE_ROLE) + { + return messages[index.row()]; + } + + return QVariant(); +} + +QHash GameHistory::roleNames() const +{ + QHash roles; + roles[MESSAGE_ROLE] = "message"; + return roles; +} + +void GameHistory::onMessageAdded(const QString&msg) +{ + beginResetModel(); + messages.push_back(msg); + endResetModel(); +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/gui/gui.h b/Khamidullin.Ilsaf/lab2/gui/gui.h new file mode 100644 index 0000000..adc6d88 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/gui/gui.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "utils/message.h" + +class GameHistory : public QAbstractListModel +{ + Q_OBJECT +private: + const int MESSAGE_ROLE = Qt::UserRole + 1; + + std::vector messages; + +public: + GameHistory() = default; + ~GameHistory() = default; + + int rowCount(const QModelIndex &p) const final; + QVariant data(const QModelIndex &index, int role) const final; + QHash roleNames() const final; + +public slots: + void onMessageAdded(const QString& msg); + +signals: + void addMessage(const QString& msg); +}; + +class Gui : public QObject +{ + Q_OBJECT +private: + using isRunningCallback = std::function; + using stopRunningCallback = std::function; + using sendMessageCallback = std::function; + + isRunningCallback isRunning; + stopRunningCallback stopRunning; + sendMessageCallback sendMessage; + + QTimer *timer; + + GameHistory gameHistory; + +public: + static Gui &getInstance(); + + void init(isRunningCallback isRunning, stopRunningCallback stopRunning, sendMessageCallback sendMessage); + + int run(); + + static void getMessageFromHost(const std::string& msg); + +private: + Gui() = default; + Gui(Gui &w) = delete; + Gui &operator=(const Gui &w) = delete; + + unsigned int generateWolfNumber(); +public slots: + void sendNewWolfMessage(const unsigned int &newNum); + +signals: +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/gui/main.qml b/Khamidullin.Ilsaf/lab2/gui/main.qml new file mode 100644 index 0000000..f8d1f45 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/gui/main.qml @@ -0,0 +1,73 @@ +import QtQuick 2.4 +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 + +Window { + id: mainWindow + + title: qsTr("Создание моделей") + + minimumWidth: 500 + minimumHeight: 700 + + visible: true + + ColumnLayout { + anchors.fill: parent + RowLayout { + Text { + text: "Введите число:" + } + TextField { + id: thrownNum + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[0-9]+/ } + onTextChanged: { + if (parseInt(text) > MAX_WOLF_NUMBER) { + text = MAX_WOLF_NUMBER.toString(); + } + } + + } + Button { + text: "Отправить" + + onClicked: { + let num = 0; + if (thrownNum.text !== "") + num = parseInt(thrownNum.text); + gui.sendNewWolfMessage(num); + thrownNum.text = ""; + } + } + } + + Text { + text: "Сообщения:" + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: "transparent" + + ListView { + anchors.fill: parent + model: typeof (gameHistory) !== "undefined" ? gameHistory : 0 + + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: ScrollBar {} + + clip:true + + delegate: Component { + Text { + text: message + } + } + } + } + } +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/gui/qml.qrc b/Khamidullin.Ilsaf/lab2/gui/qml.qrc new file mode 100644 index 0000000..dc7d52f --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/gui/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/utils/configuration.h b/Khamidullin.Ilsaf/lab2/utils/configuration.h new file mode 100644 index 0000000..66c7230 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/utils/configuration.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace Configuration +{ + static const std::string HOST_SEMAPHORE_NAME = "wolf_semaphore"; + static const std::string CLIENT_SEMAPHORE_NAME = "goat_semaphore"; + + static const size_t TIME_OUT = 5; + static const size_t ROUND_TIME_IN_MS = 3000; + + namespace Wolf + { + static const size_t MIN_NUMBER = 1; + static const size_t MAX_NUMBER = 100; + }; + + namespace Goat + { + static const size_t MIN_NUMBER = 1; + static const size_t MAX_ALIVE_NUMBER = 100; + static const size_t MAX_ALMOST_DEATH_NUMBER = 50; + static const size_t ALMOST_DEAD_GOAT_VALIDATION = 20; + static const size_t ALIVE_GOAT_VALIDATION = 70; + }; +}; \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/utils/message.h b/Khamidullin.Ilsaf/lab2/utils/message.h new file mode 100644 index 0000000..6530357 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/utils/message.h @@ -0,0 +1,14 @@ +#pragma once + +enum GOAT_STATE +{ + ALIVE = 0, + DEAD, + ALMOST_DEAD, +}; + +struct Message +{ + unsigned short thrownNumber; + GOAT_STATE goatState; +}; diff --git a/Khamidullin.Ilsaf/lab2/wolf/host.cpp b/Khamidullin.Ilsaf/lab2/wolf/host.cpp new file mode 100644 index 0000000..e94e6f8 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/wolf/host.cpp @@ -0,0 +1,25 @@ +#include "wolf.h" +#include "gui/gui.h" + +#include +#include + +int main() +{ + if (Wolf::getInstance().init(Gui::getMessageFromHost)) + { + Gui::getInstance().init(Wolf::isRunning, Wolf::stopRunning, Wolf::getNewWolfMessage); + std::thread wolfThread(&Wolf::run, &Wolf::getInstance()); + Gui::getInstance().run(); + wolfThread.join(); + } + else + { + std::cout << "ERROR: can't initialize wolf" << std::endl; + syslog(LOG_ERR, "ERROR: failed to initialize host"); + } + + syslog(LOG_INFO, "INFO: game over"); + + return 0; +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/wolf/wolf.cpp b/Khamidullin.Ilsaf/lab2/wolf/wolf.cpp new file mode 100644 index 0000000..bb5348a --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/wolf/wolf.cpp @@ -0,0 +1,225 @@ +#include "wolf.h" +#include "utils/configuration.h" +#include "goat/goat.h" + +#include +#include +#include +#include + +Wolf &Wolf::getInstance() +{ + static Wolf instance; + return instance; +} + +Wolf::Wolf() +{ + conn = Connection::create(); + hostPid = getpid(); + openlog("WolfAndGoat", LOG_NDELAY | LOG_PID, LOG_USER); + struct sigaction sig; + memset(&sig, 0, sizeof(sig)); + sig.sa_flags = SA_SIGINFO; + sig.sa_sigaction = Wolf::signalHandle; + sigaction(SIGTERM, &sig, nullptr); + sigaction(SIGINT, &sig, nullptr); +} + +Wolf::~Wolf() +{ + kill(clientPid, SIGTERM); + if (!conn->close()) + { + syslog(LOG_ERR, "ERROR: failed to close connection"); + } + + if (semHost != SEM_FAILED) + { + sem_unlink(Configuration::HOST_SEMAPHORE_NAME.c_str()); + } + + if (semClient != SEM_FAILED) + { + sem_unlink(Configuration::CLIENT_SEMAPHORE_NAME.c_str()); + } +} + +bool Wolf::init(sendMessageToGuiCallback sendToGui) +{ + syslog(LOG_INFO, "Host initialization"); + + sendMessageToGui = sendToGui; + + semHost = sem_open(Configuration::HOST_SEMAPHORE_NAME.c_str(), O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0); + if (semHost == SEM_FAILED) + { + syslog(LOG_ERR, "ERROR: host semaphore not created"); + return false; + } + + semClient = sem_open(Configuration::CLIENT_SEMAPHORE_NAME.c_str(), O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0); + if (semClient == SEM_FAILED) + { + syslog(LOG_ERR, "ERROR: client semaphore not created"); + sem_unlink(Configuration::HOST_SEMAPHORE_NAME.c_str()); + return false; + } + + pid_t childPid = fork(); + if (childPid == 0) + { + clientPid = getpid(); + + if (Goat::getInstance().init(hostPid)) + { + Goat::getInstance().run(); + } + else + { + syslog(LOG_ERR, "ERROR: client initialization error"); + return false; + } + exit(EXIT_SUCCESS); + } + + if (!conn->open(hostPid, true)) + { + syslog(LOG_ERR, "ERROR: failed to open connection"); + return false; + } + + Wolf::getInstance().isRun = true; + syslog(LOG_INFO, "INFO: host initialize successfully"); + + return true; +} + +void Wolf::run() +{ + size_t round = 0; + + while (isRun) + { + if (isNewMessage) + { + if (!stop()) + { + syslog(LOG_ERR, "ERROR: failed to stop wolf"); + return; + } + + Message goatMsg; + if (!getGoatMessage(goatMsg)) + { + syslog(LOG_ERR, "ERROR: failed to get message from goat"); + return; + } + + goatMsg.goatState = updateGoatState(wolfMessage.load().thrownNumber, goatMsg); + if (!sendWolfMessage({wolfMessage.load().thrownNumber, goatMsg.goatState})) + { + syslog(LOG_ERR, "ERROR: send message to wolf failed"); + return; + } + isRun = checkRun(goatMsg.goatState); + if (!isRun) + { + kill(clientPid, SIGTERM); + clientPid = 0; + } + isNewMessage = false; + + sendMessageToGui("Round " + std::to_string(round)); + sendMessageToGui("Wolf number: " + std::to_string(wolfMessage.load().thrownNumber)); + sendMessageToGui("Goat number: " + std::to_string(goatMsg.thrownNumber)); + + round++; + } + } + + kill(clientPid, SIGTERM); +} + +bool Wolf::isRunning() +{ + return Wolf::getInstance().isRun; +} + +void Wolf::stopRunning() +{ + Wolf::getInstance().isRun = false; +} + +void Wolf::getNewWolfMessage(unsigned short thrownNum) +{ + Wolf::getInstance().isNewMessage = true; + Wolf::getInstance().wolfMessage.store({thrownNum, GOAT_STATE::ALIVE}); +} + +bool Wolf::stop() +{ + if (sem_post(semClient) == -1) + { + syslog(LOG_ERR, "ERROR: failed to continue client"); + return false; + } + if (sem_wait(semHost) == -1) + { + syslog(LOG_ERR, "ERROR: failed to stop host"); + return false; + } + return true; +} + +bool Wolf::getGoatMessage(Message &msg) +{ + return conn->read(msg); +} + +GOAT_STATE Wolf::updateGoatState(const size_t &wolfNum, const Message &goatMessage) +{ + size_t div = std::abs(static_cast(wolfNum) - static_cast(goatMessage.thrownNumber)); + switch (goatMessage.goatState) + { + case GOAT_STATE::ALIVE: + return div > Configuration::Goat::ALIVE_GOAT_VALIDATION ? GOAT_STATE::ALMOST_DEAD : GOAT_STATE::ALIVE; + case GOAT_STATE::ALMOST_DEAD: + return div > Configuration::Goat::ALMOST_DEAD_GOAT_VALIDATION ? GOAT_STATE::DEAD : GOAT_STATE::ALIVE; + case GOAT_STATE::DEAD: + return GOAT_STATE::DEAD; + } + + return GOAT_STATE::DEAD; +} + +bool Wolf::sendWolfMessage(const Message &msg) +{ + return conn->write(msg); +} + +bool Wolf::checkRun(GOAT_STATE goatState) +{ + if (goatState == GOAT_STATE::ALIVE || goatState == GOAT_STATE::ALMOST_DEAD) + { + return true; + } + syslog(LOG_INFO, "INFO: goat dead. Game over"); + return false; +} + +void Wolf::signalHandle(int sig, siginfo_t *sigInfo, void *ptr) +{ + switch (sig) + { + case SIGTERM: + Wolf::getInstance().isRun = false; + return; + case SIGINT: + syslog(LOG_INFO, "INFO: wolf terminate"); + exit(EXIT_SUCCESS); + return; + default: + syslog(LOG_INFO, "INFO: unknown command"); + } +} \ No newline at end of file diff --git a/Khamidullin.Ilsaf/lab2/wolf/wolf.h b/Khamidullin.Ilsaf/lab2/wolf/wolf.h new file mode 100644 index 0000000..2998530 --- /dev/null +++ b/Khamidullin.Ilsaf/lab2/wolf/wolf.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "connections/conn.h" +#include +#include +#include + +class Wolf +{ +private: + using sendMessageToGuiCallback = std::function; + + bool isRun = false; + sem_t *semHost{}; + sem_t *semClient{}; + pid_t hostPid = 0; + pid_t clientPid = 0; + std::unique_ptr conn; + + std::atomic wolfMessage; + + bool isNewMessage = false; + +public: + static Wolf &getInstance(); + ~Wolf(); + + bool init(sendMessageToGuiCallback sendToGui); + void run(); + + static bool isRunning(); + static void stopRunning(); + static void getNewWolfMessage(unsigned short thrownNum); + + sendMessageToGuiCallback sendMessageToGui; + +private: + Wolf(); + Wolf(Wolf &w) = delete; + Wolf &operator=(const Wolf &w) = delete; + + bool stop(); + bool getGoatMessage(Message &msg); + bool sendWolfMessage(const Message &msg); + GOAT_STATE updateGoatState(const size_t &wolfNum, const Message& goatMessage); + bool checkRun(GOAT_STATE goatState); + static void signalHandle(int sig, siginfo_t *sigInfo, void *ptr); +}; \ No newline at end of file