A simple C/C++ database daemon

Here is a simple C/C++ daemon for watching MySQL tables for updates.   This is a full working program that illustrates how to build a daemon,  that uses MySQL C/C++ connector and Google glog for logging.   This daemon also uses a simple emailer that employs a pipe to execute the mail trasport agent (MTA) in our Debian system.   Postfix sendmail is our MTA.

Install the build tools and the MySQL C/C++ connector libraries and Google glog logging system.   We assume you already have MySQL server and client installed.    For the emailer to work you should also have an MTA installed and configured.

apt-get update

followed by

apt-get install build-essentials libmysqlcppconn-dev libgoogle-glog-dev

The main daemon block

Please refer to the inline comments for explanation of the lines in the code.


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>

#include <string>
#include <memory>

#include <cppconn/driver.h>
#include <cppconn/connection.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>
#include <cppconn/resultset.h>

#define DEBUG__

#include <glog/logging.h>

using namespace std;
using namespace sql;

bool exitNow = false;

// The main task
void checkforupdates();
// The emailer
bool sendmail(const string&, const string&, const string&);

// Signal handler

void handler(int signum) {
    exitNow = true;
    LOG(INFO) << "Exit signal received !";
}

int main(int argrc, char *argv[]) {

    // Initialize Google logging system.
    google::InitGoogleLogging("mydeamon");
    google::SetLogDestination(google::INFO, "/var/tmp/mylogs.text");
    google::SetLogDestination(google::FATAL, "/var/tmp/mylogs.text");
    google::SetLogDestination(google::ERROR, "/var/tmp/mylogs.text");

    // Create child process
    pid_t pid = fork();

    if (pid < 0) {
        LOG(FATAL) << "Could not create child process !";
        exit(EXIT_FAILURE);
    }
    // Child process created, exit parent process
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }
    // Set file permission for files created by our child process
    umask(0);

    // Create session for our new process
    pid_t sid = setsid();
    if (sid < 0) {
        LOG(FATAL) << "Could not create session for child process !";
        exit(EXIT_FAILURE);
    }

    LOG(INFO) << "Child process created";

    // close all standard file descriptors.
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);

    // Install our signal handler.  This responds to 
    // kill [pid] from the command line
    signal(SIGTERM, handler);

    // Ignore signal  when terminal session is closed.  This keeps our
    // daemon alive even when user closed terminal session
    signal(SIGHUP, SIG_IGN);

    // The daemon loop
    // If you break from this loop,  our daemon process exits.
    while (1) {

        // The main task 
        checkforupdates();

        if (exitNow) {
            break;
        }

        sleep(60 * 10);
    }

    exit(EXIT_SUCCESS);    
}

The Checkforupdates function is where the actual task of the daemon is done.   It is placed in a loop that exits only when it receives a signal to break out.   Use kill [pid] from the command line to send a terminate signal to our daemon.

Database functions

This is an illustration on how to use the MySQL C/C++ connector library and the Google glog logging system. For more information on using Google Glog please see thier page here http://google-glog.googlecode.com/svn/trunk/doc/glog.html .   Documentation for Mysql C++ connector is here http://dev.mysql.com/doc/ .

Note that we did our glog initialization in our main function.    Also note the use of LOG(FATAL).   LOG(FATAL) calls abort after logging the message.   If MySQL throws exception,  there is not much you can do other than fixing something from MySQL or in your code.   Use LOG(ERROR) if you want to continue after an exception.

void checkforupdates() {

    Driver *driver;
    try {
        driver = ::get_driver_instance();
#ifdef DEBUG__        
        LOG(INFO) << "Driver instance created";
#endif        
    } catch (SQLException& e) {
        LOG(FATAL) << e.what();
        return;
    }

    auto_ptr<Connection> connection;
    try {
        connection = auto_ptr<Connection > (driver->connect(
                "tcp://localhost:3306", 
                "user", "password"));
         connection->setSchema("database"); // database to use
#ifdef DEBUG__        
         LOG(INFO) << "Connection established";
#endif
    } catch (SQLException& e) {
        LOG(FATAL) << e.what();
    }

    auto_ptr<Statement> statement;
    try {
        statement = auto_ptr<Statement> 
                ((connection->createStatement()));
    }
    catch (SQLException& e){
        connection->close (); // catch this as well
        LOG(FATAL) << e.what();
    }
    
    try {        
        auto_ptr<ResultSet> newsitems(statement->executeQuery(
                "select news_title,news_teaser, news_type, news_id "
                "from newsitems where news_id < 100"));
        // Code remove for clarity
        while (newsitems->next()) {
            auto_ptr<ResultSet> subscriber(statement->executeQuery(
                "select subs_email from susbscribers "
                "where subs_type="+newsitems->getString(3)));
            while (subscriber->next()) {
                String message;
                // Build message and url
                if (sendemail (subscriber->getString(1),newsitems->getString(1),
                        message)) {                    
                }
            }
            // update news_status
            // Code removed for clarity
        }
        // Code removed for clarity
    } catch (SQLException& e) {
        connection->close();  // catch this as well
        LOG(FATAL) << e.what();
    }

    try {
        connection->close();
#ifdef DEBUG__
        LOG(INFO) << "Connection closed";
#endif
    } catch (SQLException& e) {
        LOG(FATAL) << e.what();
    }
}

If you are using C++11 compiler, unique_ptr may be desirable instead of auto_ptr.

The emailer routine

This is a simple emailer routine that executes sendmail as a subprocess.   The -t option is telling sendmail to use the message header for the mail recepients.   Please see your sendmail man pages for more options.

const char* sender_email="ouremail@gmail.com";

bool sendmail(const string& receiver, 
        const string& subject, const string& message) {

    bool success = false;

    try {
        FILE *mta = popen("/usr/lib/sendmail -t", "w");
        if (mta != 0) {
            fprintf(mta, "To: %s\n", receiver.c_str());
            fprintf(mta, "From: %s\n", sender_email);
            fprintf(mta, "Subject: %s\n\n", subject.c_str());
            fwrite(message.c_str(), 1, strlen(message.c_str()), mta);
            fwrite(".\n", 1, 2, mta);

            pclose(mta);

            success = true;
        }
    } catch (...) {
        LOG(ERROR) << "Error in sending email";     
    }

    return success;
}

Makefile

The precedding code is fairly small can be placed in a single cpp file in the order it appears in this page.   If you name your file as daemon.cpp,  you can build it using the following command.

g++ -I. -Wall -o daemon -lmysqlcppconn -lglog daemon.cpp

You can also use the following Makefile.

CC	= g++ 
CFLAGS	= -I. -Wall 
LDFLAGS = -lmysqlcppconn -lglog

all: daemon  clean

deamon: daemon.o 
	$(CC) -o $@ $^ $(LDFLAGS) 

daemon.o: daemon.cpp 
	$(CC) -c $(CFLAGS) $<

clean: 
	rm *.o 

That's it. Good luck.

If you like the article, please share.
(Site URL pattern has changed as a result social actions counter was reset.)



Comment icon   Comments (Newest first)