/* * Copyright (c) 2016, Peter Haag * Copyright (c) 2014, Peter Haag * Copyright (c) 2009, Peter Haag * Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the author nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDINT_H #include #endif #ifndef HAVE_SEMUN union semun { int val; // value for SETVAL struct semid_ds *buf; // buffer for IPC_STAT & IPC_SET u_short *array; // array for GETALL & SETALL }; #endif #include "util.h" #include "bookkeeper.h" static bookkeeper_list_t *bookkeeper_list = NULL; /* function prototypes */ static uint32_t hash(char *str, int flag); static void sem_lock(int sem_set_id); static void sem_unlock(int sem_set_id); static inline bookkeeper_list_t *Get_bookkeeper_list_entry(bookkeeper_t *bookkeeper); /* Create shared memory object and set its size */ /* hash: compute hash value of string */ #define MULTIPLIER 37 static uint32_t hash(char *str, int flag) { uint32_t h; unsigned char *p; h = 0; for (p = (unsigned char*)str; *p != '\0'; p++) h = MULTIPLIER * h + *p; if ( flag ) { h = MULTIPLIER * h + 'R'; } return h; // or, h % ARRAY_SIZE; } // End of hash // locks the semaphore, for exclusive access to the bookkeeping record static void sem_lock(int sem_set_id) { struct sembuf sem_op; /* wait on the semaphore, unless it's value is non-negative. */ sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; if ( semop(sem_set_id, &sem_op, 1) == 0 ) return; LogError("semop() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); } // End of sem_lock // sem_unlock. un-locks the semaphore. static void sem_unlock(int sem_set_id) { struct sembuf sem_op; /* signal the semaphore - increase its value by one. */ sem_op.sem_num = 0; sem_op.sem_op = 1; sem_op.sem_flg = 0; if ( semop(sem_set_id, &sem_op, 1) == 0 ) return; LogError("semop() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); } // End of sem_unlock static inline bookkeeper_list_t *Get_bookkeeper_list_entry(bookkeeper_t *bookkeeper) { bookkeeper_list_t *bookkeeper_list_entry; if ( bookkeeper == NULL ) return NULL; bookkeeper_list_entry = bookkeeper_list; while ( bookkeeper_list_entry != NULL && bookkeeper_list_entry->bookkeeper != bookkeeper ) bookkeeper_list_entry = bookkeeper_list_entry->next; return bookkeeper_list_entry; } // End of Get_bookkeeper_list_entry int InitBookkeeper(bookkeeper_t **bookkeeper, char *path, pid_t nfcapd_pid, pid_t launcher_pid) { int sem_key, shm_key, shm_id, sem_id; union semun sem_val; bookkeeper_list_t **bookkeeper_list_entry; *bookkeeper = NULL; shm_key = hash(path, 0); if ( shm_key == - 1 ) return ERR_PATHACCESS; // check if the shared memory is already allocated shm_id = shmget(shm_key, sizeof(bookkeeper_t), 0600); if ( shm_id >= 0 ) { // the segment already exists. Either a running process is active // or an unclean shutdown happened // map the segement and check the record *bookkeeper = (bookkeeper_t *)shmat(shm_id, NULL, 0); if ( *bookkeeper == (bookkeeper_t *)-1 ) { LogError("shmat() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } if ( (*bookkeeper)->nfcapd_pid <= 0 ) { // rubbish or invalid pid of nfcapd process. // Assume unclean shutdown or something else. We clean up and take this record. memset((void *)(*bookkeeper), 0, sizeof(bookkeeper_t)); } else { // check if the process created this record is still alive int ret = kill((*bookkeeper)->nfcapd_pid, 0); if ( ret == - 1 ) { switch (errno) { case ESRCH: // process does not exist, we can clean up this record and use it memset((void *)(*bookkeeper), 0, sizeof(bookkeeper_t)); break; case EPERM: // A process exists, but we are not allowed to signal this process LogError("Another collector with pid %i but different user ID is already running, and configured for '%s'", (*bookkeeper)->nfcapd_pid, path); return ERR_EXISTS; break; default: // This should never happen, but catch it anyway LogError("semop() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } } else { // process exists; LogError("Another collector with pid %i is already running, and configured for '%s'", (*bookkeeper)->nfcapd_pid, path); return ERR_EXISTS; } // if we pass this point, we have recycled an existing record } } else { // no valid shared segment was found switch (errno) { case ENOENT: // this is ok - no shared segemtn exists, we can create a new one below break; case EACCES: // there is such a segment, but we are not allowed to get it // Assume it's another nfcapd LogError("Access denied to collector bookkeeping record."); return ERR_EXISTS; break; default: // This should never happen, but catch it anyway LogError("semop() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } // we now create a new segement, this hould not fail now shm_id = shmget(shm_key, sizeof(bookkeeper_t), IPC_CREAT | 0600); if ( shm_id == - 1 ) { // but did anyway - give up LogError("shmget() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } *bookkeeper = (bookkeeper_t *)shmat(shm_id, NULL, 0); if ( (*bookkeeper) == (bookkeeper_t *)-1 ) { LogError("shmget() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } memset((void *)(*bookkeeper), 0, sizeof(bookkeeper_t)); } // at this point we now have a valid record and can proceed (*bookkeeper)->nfcapd_pid = nfcapd_pid; (*bookkeeper)->launcher_pid = launcher_pid; (*bookkeeper)->sequence++; // create semaphore sem_key = hash(path, 1); // this should never fail, as we aleady got a key for the shared memory if ( sem_key == - 1 ) { // .. but catch it anyway .. and release shared memory. something is fishy struct shmid_ds buf; shmdt((void *)(*bookkeeper)); shmctl(shm_id, IPC_RMID, &buf); return ERR_FAILED; } // get the semaphore sem_id = semget(sem_key, 1, IPC_CREAT | 0600); if ( sem_id == - 1 ) { struct shmid_ds buf; // this should not have failed LogError("semget() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); // release shared memory shmdt((void *)(*bookkeeper)); shmctl(shm_id, IPC_RMID, &buf); return ERR_FAILED; } // initialize the semaphore sem_val.val = 1; if ( semctl(sem_id, 0, SETVAL, sem_val) == -1) { struct shmid_ds buf; // this should not have failed LogError("semctl() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); // release shared memory shmdt((void *)(*bookkeeper)); shmctl(shm_id, IPC_RMID, &buf); return ERR_FAILED; } bookkeeper_list_entry = &bookkeeper_list; while ( *bookkeeper_list_entry != NULL ) bookkeeper_list_entry = &((*bookkeeper_list_entry)->next); (*bookkeeper_list_entry) = (bookkeeper_list_t *)malloc(sizeof(bookkeeper_list_t)); if ( !*bookkeeper_list_entry ) { struct shmid_ds buf; LogError("malloc() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); shmdt((void *)(*bookkeeper)); shmctl(shm_id, IPC_RMID, &buf); semctl( sem_id, 0, IPC_RMID ); return ERR_FAILED; } memset((void *)*bookkeeper_list_entry, 0, sizeof(bookkeeper_list_t)); (*bookkeeper_list_entry)->shm_id = shm_id; (*bookkeeper_list_entry)->sem_id = sem_id; (*bookkeeper_list_entry)->bookkeeper = *bookkeeper; (*bookkeeper_list_entry)->next = NULL; // we are done return BOOKKEEPER_OK; } // End of InitBookkeeper int AccessBookkeeper(bookkeeper_t **bookkeeper, char *path) { bookkeeper_list_t **bookkeeper_list_entry; int sem_key, shm_key, shm_id, sem_id; *bookkeeper = NULL; shm_key = hash(path, 0); if ( shm_key == - 1 ) return ERR_PATHACCESS; // check if the shared memory is already allocated shm_id = shmget(shm_key, sizeof(bookkeeper_t), 0600); if ( shm_id < 0 ) { // the segment does not exists. Check why switch (errno) { case ENOENT: // no shared segemtn exists. return ERR_NOTEXISTS; break; case EACCES: // there is such a segment, but we are not allowed to get it // Assume it's another nfcapd LogError("Access denied to collector bookkeeping record."); return ERR_FAILED; break; default: // This should never happen, but catch it anyway LogError("semop() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } // not reached } // at this point we now have a valid record and can proceed // create semaphore sem_key = hash(path, 1); // this should never fail, as we aleady got a key for the shared memory if ( sem_key == - 1 ) { // .. but catch it anyway .. and release shared memory. something is fishy return ERR_FAILED; } // get the semaphore sem_id = semget(sem_key, 1, 0600); if ( sem_id == - 1 ) { // this should not have failed LogError("semget() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } // map the shared segment *bookkeeper = (bookkeeper_t *)shmat(shm_id, NULL, 0); if ( *bookkeeper == (bookkeeper_t *)-1 ) { LogError("shmat() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } bookkeeper_list_entry = &bookkeeper_list; while ( *bookkeeper_list_entry != NULL && (*bookkeeper_list_entry)->bookkeeper != NULL ) bookkeeper_list_entry = &((*bookkeeper_list_entry)->next); // allocate new slot, else use unused slot if ( *bookkeeper_list_entry == NULL ) { (*bookkeeper_list_entry) = (bookkeeper_list_t *)malloc(sizeof(bookkeeper_list_t)); if ( !*bookkeeper_list_entry ) { LogError("malloc() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); return ERR_FAILED; } memset((void *)*bookkeeper_list_entry, 0, sizeof(bookkeeper_list_t)); } (*bookkeeper_list_entry)->shm_id = shm_id; (*bookkeeper_list_entry)->sem_id = sem_id; (*bookkeeper_list_entry)->bookkeeper = *bookkeeper; (*bookkeeper_list_entry)->next = NULL; return BOOKKEEPER_OK; } // End of AccessBookkeeper void ReleaseBookkeeper(bookkeeper_t *bookkeeper, int destroy) { bookkeeper_list_t *bookkeeper_list_entry; struct shmid_ds buf; if ( !bookkeeper ) return; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return; } // detach from my process addr space memory if ( shmdt((void *)bookkeeper) == -1 ) { // ups .. LogError("shmdt() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); } bookkeeper = NULL; if ( destroy == 0 ) { // Entry no longer valid bookkeeper_list_entry->bookkeeper = NULL; bookkeeper_list_entry->shm_id = 0; bookkeeper_list_entry->sem_id = 0; return; } // prevent other proceeses to access the share memory, while we are removing it // try to clean up. sem_lock(bookkeeper_list_entry->sem_id); if ( shmctl(bookkeeper_list_entry->shm_id, IPC_RMID, &buf) ) { // ups .. LogError("shmctl() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); } sem_unlock(bookkeeper_list_entry->sem_id); if ( semctl( bookkeeper_list_entry->sem_id, 0, IPC_RMID ) == -1 ) { // ups .. LogError("semctl() error in %s line %d: %s", __FILE__, __LINE__, strerror(errno) ); } // Entry no longer valid bookkeeper_list_entry->bookkeeper = NULL; bookkeeper_list_entry->shm_id = 0; bookkeeper_list_entry->sem_id = 0; } // End of ReleaseBookkeeper int LookBooks(bookkeeper_t *bookkeeper) { bookkeeper_list_t *bookkeeper_list_entry; if ( !bookkeeper ) return 0; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return 1; } sem_lock(bookkeeper_list_entry->sem_id); return 0; } // End of LookBooks int UnlookBooks(bookkeeper_t *bookkeeper) { bookkeeper_list_t *bookkeeper_list_entry; if ( !bookkeeper ) return 0; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return 1; } sem_unlock(bookkeeper_list_entry->sem_id); return 0; } // End of UnlookBooks void ClearBooks(bookkeeper_t *bookkeeper, bookkeeper_t *tmp_books) { bookkeeper_list_t *bookkeeper_list_entry; if ( !bookkeeper ) return; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return; } sem_lock(bookkeeper_list_entry->sem_id); // backup copy if ( tmp_books != NULL ) { memcpy((void *)tmp_books, (void *)bookkeeper, sizeof(bookkeeper_t)); } bookkeeper->first = 0; bookkeeper->last = 0; bookkeeper->numfiles = 0; bookkeeper->filesize = 0; bookkeeper->sequence++; sem_unlock(bookkeeper_list_entry->sem_id); } // End of ClearBooks uint64_t BookSequence(bookkeeper_t *bookkeeper) { bookkeeper_list_t *bookkeeper_list_entry; uint64_t seq; if ( !bookkeeper ) return 0; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return 0; } sem_lock(bookkeeper_list_entry->sem_id); seq = bookkeeper->sequence; sem_unlock(bookkeeper_list_entry->sem_id); return seq; } // End of BookSequence void UpdateBooks(bookkeeper_t *bookkeeper, time_t when, uint64_t size) { bookkeeper_list_t *bookkeeper_list_entry; if ( !bookkeeper ) return; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return; } sem_lock(bookkeeper_list_entry->sem_id); if ( bookkeeper->first == 0 ) bookkeeper->first = when; bookkeeper->last = when; bookkeeper->numfiles++; bookkeeper->filesize += size; bookkeeper->sequence++; sem_unlock(bookkeeper_list_entry->sem_id); } // End of UpdateBooks void UpdateBooksParam(bookkeeper_t *bookkeeper, time_t lifetime, uint64_t maxsize) { bookkeeper_list_t *bookkeeper_list_entry; if ( !bookkeeper ) return; bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return; } sem_lock(bookkeeper_list_entry->sem_id); bookkeeper->max_lifetime = lifetime; bookkeeper->max_filesize = maxsize; bookkeeper->sequence++; sem_unlock(bookkeeper_list_entry->sem_id); } // End of UpdateBooksParam void PrintBooks(bookkeeper_t *bookkeeper) { bookkeeper_list_t *bookkeeper_list_entry; struct tm *ts; time_t t; char string[32]; if ( !bookkeeper ) { printf("No bookkeeper record available!\n"); return; } bookkeeper_list_entry = Get_bookkeeper_list_entry(bookkeeper); if ( !bookkeeper_list_entry ) { // this should never happen LogError("Software error in %s line %d: %s", __FILE__, __LINE__, "Entry not found in list"); return; } sem_lock(bookkeeper_list_entry->sem_id); printf("Collector process: %lu\n", (unsigned long)bookkeeper->nfcapd_pid); if ( bookkeeper->launcher_pid ) printf("Launcher process: %lu\n", (unsigned long)bookkeeper->launcher_pid); else printf("Launcher process: \n"); printf("Record sequence : %llu\n", (unsigned long long)bookkeeper->sequence); t = bookkeeper->first; ts = localtime(&t); strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts); string[31] = '\0'; printf("First : %s\n", bookkeeper->first ? string : ""); t = bookkeeper->last; ts = localtime(&t); strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts); string[31] = '\0'; printf("Last : %s\n", bookkeeper->last ? string : ""); printf("Number of files : %llu\n", (unsigned long long)bookkeeper->numfiles); printf("Total file size : %llu\n", (unsigned long long)bookkeeper->filesize); printf("Max file size : %llu\n", (unsigned long long)bookkeeper->max_filesize); printf("Max life time : %llu\n", (unsigned long long)bookkeeper->max_lifetime); sem_unlock(bookkeeper_list_entry->sem_id); } // End of PrintBooks