diff --git a/src/TkUtil/ApplicationStats.cpp b/src/TkUtil/ApplicationStats.cpp index 13098d0..c848497 100644 --- a/src/TkUtil/ApplicationStats.cpp +++ b/src/TkUtil/ApplicationStats.cpp @@ -8,10 +8,12 @@ #include #include +#include #include #include #include +#include #include #include @@ -20,12 +22,15 @@ #include // strerror #include // fchmod #include +#include // MAXPATHLEN #include // fork, setsid USING_STD BEGIN_NAMESPACE_UTIL +static const Charset charset ("àéèùô"); + ApplicationStats::ApplicationStats ( ) { @@ -63,219 +68,285 @@ string ApplicationStats::getFileName (const string& appName, const string& logDi void ApplicationStats::logUsage (const string& appName, const string& logDir) { - // En vue de ne pas altérer le comportement de l'application tout est effectuée dans un processus fils => exit (0) en toutes circonstances. - errno = 0; - const pid_t pid = fork ( ); - if ((pid_t)-1 == pid) - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : échec de fork : " << strerror (errno) << co_endl; - return; - } // if ((pid_t)-1 == pid) - if (0 != pid) - { - Process::killAtEnd (pid); - return; // Parent - } + FILE* file = 0; + string fileName; - // On détache complètement le fils du parent => peut importe qui fini en premier, l'autre ira jusqu'au bout : - const pid_t sid = setsid ( ); - if ((pid_t)-1 == sid) + try { + if ((true == appName.empty ( )) || (true == logDir.empty ( ))) { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : échec de setsid : " << strerror (errno) << co_endl; - } - exit (0); - } // if ((pid_t)-1 == sid) - - if ((true == appName.empty ( )) || (true == logDir.empty ( ))) - { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : nom d'application ou répertoire des logs non renseigné (" << appName << "/" << logDir << ")." << co_endl; - } - exit (0); - } // if ((true == appName.empty ( )) || (true == logDir.empty ( ))) - - // Le nom du fichier : - const Date date; - const string user (UserData (true).getName ( )); - UTF8String fileName (getFileName (appName, logDir, (unsigned long)date.getMonth ( ), date.getYear ( )), Charset::UTF_8); + { + + ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : nom d'application ou répertoire des logs non renseigné (" << appName << "/" << logDir << ")." << co_endl; + } - // On ouvre le fichier en lecture/écriture : - FILE* file = fopen (fileName.utf8 ( ).c_str ( ), "r+"); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire : - const bool created = NULL == file ? true : false; - file = NULL == file ? fopen (fileName.utf8 ( ).c_str ( ), "a+") : file; - if (NULL == file) - { - try - { // On peut avoir des exceptions de levées : chemin non traversable, ... - File dir (logDir); - if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || (false == dir.isExecutable ( )) || (false == dir.isWritable ( ))) + return; // Tolérance aux erreurs + } // if ((true == appName.empty ( )) || (true == logDir.empty ( ))) + + // Le nom du fichier : + const Date date; + const string user (UserData (true).getName ( )); + fileName = getFileName (appName, logDir, (unsigned long)date.getMonth ( ), date.getYear ( )); + + file = initLogSession (fileName); + if (0 == file) + return; // Process père + + { // FileLock scope + FileLock logLock (fileName, file); + + // Lecture et actualisation des logs existants : + map logs; + char name [256]; + size_t count = 0, line = 1; + bool found = false; + int flag = 0; + errno = 0; + while (2 == (flag = fscanf (file, "%s\t%lu", name, &count))) { + line++; + if (name == user) { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur, " << logDir << " n'est pas un répertoire existant avec les droits en écriture pour vous." << co_endl; - } - exit (0); - } // if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || ... - File logFile (fileName.utf8 ( )); - if (false == logFile.isWritable ( )) + found = true; + count++; + } // if (name == user) + logs.insert (pair (name, count)); + count = 0; + } // while (2 == fscanf (file, "%s\t%lu", name, &count)) + if (0 != errno) + { + UTF8String error (charset); + error << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != errno) + else if ((flag < 2) && (EOF != flag)) { + UTF8String error (charset); + error << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : fichier probablement corrompu."; + throw Exception (error); + } // if (flag < 2) + if (false == found) + logs.insert (pair (user, 1)); + + // Réécriture des logs actualisés : + errno = 0; + if (0 != fseek (file, 0, SEEK_SET)) + { + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != fseek (file, 0, SEEK_SET)) + + for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + { + if (fprintf (file, "%s\t%lu\n", (*itl).first.c_str ( ), (*itl).second) < 0) { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : absence de droits en écriture." << co_endl; - } - exit (0); - } // if (false == logFile.isWritable ( )) - } - catch (const Exception& exc) - { + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << "."; + throw Exception (error); + } // if (fprintf (file, "%s\t%lu\n", (*itl).first.c_str ( ), (*itl).second) < 0) + } // for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + + errno = 0; + if (0 != fflush (file)) { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : " << exc.getFullMessage ( ) << co_endl; - } - exit (0); - } - catch (...) - { - } - - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : erreur non documentée." << co_endl; - } - - exit (0); - } // if (NULL == file) + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != fflush (file)) - // Obtenir le descripteur de fichier : - int fd = fileno (file); - if (-1 == fd) + } // FileLock scope + } + catch (const Exception& exc) { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << co_endl; - } - exit (0); - } // if (-1 == fd) - - // Appliquer un verrou exclusif sur le fichier de logs : - errno = 0; - if (0 != flock (fd, LOCK_EX)) + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << exc.getFullMessage ( ) << co_endl; + } + catch (...) { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors du verrouillage du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; - } - fclose (file); - exit (0); - } // if (0 != flock (fd, LOCK_EX)) + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : erreur non renseignée lors de l'écriture d'informations dans le fichier \"" << fileName << "\"." << co_endl; + } - // Conférer aufichier les droits en écriture pour tous le monde si il vient d'être créé : - if (true == created) + if (0 != file) { - if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO)) - { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors du confèrement à autrui des droits en écriture sur le fichier de logs " << fileName << " : " << strerror (errno) << co_endl; - } - fclose (file); - exit (0); + fclose (file); + file = 0; + } // if (0 != file) + + exit (0); +} // ApplicationStats::logUsage - } // if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO)) - } // if (true == created) - // Lecture et actualisation des logs existants : - map logs; - char name [256]; - size_t count = 0, line = 1; - bool found = false; - int flag = 0; - errno = 0; - while (2 == (flag = fscanf (file, "%s\t%u", name, &count))) - { - line++; - if (name == user) - { - found = true; - count++; - } // if (name == user) - logs.insert (pair (name, count)); - count = 0; - } // while (2 == fscanf (file, "%s\t%u", name, &count)) - if (0 != errno) +/** + * Structure portant les informations à enregistrer dans un fichier, à savoir la version de l'application, le système d'exploitation, et le chemin + * d'accès complet à l'application. + */ +struct OriginInfos +{ + OriginInfos (const string& v, const string& o, const string& p) + : version (v), os (o), path (p), key ( ) { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : " << strerror (errno) << co_endl; - } - fclose (file); - exit (0); - } // if (0 != errno) - else if ((flag < 2) && (EOF != flag)) + ostringstream s; + s << version << os << path; + key = s.str ( ); + } + OriginInfos (const OriginInfos& oi) + : version (oi.version), os (oi.os), path (oi.path), key (oi.key) + { } + OriginInfos& operator = (const OriginInfos& oi) { + if (&oi != this) { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : fichier probablement corrompu." << co_endl; + version = oi.version; + os = oi.os; + path = oi.path; + key = oi.key; } - fclose (file); - exit (0); - } // if (flag < 2) - if (false == found) - logs.insert (pair (user, 1)); - // Ecriture des logs actualisés : - errno = 0; - if (0 != fseek (file, 0, SEEK_SET)) - { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; - } - fclose (file); - exit (0); - } // if (0 != fseek (file, 0, SEEK_SET)) - - for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + return *this; + } + string version, os, path; + string key; +}; // struct OriginInfos + + +static bool operator == (const OriginInfos& left, const OriginInfos& right) +{ + return (left.version == right.version) && (left.os == right.os) && (left.path == right.path) ? true : false; +} // operator == (const OriginInfos& left, const OriginInfos& right) + + +static bool operator != (const OriginInfos& left, const OriginInfos& right) +{ + return !(left == right); +} // operator != (const OriginInfos& left, const OriginInfos& right) + + +static bool operator < (const OriginInfos& left, const OriginInfos& right) +{ + return left.key < right.key; +} // operator < (const OriginInfos& left, const OriginInfos& right) + + + +void ApplicationStats::logUsage (const string& appName, const string& logDir, const string& version, const string& os, const string& path) +{ + FILE* file = 0; + string fileName; + + try { - if (fprintf (file, "%s\t%u\n", (*itl).first.c_str ( ), (*itl).second) < 0) + if ((true == appName.empty ( )) || (true == logDir.empty ( ))) { { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << "."<< co_endl; + + ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : nom d'application ou répertoire des logs non renseigné (" << appName << "/" << logDir << ")." << co_endl; } - fclose (file); - exit (0); - } - } // for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) - errno = 0; - if (0 != fflush (file)) + + return; // Tolérance aux erreurs + } // if ((true == appName.empty ( )) || (true == logDir.empty ( ))) + + // Le nom du fichier : + const Date date; + const string user (UserData (true).getName ( )); + fileName = getFileName (appName + string ("_usages"), logDir, (unsigned long)date.getMonth ( ), date.getYear ( )); + + file = initLogSession (fileName); + if (0 == file) + return; // Process père + + { // FileLock scope + FileLock logLock (fileName, file); + + // Lecture et actualisation des logs existants : + map logs; + char v [255], o [255], p [MAXPATHLEN + 1]; + size_t count = 0, line = 1; + bool found = false; + int flag = 0; + + errno = 0; + const OriginInfos addedOi (version, os, path); + while (4 == (flag = fscanf (file, "%s\t%s\t%s\t%lu", v, o, p, &count))) + { + line++; + const OriginInfos oi (v, o, p); + if (addedOi == oi) + { + found = true; + count++; + } // if (addedOi == oi) + logs.insert (pair(oi, count)); + count = 0; + } // while (4 == (flag = fscanf (file, "%s\t%s\t%s\t%lu", v, o, p, &count))) + + if (0 != errno) + { + UTF8String error (charset); + error << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != errno) + else if ((flag < 4) && (EOF != flag)) + { + UTF8String error (charset); + error << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : fichier probablement corrompu."; + throw Exception (error); + } // if ((flag < 4) && (EOF != flag)) + + if (false == found) + logs.insert (pair(addedOi, 1)); + + // Réécriture des logs actualisés : + errno = 0; + if (0 != fseek (file, 0, SEEK_SET)) + { + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != fseek (file, 0, SEEK_SET)) + + for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + { + if (fprintf (file, "%s\t%s\t%s\t%lu\n", (*itl).first.version.c_str ( ), (*itl).first.os.c_str ( ),(*itl).first.path.c_str ( ), (*itl).second) < 0) + { + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << "."; + throw Exception (error); + } // if (fprintf (file, "%s\t%s\t%s\t%lu\n", (*itl).first.version.c_str ( ), (*itl).first.os.c_str ( ),(*itl).first.path.c_str ( ), (*itl).second) < 0) + } // for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + + errno = 0; + if (0 != fflush (file)) + { + UTF8String error (charset); + error << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno); + errno = 0; + throw Exception (error); + } // if (0 != fflush (file)) + } // FileLock scope + } + catch (const Exception& exc) { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; - } - fclose (file); - exit (0); - } // if (0 != fflush (file)) + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << exc.getFullMessage ( ) << co_endl; + } + catch (...) + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : erreur non renseignée lors de l'écriture d'informations dans le fichier \"" << fileName << "\"." << co_endl; + } - // Libération du verrou : - errno = 0; - if (0 != flock (fd, LOCK_UN)) + if (0 != file) { - { - TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); - ConsoleOutput::cerr ( ) << "Erreur lors du déverrouillage du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; - } fclose (file); - } // if (0 != flock (fd, LOCK_UN)) - - fclose (file); - file = NULL; - fd = -1; + file = 0; + } // if (0 != file) exit (0); } // ApplicationStats::logUsage @@ -335,6 +406,111 @@ void ApplicationStats::logStats (std::ostream& output, const std::string& appNam } // ApplicationStats::logStats +FILE* ApplicationStats::initLogSession (const string fullFileName) +{ + // En vue de ne pas altérer le comportement de l'application tout est effectuée dans un processus fils => exit (0) en toutes circonstances. + errno = 0; + const pid_t pid = fork ( ); + if ((pid_t)-1 == pid) + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "ApplicationStats::initLogSession : échec de fork : " << strerror (errno) << co_endl; + return 0; + } // if ((pid_t)-1 == pid) + if (0 != pid) + { + Process::killAtEnd (pid); + return 0; // Parent + } + + // On détache complètement le fils du parent => peut importe qui fini en premier, l'autre ira jusqu'au bout : + const pid_t sid = setsid ( ); + if ((pid_t)-1 == sid) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "ApplicationStats::initLogSession : échec de setsid : " << strerror (errno) << co_endl; + } + exit (0); + } // if ((pid_t)-1 == sid) + + // On ouvre le fichier en lecture/écriture : + FILE* file = fopen (fullFileName.c_str ( ), "r+"); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire : + const bool created = NULL == file ? true : false; + file = NULL == file ? fopen (fullFileName.c_str ( ), "a+") : file; + if (NULL == file) + { + try + { // On peut avoir des exceptions de levées : chemin non traversable, ... + File logFile (fullFileName); + File dir = logFile.getPath ( ); + if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || (false == dir.isExecutable ( )) || (false == dir.isWritable ( ))) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur, " << dir.getFullFileName ( ) << " n'est pas un répertoire existant avec les droits en écriture pour vous." << co_endl; + } + exit (0); + } // if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || ... + if (false == logFile.isWritable ( )) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : absence de droits en écriture." << co_endl; + } + exit (0); + } // if (false == logFile.isWritable ( )) + } + catch (const Exception& exc) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : " << exc.getFullMessage ( ) << co_endl; + } + exit (0); + } + catch (...) + { + } + + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : erreur non documentée." << co_endl; + } + + exit (0); + } // if (NULL == file) + + // Obtenir le descripteur de fichier : + int fd = fileno (file); + if (-1 == fd) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fullFileName << co_endl; + } + exit (0); + } // if (-1 == fd) + + // Conférer aufichier les droits en écriture pour tous le monde si il vient d'être créé : + if (true == created) + { + if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO)) + { + { + TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg); + ConsoleOutput::cerr ( ) << "Erreur lors du confèrement à autrui des droits en écriture sur le fichier de logs " << fullFileName << " : " << strerror (errno) << co_endl; + } + fclose (file); + exit (0); + + } // if (0 != fchmod (fd) + } // if (true == created) + + return file; +} // ApplicationStats::initLogSession + + int ApplicationStats::readLogs (const string& fileName, map& logs) { File logFile (fileName); @@ -360,7 +536,7 @@ int ApplicationStats::readLogs (FILE& file, const string& fileName, map::iterator itl = logs.find (name); @@ -369,7 +545,7 @@ int ApplicationStats::readLogs (FILE& file, const string& fileName, map::iterator itk = _toKill.begin ( ); _toKill.end ( ) != itk; itk++) { + // int status = 0; + // if ((0 != delay) && (0 == ::waitpid (*itk, &status, WNOHANG))) // fonctionne également :) + if ((0 != delay) && (0 == ::kill (*itk, 0))) + { + ::sleep (delay); + delay = 0; + } // if ((0 != delay) && (0 == ::kill (*itk, 0))) + int res = ::kill (*itk, SIGKILL); if (0 != res) { diff --git a/src/TkUtil/public/TkUtil/ApplicationStats.h b/src/TkUtil/public/TkUtil/ApplicationStats.h index c4a1c81..cdd56cf 100644 --- a/src/TkUtil/public/TkUtil/ApplicationStats.h +++ b/src/TkUtil/public/TkUtil/ApplicationStats.h @@ -47,6 +47,18 @@ class ApplicationStats */ static void logUsage (const std::string& appName, const std::string& logDir); + /** + * Ajoute une utilisation de cette version de l'application pour un operating system et un chemin d'accès donnés. + * L'opération se fait dans un processus détaché. Toute erreur rencontrée est affichée dans la console de lancement de l'application. + * @param appName est le nom de l'application + * @param logDir est le répertoire où sont stockés les fichiers de logs + * @param version est la version utilisée de l'application + * @param os est le nom du système d'exploitation (ou tout autre moyen de l'identifier) + * @param path est le chemin d'accès complet de l'application + * @since 6.14.0 + */ + static void logUsage (const std::string& appName, const std::string& logDir, const std::string& version, const std::string& os, const std::string& path); + /** * Rassemble les utilisations d'une application sur la période demandée et en affiche la synthèse dans le flux transmis en argument. * Toute erreur rencontrée est affichée dans la console de lancement de l'application. @@ -62,6 +74,14 @@ class ApplicationStats private : + /** + * Effectue un fork, ouvre le fichier en lecture/réécriture avec les bons droits, ... + * @return un pointeur sur le fichier ouvert pour y écrire des informations (processus fils) ou 0 (processus père). Appelle exit (0) en cas d'erreur dans le + * processus fils (tolérance aux fautes). + * @since 6.14.0 + */ + static FILE* initLogSession (const std::string fullFileName); + /** * Charge les logs du fichiers ouvert transmis en argument dans la map transmise en second argument. * @return 0 en cas de succès. En cas d'erreur errno est positionné. diff --git a/src/TkUtil/public/TkUtil/FileLock.h b/src/TkUtil/public/TkUtil/FileLock.h new file mode 100644 index 0000000..3b804b3 --- /dev/null +++ b/src/TkUtil/public/TkUtil/FileLock.h @@ -0,0 +1,134 @@ +/** + * \file FileLock.h + * + * \author Charles PIGNEROL, CEA/DAM/DCLC + * + * \date 07/11/2025 + */ +#ifndef FILE_LOCK_H +#define FILE_LOCK_H + +#include +#include +#include +#include +#include + +#include +#include + + +//BEGIN_NAMESPACE_UTIL +namespace TkUtil { + + +/** + * Classe permettant de verrouiller un fichier contre les accès concurrents. + * + * @warning ATTENTION : il ne semble pas à ce jour exister de dispositif portable. + * Cette classe est utilisée en environnement Linux. + * + * @author Charles PIGNEROL, CEA/DAM/DCLC + * @since 6.14.0 + */ +class FileLock +{ + public : + + /** + * Constructeur. + */ + FileLock ( ) + : _path ( ), _file (0), _fd (0) + { } + + /** Constructeur. + * @param Chemin d'accès complet au fichier (pour les messages d'erreur). + * @param Pointeur sur le fichier. + */ + FileLock (const IN_STD string& path, FILE* file) + : _path (path), _file (file), _fd (0) + { + lock ( ); + } + + /** + * Destructeur : libère le verrou. + */ + ~FileLock ( ) + { + unlock ( ); + } + + /** + * Verrouille l'accès au fichier. + * @see unlock + */ + void lock ( ) + { + if (0 == _fd) + { + if (0 == _file) + throw IN_UTIL Exception (IN_UTIL UTF8String ("FileLock::lock : absence de pointeur sur un fichier à verrouiller.", IN_UTIL Charset::UTF_8)); + + _fd = fileno (_file); + if (-1 == _fd) + { + _fd = 0; + throw IN_UTIL Exception (IN_UTIL UTF8String ("FileLock::lock : erreur lors de la récupération d'un descripteur du fichier à verrouiller.", IN_UTIL Charset::UTF_8)); + } // if (-1 == _fd) + } // if (0 == _fd) + + errno = 0; + if (0 != flock (_fd, LOCK_EX)) + { + _fd = 0; + IN_UTIL UTF8String error (IN_UTIL Charset::UTF_8); + error << "Erreur lors du verrouillage du fichier " << _path << " : " << strerror (errno) << "."; + errno = 0; + throw IN_UTIL Exception (error); + } // if (0 != flock (_fd, LOCK_EX)) + } // lock + + /** + * Déverrouille l'accès au fichier. + * @see lock + */ + void unlock ( ) + { + if (0 < _fd) + { + errno = 0; + if (0 != flock (_fd, LOCK_UN)) + { + _fd = 0; + IN_UTIL UTF8String error (IN_UTIL Charset::UTF_8); + error << "Erreur lors du déverrouillage du fichier " << _path << " : " << strerror (errno) << "."; + errno = 0; + throw IN_UTIL Exception (error); + } // if (0 != flock (_fd, LOCK_UN)) + _fd = 0; + } // if (0 < _fd) + } // unlock + + + private : + + /** Constructeur de copie, opérateur = : interdits. */ + FileLock (const FileLock&) + { } + FileLock& operator = (const FileLock&) + { return *this; } + + IN_STD string _path; + FILE* _file; + int _fd; +}; // class FileLock + + +//END_NAMESPACE_UTIL +} + + +#endif // FILE_LOCK_H + diff --git a/src/TkUtil/public/TkUtil/MachineData.h b/src/TkUtil/public/TkUtil/MachineData.h index 94352cc..3c0e558 100644 --- a/src/TkUtil/public/TkUtil/MachineData.h +++ b/src/TkUtil/public/TkUtil/MachineData.h @@ -18,8 +18,7 @@ class OperatingSystem public : /** - * Constructeur par défaut. S'initialise avec les valeurs de la machine - * sur lequel s'exécute le processus. + * Constructeur par défaut. S'initialise avec les valeurs de la machine sur lequel s'exécute le processus. */ OperatingSystem ( ); @@ -30,26 +29,19 @@ class OperatingSystem * @param Diverses informations sur le système d'exploitation. * @param Diverses informations concernant les aspects numériques. */ - OperatingSystem (const IN_STD string& name, const Version& version, - const IN_STD string& infos = "", - const IN_STD string& numericInfos = "") - : _name (name), _version (version), _infos (infos), - _numericInfos (numericInfos) - { } + OperatingSystem (const IN_STD string& name, const Version& version, const IN_STD string& infos = "", const IN_STD string& numericInfos = ""); /** * Constructeur de copie, opérateur = : RAS. */ - OperatingSystem (const OperatingSystem& os) - : _name (os._name), _version (os._version), _infos (os._infos), - _numericInfos (os._numericInfos) - { } + OperatingSystem (const OperatingSystem& os); OperatingSystem& operator = (const OperatingSystem& os) { if (&os != this) { _name = os._name; _version = os._version; + _distribution = os._distribution; _infos = os._infos; _numericInfos = os._numericInfos; } @@ -89,6 +81,15 @@ class OperatingSystem */ virtual void setVersion (const Version& version) { _version = version; } + + /** + * @return La distibution du système d'exploitation (RedHat, Ubuntu, ...). + * @warning Elle est obtenue à la compilation de la bibliothèque. + * @see getName + * @since 6.14.0 + */ + virtual const IN_STD string& getDistribution ( ) const + { return _distribution; } /** * @return Diverses informations sur le système d'exploitation. @@ -128,6 +129,9 @@ class OperatingSystem /** Le nom du système d'exploitation. */ IN_STD string _name; + + /** La distibution du système d'exploitation (RedHat, Ubuntu, ...). */ + IN_STD string _distribution; // v 6.14.0 /** La version du système d'exploitation. */ Version _version; diff --git a/src/TkUtil/public/TkUtil/Process.h b/src/TkUtil/public/TkUtil/Process.h index c02e12e..69f9348 100644 --- a/src/TkUtil/public/TkUtil/Process.h +++ b/src/TkUtil/public/TkUtil/Process.h @@ -305,10 +305,12 @@ class Process /** * Détruit tous les processus dont la destruction a été confiée à cette classe. Ce peut être par exemple des processus * fils lancés via fork et non attendus. + * @param un délai (en secondes) accordés aux processus fils pour se terminer seuls si au moins l'un d'entre-eux + * n'est pas achevé. * @see killAtEnd * @since 6.13.0 */ - static void finalize ( ); + static void finalize (unsigned int delay = 0); /** * Délègue à cette classe le fait de tuer le processus de pid transmis en argument lorsque Process::finalize sera appelé. diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index fbbad54..e8b4175 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -8,6 +8,7 @@ set (ALL_EXECUTABLES logs memory modify_script process remoteProcess removefile script_tags scripting_logs thread_manager thread_pool timer tmpfile unicode urlfifo user_representation userinfos utf8 utf8chars +# app_usage ) set(CMAKE_CTEST_ARGUMENTS "--verbose") enable_testing() diff --git a/src/tests/app_usage.cpp b/src/tests/app_usage.cpp index b9cc6b3..0cc2909 100644 --- a/src/tests/app_usage.cpp +++ b/src/tests/app_usage.cpp @@ -1,5 +1,8 @@ // Test du stockage de stats d'utilisation d'un produit. -#include "TkUtil/ApplicationStats.h" +#include +#include +#include +#include #include #include @@ -8,15 +11,19 @@ using namespace TkUtil; using namespace std; -int main (int argc, char* argv []) +int main (int argc, char* argv [], char* envp []) { if (argc < 3) { cerr << "Syntaxe : " << argv [0] << " NomApp RépertoireLogs" << endl; return -1; } // if (argc < 3) - + + Process::initialize (argc, argv, envp); ApplicationStats::logUsage (argv [1], argv [2]); + const File exe (argv [0]); + ApplicationStats::logUsage (argv [1], argv [2], UtilInfos::getVersion ( ).getVersion ( ), OperatingSystem::instance ( ).getDistribution ( ), exe.getPath ( ).getFullFileName ( )); + Process::finalize (1); return 0; } // main diff --git a/versions.txt b/versions.txt index 3deb45b..223275b 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,13 @@ +Version 6.14.0 : 07/11/25 +================= + +Process::finalize prend en argument supplémentaire un délai accordé aux processus fils pour se terminer sans être tués. + +Classe FileLock permettant de verrouiller un fichier contre des accès concurents. + +Réécriture partielle de la classe ApplicationStats + méthode logUsage enregistrant version + OS. + + Version 6.13.1 : 20/06/25 =================