Developing Cross Platform Apache Modules using ap_retained_data_create/get in Post Read Config

292 views Asked by At

I had the express pleasure of debugging for hours a quirk in the design for Apache 2.4 while building a cross platform module.

The problem I faced is that Apache, when starting, loads the configuration file twice, and thus loads the module itself twice. Once I guess to test the configuration and read the command struct and then the next to fill in the blanks for the server to run. This would not normally be of any issue, but I needed to load my module's configuration at load time before serving any clients and only parse my module's needs ONCE AND ONCE ONLY.

Since I use a number of resources including databases and the like, I decided running multiple times is not the best idea, especially when taking hits on a database server.

The online manual ("Developer API 2.5") says to instead of using an old method, which I will leave the reader to look up, to use ap_retained_data_get and ap_retained_data_create to retain data across module unloading. Preferably to pass a flag to the next phase that you have passed the testing phase already.

Herein is where the headache lies. This is not the way it works on Windows.

1

There are 1 answers

1
Alex Erwin On

Linux does operate in two passes, but Windows works in four passes.

Using this method in the post config read hook works for Linux but not Windows

    // variables for base config start
    const char  *flag = "some_prefixed_flag_to_mashup_with_other_flags";
    void        *init_flag = NULL;
    int         dbl = APLOG_TRACE4;
    // logger
    logging logger(NULL, s, p);     
    // determine if this is the first time we have loaded
    init_flag = ap_retained_data_get(flag);
    // check flag result
    if (init_flag == NULL) 
    {
        // breakpoint
        stdLog(logger, INFX_LOG_DATA, dbl);
        // set first time flag local
        ap_retained_data_create(flag, 1);
    }
    // call initization routine
    else
    {
        // do something here

    }

Please note that I am export C++ code in my modules, hence the use of classes. Also some of the variable declarations are absent for brevity.

So this should be enough for Windows right? Wrong. I had to learn this backwards because I am building on Windows first, but Windows has four passes instead of two. There for your initialization will still run twice.

My next solution was as follows:

    // variables for base config start
    const char  *flag = "some_prefixed_flag_to_mashup_with_other_flags";
    void        *init_flag = NULL;
    char        *pidname;
    int         dbl = APLOG_TRACE4;
    pid_t       pidNKey;
    apr_file_t  *pidfile;       
    // logger
    logging logger(NULL, s, p);     
    // determine if this is the first time we have loaded
    init_flag = ap_retained_data_get(flag);
    // check flag result
    if (init_flag == NULL) 
    {
        // breakpoint
        stdLog(logger, INFX_LOG_DATA, dbl);
        // set first time flag local
        ap_retained_data_create(flag, 1);
    }
    else
    {
        // break point
        stdLog(logger, INFX_LOG_DATA, dbl);
        // create a pid if not exists
        if (ap_read_pid(p, "logs/httpd.pid", &pidNKey) == OK)
        {
            // break point
            stdLog(logger, INFX_LOG_DATA, dbl);
            // create a pid especially for our setup
            pidname = apr_psprintf(ptemp, "logs/infx.%d.pid", pidNKey);
            // if pidfile does not exist then create it
            if (!fileExists(pidname, ptemp))
            {
                // break point
                stdLog(logger, INFX_LOG_DATA, dbl);
                // create the pid
                apr_file_open(&pidfile, pidname, APR_WRITE|APR_APPEND|APR_CREATE, INFX_BASE_PERM, ptemp);
                // add nonsensical data to it
                apr_file_puts("1", pidfile);
                // cllose the file and wait for run 2
                apr_file_close(pidfile);
            }
            // begin work
            else 
            {
                // break point
                stdLog(logger, INFX_LOG_DATA, dbl);
                // we no longer require the pid file
                apr_file_remove(pidname, ptemp);
            }
        }
    }

Seemed to work.... Ok, I am done with that part, right? WRONG! I got over to the Linux box and segfault city. I am sure that plenty of seasoned Apache developers are probably shaking their heads right now, but these are the same guys who are not documenting these issues.

My final fix was to wrap the Windows code in define blocks. I am not sure if there was a better way or not, but it worked for me. So the following works on both platforms without segfault.

    // variables for base config start
    const char  *flag = "some_prefixed_flag_to_mashup_with_other_flags";
    void        *init_flag = NULL;
    int         dbl = APLOG_TRACE4;
    // logger
    logging logger(NULL, s, p);     
    // determine if this is the first time we have loaded
    init_flag = ap_retained_data_get(flag);
    // check flag result
    if (init_flag == NULL) 
    {
        // breakpoint
        stdLog(logger, INFX_LOG_DATA, dbl);
        // set first time flag local
        ap_retained_data_create(flag, 1);
    }
    // call initization routine
    else
    {
        // break point
        stdLog(logger, INFX_LOG_DATA, dbl);
        #if defined(WIN32)
        // create a pid if not exists
        if (ap_read_pid(p, "logs/httpd.pid", &pidNKey) == OK)
        {
            // break point
            stdLog(logger, INFX_LOG_DATA, dbl);
            // create a pid especially for our setup
            pidname = apr_psprintf(ptemp, "logs/infx.%d.pid", pidNKey);
            // if pidfile does not exist then create it
            if (!fileExists(pidname, ptemp))
            {
                // break point
                stdLog(logger, INFX_LOG_DATA, dbl);
                // create the pid
                apr_file_open(&pidfile, pidname, APR_WRITE|APR_APPEND|APR_CREATE, INFX_BASE_PERM, ptemp);
                // add nonsensical data to it
                apr_file_puts("1", pidfile);
                // cllose the file and wait for run 2
                apr_file_close(pidfile);
            }
            // begin work
            else 
            {
                // break point
                stdLog(logger, INFX_LOG_DATA, dbl);
                // we no longer require the pid file
                apr_file_remove(pidname, ptemp);
        #endif
                // do something here for both platforms
        #if defined(WIN32)
            }
        }
        // crash if we do get a proper pid
        else
        {
            // breakpoint
            stdLog(logger, INFX_LOG_DATA, APLOG_CRIT, "HTTPD File not found? A Bug?");
            // set status
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        #endif
    }           

Hopefully, someone else may benefit from this.