Responder.c   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#if __APPLE__
// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated”
// error, which prevents compilation because we build with "-Werror".
// Since this is supposed to be portable cross-platform code, we don't care that daemon is
// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message.
#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou
#endif

#include <assert.h>
#include <stdio.h>          // For printf()
#include <stdlib.h>         // For exit() etc.
#include <string.h>         // For strlen() etc.
#include <unistd.h>         // For select()
#include <errno.h>          // For errno, EINTR
#include <signal.h>
#include <fcntl.h>

#if __APPLE__
#undef daemon
extern int daemon(int, int);
#endif

#include "mDNSEmbeddedAPI.h" // Defines the interface to the client layer above
#include "mDNSPosix.h"      // Defines the specific types needed to run mDNS on this platform
#include "mDNSUNP.h"        // For daemon()

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark ***** Globals
#endif

static mDNS mDNSStorage;       // mDNS core uses this to store its globals
static mDNS_PlatformSupport PlatformStorage;  // Stores this platform's globals

mDNSexport const char ProgramName[] = "mDNSResponderPosix";

static const char *gProgramName = ProgramName;

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark ***** Signals
#endif

static volatile mDNSBool gReceivedSigUsr1;
static volatile mDNSBool gReceivedSigHup;
static volatile mDNSBool gStopNow;

// We support 4 signals.
//
// o SIGUSR1 toggles verbose mode on and off in debug builds
// o SIGHUP  triggers the program to re-read its preferences.
// o SIGINT  causes an orderly shutdown of the program.
// o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous)
// o SIGKILL kills us dead (easy to implement :-)
//
// There are fatal race conditions in our signal handling, but there's not much
// we can do about them while remaining within the Posix space.  Specifically,
// if a signal arrives after we test the globals its sets but before we call
// select, the signal will be dropped.  The user will have to send the signal
// again.  Unfortunately, Posix does not have a "sigselect" to atomically
// modify the signal mask and start a select.

static void HandleSigUsr1(int sigraised)
// If we get a SIGUSR1 we toggle the state of the
// verbose mode.
{
    assert(sigraised == SIGUSR1);
    gReceivedSigUsr1 = mDNStrue;
}

static void HandleSigHup(int sigraised)
// A handler for SIGHUP that causes us to break out of the
// main event loop when the user kill 1's us.  This has the
// effect of triggered the main loop to deregister the
// current services and re-read the preferences.
{
    assert(sigraised == SIGHUP);
    gReceivedSigHup = mDNStrue;
}

static void HandleSigInt(int sigraised)
// A handler for SIGINT that causes us to break out of the
// main event loop when the user types ^C.  This has the
// effect of quitting the program.
{
    assert(sigraised == SIGINT);

    if (gMDNSPlatformPosixVerboseLevel > 0) {
        fprintf(stderr, "\nSIGINT\n");
    }
    gStopNow = mDNStrue;
}

static void HandleSigQuit(int sigraised)
// If we get a SIGQUIT the user is desperate and we
// just call mDNS_Close directly.  This is definitely
// not safe (because it could reenter mDNS), but
// we presume that the user has already tried the safe
// alternatives.
{
    assert(sigraised == SIGQUIT);

    if (gMDNSPlatformPosixVerboseLevel > 0) {
        fprintf(stderr, "\nSIGQUIT\n");
    }
    mDNS_Close(&mDNSStorage);
    exit(0);
}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark ***** Parameter Checking
#endif

static mDNSBool CheckThatRichTextNameIsUsable(const char *richTextName, mDNSBool printExplanation)
// Checks that richTextName is reasonable
// label and, if it isn't and printExplanation is true, prints
// an explanation of why not.
{
    mDNSBool result = mDNStrue;
    if (result && strlen(richTextName) > 63) {
        if (printExplanation) {
            fprintf(stderr,
                    "%s: Service name is too long (must be 63 characters or less)\n",
                    gProgramName);
        }
        result = mDNSfalse;
    }
    if (result && richTextName[0] == 0) {
        if (printExplanation) {
            fprintf(stderr, "%s: Service name can't be empty\n", gProgramName);
        }
        result = mDNSfalse;
    }
    return result;
}

static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType, mDNSBool printExplanation)
// Checks that serviceType is a reasonable service type
// label and, if it isn't and printExplanation is true, prints
// an explanation of why not.
{
    mDNSBool result;

    result = mDNStrue;
    if (result && strlen(serviceType) > 63) {
        if (printExplanation) {
            fprintf(stderr,
                    "%s: Service type is too long (must be 63 characters or less)\n",
                    gProgramName);
        }
        result = mDNSfalse;
    }
    if (result && serviceType[0] == 0) {
        if (printExplanation) {
            fprintf(stderr,
                    "%s: Service type can't be empty\n",
                    gProgramName);
        }
        result = mDNSfalse;
    }
    return result;
}

static mDNSBool CheckThatPortNumberIsUsable(long portNumber, mDNSBool printExplanation)
// Checks that portNumber is a reasonable port number
// and, if it isn't and printExplanation is true, prints
// an explanation of why not.
{
    mDNSBool result;

    result = mDNStrue;
    if (result && (portNumber <= 0 || portNumber > 65535)) {
        if (printExplanation) {
            fprintf(stderr,
                    "%s: Port number specified by -p must be in range 1..65535\n",
                    gProgramName);
        }
        result = mDNSfalse;
    }
    return result;
}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark ***** Command Line Arguments
#endif

static const char kDefaultPIDFile[]     = "/var/run/mDNSResponder.pid";
static const char kDefaultServiceType[] = "_afpovertcp._tcp.";
static const char kDefaultServiceDomain[] = "local.";
enum {
    kDefaultPortNumber = 548
};

static void PrintUsage()
{
    fprintf(stderr,
            "Usage: %s [-v level ] [-r] [-n name] [-t type] [-d domain] [-p port] [-f file] [-b] [-P pidfile] [-x name=val ...]\n",
            gProgramName);
    fprintf(stderr, "          -v verbose mode, level is a number from 0 to 2\n");
    fprintf(stderr, "             0 = no debugging info (default)\n");
    fprintf(stderr, "             1 = standard debugging info\n");
    fprintf(stderr, "             2 = intense debugging info\n");
    fprintf(stderr, "             can be cycled kill -USR1\n");
    fprintf(stderr, "          -r also bind to port 53 (port 5353 is always bound)\n");
    fprintf(stderr, "          -n uses 'name' as the service name (required)\n");
    fprintf(stderr, "          -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType);
    fprintf(stderr, "          -d uses 'domain' as the service domain (default is '%s')\n", kDefaultServiceDomain);
    fprintf(stderr, "          -p uses 'port' as the port number (default is '%d')\n",  kDefaultPortNumber);
    fprintf(stderr, "          -f reads a service list from 'file'\n");
    fprintf(stderr, "          -b forces daemon (background) mode\n");
    fprintf(stderr, "          -P uses 'pidfile' as the PID file\n");
    fprintf(stderr, "             (default is '%s')\n",  kDefaultPIDFile);
    fprintf(stderr, "             only meaningful if -b also specified\n");
    fprintf(stderr, "          -x stores name=val in TXT record (default is empty).\n");
    fprintf(stderr, "             MUST be the last command-line argument;\n");
    fprintf(stderr, "             all subsequent arguments after -x are treated as name=val pairs.\n");
}

static mDNSBool gAvoidPort53      = mDNStrue;
static const char *gServiceName      = "";
static const char *gServiceType      = kDefaultServiceType;
static const char *gServiceDomain    = kDefaultServiceDomain;
static mDNSu8 gServiceText[sizeof(RDataBody)];
static mDNSu16 gServiceTextLen   = 0;
static int gPortNumber       = kDefaultPortNumber;
static const char *gServiceFile      = "";
static mDNSBool gDaemon           = mDNSfalse;
static const char *gPIDFile          = kDefaultPIDFile;

static void ParseArguments(int argc, char **argv)
// Parses our command line arguments into the global variables
// listed above.
{
    int ch;

    // Set gProgramName to the last path component of argv[0]

    gProgramName = strrchr(argv[0], '/');
    if (gProgramName == NULL) {
        gProgramName = argv[0];
    } else {
        gProgramName += 1;
    }

    // Parse command line options using getopt.

    do {
        ch = getopt(argc, argv, "v:rn:t:d:p:f:dP:bx");
        if (ch != -1) {
            switch (ch) {
            case 'v':
                gMDNSPlatformPosixVerboseLevel = atoi(optarg);
                if (gMDNSPlatformPosixVerboseLevel < 0 || gMDNSPlatformPosixVerboseLevel > 2) {
                    fprintf(stderr,
                            "%s: Verbose mode must be in the range 0..2\n",
                            gProgramName);
                    exit(1);
                }
                break;
            case 'r':
                gAvoidPort53 = mDNSfalse;
                break;
            case 'n':
                gServiceName = optarg;
                if ( !CheckThatRichTextNameIsUsable(gServiceName, mDNStrue) ) {
                    exit(1);
                }
                break;
            case 't':
                gServiceType = optarg;
                if ( !CheckThatServiceTypeIsUsable(gServiceType, mDNStrue) ) {
                    exit(1);
                }
                break;
            case 'd':
                gServiceDomain = optarg;
                break;
            case 'p':
                gPortNumber = atol(optarg);
                if ( !CheckThatPortNumberIsUsable(gPortNumber, mDNStrue) ) {
                    exit(1);
                }
                break;
            case 'f':
                gServiceFile = optarg;
                break;
            case 'b':
                gDaemon = mDNStrue;
                break;
            case 'P':
                gPIDFile = optarg;
                break;
            case 'x':
                while (optind < argc)
                {
                    gServiceText[gServiceTextLen] = strlen(argv[optind]);
                    mDNSPlatformMemCopy(gServiceText+gServiceTextLen+1, argv[optind], gServiceText[gServiceTextLen]);
                    gServiceTextLen += 1 + gServiceText[gServiceTextLen];
                    optind++;
                }
                ch = -1;
                break;
            case '?':
            default:
                PrintUsage();
                exit(1);
                break;
            }
        }
    } while (ch != -1);

    // Check for any left over command line arguments.

    if (optind != argc) {
        PrintUsage();
        fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]);
        exit(1);
    }

    // Check for inconsistency between the arguments.

    if ( (gServiceName[0] == 0) && (gServiceFile[0] == 0) ) {
        PrintUsage();
        fprintf(stderr, "%s: You must specify a service name to register (-n) or a service file (-f).\n", gProgramName);
        exit(1);
    }
}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark ***** Registration
#endif

typedef struct PosixService PosixService;

struct PosixService {
    ServiceRecordSet coreServ;
    PosixService *next;
    int serviceID;
};

static PosixService *gServiceList = NULL;

static void RegistrationCallback(mDNS *const m, ServiceRecordSet *const thisRegistration, mStatus status)
// mDNS core calls this routine to tell us about the status of
// our registration.  The appropriate action to take depends
// entirely on the value of status.
{
    switch (status) {

    case mStatus_NoError:
        debugf("Callback: %##s Name Registered",   thisRegistration->RR_SRV.resrec.name->c);
        // Do nothing; our name was successfully registered.  We may
        // get more call backs in the future.
        break;

    case mStatus_NameConflict:
        debugf("Callback: %##s Name Conflict",     thisRegistration->RR_SRV.resrec.name->c);

        // In the event of a conflict, this sample RegistrationCallback
        // just calls mDNS_RenameAndReregisterService to automatically
        // pick a new unique name for the service. For a device such as a
        // printer, this may be appropriate.  For a device with a user
        // interface, and a screen, and a keyboard, the appropriate response
        // may be to prompt the user and ask them to choose a new name for
        // the service.
        //
        // Also, what do we do if mDNS_RenameAndReregisterService returns an
        // error.  Right now I have no place to send that error to.

        status = mDNS_RenameAndReregisterService(m, thisRegistration, mDNSNULL);
        assert(status == mStatus_NoError);
        break;

    case mStatus_MemFree:
        debugf("Callback: %##s Memory Free",       thisRegistration->RR_SRV.resrec.name->c);

        // When debugging is enabled, make sure that thisRegistration
        // is not on our gServiceList.

            #if !defined(NDEBUG)
        {
            PosixService *cursor;

            cursor = gServiceList;
            while (cursor != NULL) {
                assert(&cursor->coreServ != thisRegistration);
                cursor = cursor->next;
            }
        }
            #endif
        free(thisRegistration);
        break;

    default:
        debugf("Callback: %##s Unknown Status %ld", thisRegistration->RR_SRV.resrec.name->c, status);
        break;
    }
}

static int gServiceID = 0;

static mStatus RegisterOneService(const char *  richTextName,
                                  const char *  serviceType,
                                  const char *  serviceDomain,
                                  const mDNSu8 text[],
                                  mDNSu16 textLen,
                                  long portNumber)
{
    mStatus status;
    PosixService *      thisServ;
    domainlabel name;
    domainname type;
    domainname domain;

    status = mStatus_NoError;
    thisServ = (PosixService *) malloc(sizeof(*thisServ));
    if (thisServ == NULL) {
        status = mStatus_NoMemoryErr;
    }
    if (status == mStatus_NoError) {
        MakeDomainLabelFromLiteralString(&name,  richTextName);
        MakeDomainNameFromDNSNameString(&type, serviceType);
        MakeDomainNameFromDNSNameString(&domain, serviceDomain);
        status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ,
                                      &name, &type, &domain, // Name, type, domain
                                      NULL, mDNSOpaque16fromIntVal(portNumber),
                                      text, textLen, // TXT data, length
                                      NULL, 0,      // Subtypes
                                      mDNSInterface_Any, // Interface ID
                                      RegistrationCallback, thisServ, 0); // Callback, context, flags
    }
    if (status == mStatus_NoError) {
        thisServ->serviceID = gServiceID;
        gServiceID += 1;

        thisServ->next = gServiceList;
        gServiceList = thisServ;

        if (gMDNSPlatformPosixVerboseLevel > 0) {
            fprintf(stderr,
                    "%s: Registered service %d, name \"%s\", type \"%s\", domain \"%s\",  port %ld\n",
                    gProgramName,
                    thisServ->serviceID,
                    richTextName,
                    serviceType,
                    serviceDomain,
                    portNumber);
        }
    } else {
        if (thisServ != NULL) {
            free(thisServ);
        }
    }
    return status;
}

static mDNSBool ReadALine(char *buf, size_t bufSize, FILE *fp, mDNSBool skipBlankLines)
{
    size_t len;
    mDNSBool readNextLine;

    do {
        readNextLine = mDNSfalse;

        if (fgets(buf, bufSize, fp) == NULL)
            return mDNSfalse;   // encountered EOF or an error condition

        // These first characters indicate a blank line.
        if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\r' || buf[0] == '\n') {
            if (!skipBlankLines)
                return mDNSfalse;
            readNextLine = mDNStrue;
        }
        // always skip comment lines
        if (buf[0] == '#')
            readNextLine = mDNStrue;

    } while (readNextLine);

    len = strlen( buf);
    if ( buf[len - 1] == '\r' || buf[len - 1] == '\n')
        buf[len - 1] = '\0';

    return mDNStrue;
}

static mStatus RegisterServicesInFile(const char *filePath)
{
    mStatus status = mStatus_NoError;
    FILE *      fp = fopen(filePath, "r");

    if (fp == NULL) {
        return mStatus_UnknownErr;
    }

    if (gMDNSPlatformPosixVerboseLevel > 1)
        fprintf(stderr, "Parsing %s for services\n", filePath);

    do {
        char nameBuf[256];
        char * name = nameBuf;
        char type[256];
        const char *dom = kDefaultServiceDomain;
        char rawText[1024];
        mDNSu8 text[sizeof(RDataBody)];
        unsigned int textLen = 0;
        char port[256];
        char *p;

        // Read the service name, type, port, and optional text record fields.
        // Skip blank lines while looking for the next service name.
        if (!ReadALine(name, sizeof(nameBuf), fp, mDNStrue))
            break;

        // Special case that allows service name to begin with a '#'
        // character by escaping it with a '\' to distiguish it from
        // a comment line.  Remove the leading '\' here before
        // registering the service.
        if (name[0] == '\\' && name[1] == '#')
            name++;

        if (gMDNSPlatformPosixVerboseLevel > 1)
            fprintf(stderr, "Service name: \"%s\"\n", name);

        // Don't skip blank lines in calls to ReadAline() after finding the
        // service name since the next blank line indicates the end
        // of this service record.
        if (!ReadALine(type, sizeof(type), fp, mDNSfalse))
            break;

        // see if a domain name is specified
        p = type;
        while (*p && *p != ' ' && *p != '\t') p++;
        if (*p) {
            *p = 0; // NULL terminate the <type>.<protocol> string
            // skip any leading whitespace before domain name
            p++;
            while (*p && (*p == ' ' || *p == '\t')) p++;
            if (*p)
                dom = p;
        }
        if (gMDNSPlatformPosixVerboseLevel > 1) {
            fprintf(stderr, "Service type: \"%s\"\n", type);
            fprintf(stderr, "Service domain: \"%s\"\n", dom);
        }

        if (!ReadALine(port, sizeof(port), fp, mDNSfalse))
            break;
        if (gMDNSPlatformPosixVerboseLevel > 1)
            fprintf(stderr, "Service port: %s\n", port);

        if (   !CheckThatRichTextNameIsUsable(name, mDNStrue)
               || !CheckThatServiceTypeIsUsable(type, mDNStrue)
               || !CheckThatPortNumberIsUsable(atol(port), mDNStrue))
            break;

        // read the TXT record fields
        while (1) {
            int len;
            if (!ReadALine(rawText, sizeof(rawText), fp, mDNSfalse)) break;
            if (gMDNSPlatformPosixVerboseLevel > 1)
                fprintf(stderr, "Text string: \"%s\"\n", rawText);
            len = strlen(rawText);
            if (len <= 255)
            {
                unsigned int newlen = textLen + 1 + len;
                if (len == 0 || newlen >= sizeof(text)) break;
                text[textLen] = len;
                mDNSPlatformMemCopy(text + textLen + 1, rawText, len);
                textLen = newlen;
            }
            else
                fprintf(stderr, "%s: TXT attribute too long for name = %s, type = %s, port = %s\n",
                        gProgramName, name, type, port);
        }

        status = RegisterOneService(name, type, dom, text, textLen, atol(port));
        if (status != mStatus_NoError) {
            // print error, but try to read and register other services in the file
            fprintf(stderr, "%s: Failed to register service, name \"%s\", type \"%s\", domain \"%s\", port %s\n",
                    gProgramName, name, type, dom, port);
        }

    } while (!feof(fp));

    if (!feof(fp)) {
        fprintf(stderr, "%s: Error reading service file %s\n", gProgramName, filePath);
        status = mStatus_UnknownErr;
    }

    assert(0 == fclose(fp));

    return status;
}

static mStatus RegisterOurServices(void)
{
    mStatus status;

    status = mStatus_NoError;
    if (gServiceName[0] != 0) {
        status = RegisterOneService(gServiceName,
                                    gServiceType,
                                    gServiceDomain,
                                    gServiceText, gServiceTextLen,
                                    gPortNumber);
    }
    if (status == mStatus_NoError && gServiceFile[0] != 0) {
        status = RegisterServicesInFile(gServiceFile);
    }
    return status;
}

static void DeregisterOurServices(void)
{
    PosixService *thisServ;
    int thisServID;

    while (gServiceList != NULL) {
        thisServ = gServiceList;
        gServiceList = thisServ->next;

        thisServID = thisServ->serviceID;

        mDNS_DeregisterService(&mDNSStorage, &thisServ->coreServ);

        if (gMDNSPlatformPosixVerboseLevel > 0) {
            fprintf(stderr,
                    "%s: Deregistered service %d\n",
                    gProgramName,
                    thisServ->serviceID);
        }
    }
}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark **** Main
#endif

int main(int argc, char **argv)
{
    mStatus status;
    int result;

    // Parse our command line arguments.  This won't come back if there's an error.

    ParseArguments(argc, argv);

    // If we're told to run as a daemon, then do that straight away.
    // Note that we don't treat the inability to create our PID
    // file as an error.  Also note that we assign getpid to a long
    // because printf has no format specified for pid_t.

    if (gDaemon) {
        int result;
        if (gMDNSPlatformPosixVerboseLevel > 0) {
            fprintf(stderr, "%s: Starting in daemon mode\n", gProgramName);
        }
        result = daemon(0,0);
        if (result == 0) {
            FILE *fp;
            int junk;

            fp = fopen(gPIDFile, "w");
            if (fp != NULL) {
                fprintf(fp, "%ld\n", (long) getpid());
                junk = fclose(fp);
                assert(junk == 0);
            }
        } else {
            fprintf(stderr, "%s: Could not run as daemon - exiting\n", gProgramName);
            exit(result);
        }
    } else {
        if (gMDNSPlatformPosixVerboseLevel > 0) {
            fprintf(stderr, "%s: Starting in foreground mode, PID %ld\n", gProgramName, (long) getpid());
        }
    }

    status = mDNS_Init(&mDNSStorage, &PlatformStorage,
                       mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
                       mDNS_Init_AdvertiseLocalAddresses,
                       mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
    if (status != mStatus_NoError) return(2);

    status = RegisterOurServices();
    if (status != mStatus_NoError) return(2);

    signal(SIGHUP,  HandleSigHup);      // SIGHUP has to be sent by kill -HUP <pid>
    signal(SIGINT,  HandleSigInt);      // SIGINT is what you get for a Ctrl-C
    signal(SIGQUIT, HandleSigQuit);     // SIGQUIT is what you get for a Ctrl-\ (indeed)
    signal(SIGUSR1, HandleSigUsr1);     // SIGUSR1 has to be sent by kill -USR1 <pid>

    while (!gStopNow)
    {
        int nfds = 0;
        fd_set readfds;
        struct timeval timeout;
        int result;

        // 1. Set up the fd_set as usual here.
        // This example client has no file descriptors of its own,
        // but a real application would call FD_SET to add them to the set here
        FD_ZERO(&readfds);

        // 2. Set up the timeout.
        // This example client has no other work it needs to be doing,
        // so we set an effectively infinite timeout
        timeout.tv_sec = 0x3FFFFFFF;
        timeout.tv_usec = 0;

        // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout
        mDNSPosixGetFDSet(&mDNSStorage, &nfds, &readfds, &timeout);

        // 4. Call select as normal
        verbosedebugf("select(%d, %d.%06d)", nfds, timeout.tv_sec, timeout.tv_usec);
        result = select(nfds, &readfds, NULL, NULL, &timeout);

        if (result < 0)
        {
            verbosedebugf("select() returned %d errno %d", result, errno);
            if (errno != EINTR) gStopNow = mDNStrue;
            else
            {
                if (gReceivedSigUsr1)
                {
                    gReceivedSigUsr1 = mDNSfalse;
                    gMDNSPlatformPosixVerboseLevel += 1;
                    if (gMDNSPlatformPosixVerboseLevel > 2)
                        gMDNSPlatformPosixVerboseLevel = 0;
                    if ( gMDNSPlatformPosixVerboseLevel > 0 )
                        fprintf(stderr, "\nVerbose level %d\n", gMDNSPlatformPosixVerboseLevel);
                }
                if (gReceivedSigHup)
                {
                    if (gMDNSPlatformPosixVerboseLevel > 0)
                        fprintf(stderr, "\nSIGHUP\n");
                    gReceivedSigHup = mDNSfalse;
                    DeregisterOurServices();
                    status = mDNSPlatformPosixRefreshInterfaceList(&mDNSStorage);
                    if (status != mStatus_NoError) break;
                    status = RegisterOurServices();
                    if (status != mStatus_NoError) break;
                }
            }
        }
        else
        {
            // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work
            mDNSPosixProcessFDSet(&mDNSStorage, &readfds);

            // 6. This example client has no other work it needs to be doing,
            // but a real client would do its work here
            // ... (do work) ...
        }
    }

    debugf("Exiting");

    DeregisterOurServices();
    mDNS_Close(&mDNSStorage);

    if (status == mStatus_NoError) {
        result = 0;
    } else {
        result = 2;
    }
    if ( (result != 0) || (gMDNSPlatformPosixVerboseLevel > 0) ) {
        fprintf(stderr, "%s: Finished with status %d, result %d\n", gProgramName, (int)status, result);
    }

    return result;
}