root/usr.sbin/unbound/testcode/lock_verify.c
/*
 * testcode/lock_verify.c - verifier program for lock traces, checks order.
 *
 * Copyright (c) 2007, NLnet Labs. All rights reserved.
 *
 * This software is open source.
 * 
 * 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 NLNET LABS 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
 * HOLDER 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.
 */

/**
 * \file
 *
 * This file checks the lock traces generated by checklock.c.
 * Checks if locks are consistently locked in the same order.
 * If not, this can lead to deadlock if threads execute the different
 * ordering at the same time.
 * 
 */

#include "config.h"
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include "util/log.h"
#include "util/rbtree.h"
#include "util/locks.h"
#include "util/fptr_wlist.h"

/* --- data structures --- */
struct lock_ref;

/** keep track of lock id in lock-verify application 
 * Also defined in smallapp/worker_cb.c for fptr_wlist encapsulation 
 * breakage (the security tests break encapsulation for this test app) */
struct order_id {
        /** the thread id that created it */
        int thr;
        /** the instance number of creation */
        int instance;
};

/** a lock */
struct order_lock {
        /** rbnode in all tree */
        rbnode_type node;
        /** lock id */
        struct order_id id;
        /** the creation file */
        char* create_file;
        /** creation line */
        int create_line;
        /** set of all locks that are smaller than this one (locked earlier) */
        rbtree_type* smaller;
        /** during depthfirstsearch, this is a linked list of the stack 
         * of locks. points to the next lock bigger than this one. */
        struct lock_ref* dfs_next;
        /** if lock has been visited (all smaller locks have been compared to
         * this lock), only need to compare this with all unvisited(bigger) 
         * locks */
        int visited;
};

/** reference to a lock in a rbtree set */
struct lock_ref {
        /** rbnode, key is an order_id ptr */
        rbnode_type node;
        /** the lock referenced */
        struct order_lock* lock;
        /** why is this ref */
        char* file;
        /** line number */
        int line;
};

/** count of errors detected */
static int errors_detected = 0;
/** verbose? */
static int verb = 0;

/** print program usage help */
static void
usage(void)
{
        printf("lock_verify <trace files>\n");
}

/** read header entry. 
 * @param in: file to read header of.
 * @return: False if it does not belong to the rest. */
static int 
read_header(FILE* in)
{
        time_t t;
        pid_t p;
        int thrno;
        static int have_values = 0;
        static time_t the_time;
        static pid_t the_pid;
        static int threads[256];

        if(fread(&t, sizeof(t), 1, in) != 1 ||  
                fread(&thrno, sizeof(thrno), 1, in) != 1 ||
                fread(&p, sizeof(p), 1, in) != 1) {
                fatal_exit("fread failed");
        }
        /* check these values are sorta OK */
        if(!have_values) {
                the_time = t;
                the_pid = p;
                memset(threads, 0, 256*sizeof(int));
                if(thrno >= 256) {
                        fatal_exit("Thread number too big. %d", thrno);
                }
                threads[thrno] = 1;
                have_values = 1;
                printf(" trace %d from pid %u on %s", thrno, 
                        (unsigned)p, ctime(&t));
        } else {
                if(the_pid != p) {
                        printf(" has pid %u, not %u. Skipped.\n",
                                (unsigned)p, (unsigned)the_pid);
                        return 0;
                }
                if(threads[thrno])
                        fatal_exit("same threadno in two files");
                threads[thrno] = 1;
                if( abs((int)(the_time - t)) > 3600)
                        fatal_exit("input files from different times: %u %u",
                                (unsigned)the_time, (unsigned)t);
                printf(" trace of thread %u:%d\n", (unsigned)p, thrno);
        }
        return 1;
}

/** max length of strings: filenames and function names. */
#define STRMAX 1024
/** read a string from file, false on error */
static int readup_str(char** str, FILE* in)
{
        char buf[STRMAX];
        int len = 0;
        int c;
        /* ends in zero */
        while( (c = fgetc(in)) != 0) {
                if(c == EOF)
                        fatal_exit("eof in readstr, file too short");
                buf[len++] = c;
                if(len == STRMAX) {
                        fatal_exit("string too long, bad file format");
                }
        }
        buf[len] = 0;
        *str = strdup(buf);
        if(!*str)
                fatal_exit("strdup failed: out of memory");
        return 1;
}

/** read creation entry */
static void read_create(rbtree_type* all, FILE* in)
{
        struct order_lock* o = calloc(1, sizeof(struct order_lock));
        if(!o) fatal_exit("malloc failure");
        if(fread(&o->id.thr, sizeof(int), 1, in) != 1 ||        
           fread(&o->id.instance, sizeof(int), 1, in) != 1 ||   
           !readup_str(&o->create_file, in) ||
           fread(&o->create_line, sizeof(int), 1, in) != 1)
                fatal_exit("fread failed");
        o->smaller = rbtree_create(order_lock_cmp);
        o->node.key = &o->id;
        if(!rbtree_insert(all, &o->node)) {
                /* already inserted */
                struct order_lock* a = (struct order_lock*)rbtree_search(all, 
                        &o->id);
                log_assert(a);
                a->create_file = o->create_file;
                a->create_line = o->create_line;
                free(o->smaller);
                free(o);
                o = a;
        }
        if(verb) printf("read create %u %u %s %d\n", 
                (unsigned)o->id.thr, (unsigned)o->id.instance,
                o->create_file, o->create_line);
}

/** insert lock entry (empty) into list */
static struct order_lock* 
insert_lock(rbtree_type* all, struct order_id* id)
{
        struct order_lock* o = calloc(1, sizeof(struct order_lock));
        if(!o) fatal_exit("malloc failure");
        o->smaller = rbtree_create(order_lock_cmp);
        o->id = *id;
        o->node.key = &o->id;
        if(!rbtree_insert(all, &o->node))
                fatal_exit("insert fail should not happen");
        return o;
}

/** read lock entry */
static void read_lock(rbtree_type* all, FILE* in, int val)
{
        struct order_id prev_id, now_id;
        struct lock_ref* ref;
        struct order_lock* prev, *now;
        ref = (struct lock_ref*)calloc(1, sizeof(struct lock_ref));
        if(!ref) fatal_exit("malloc failure");
        prev_id.thr = val;
        if(fread(&prev_id.instance, sizeof(int), 1, in) != 1 || 
           fread(&now_id.thr, sizeof(int), 1, in) != 1 ||       
           fread(&now_id.instance, sizeof(int), 1, in) != 1 ||  
           !readup_str(&ref->file, in) ||
           fread(&ref->line, sizeof(int), 1, in) != 1)
                fatal_exit("fread failed");
        if(verb) printf("read lock %u %u %u %u %s %d\n", 
                (unsigned)prev_id.thr, (unsigned)prev_id.instance,
                (unsigned)now_id.thr, (unsigned)now_id.instance,
                ref->file, ref->line);
        /* find the two locks involved */
        prev = (struct order_lock*)rbtree_search(all, &prev_id);
        now = (struct order_lock*)rbtree_search(all, &now_id);
        /* if not there - insert 'em */
        if(!prev) prev = insert_lock(all, &prev_id);
        if(!now) now = insert_lock(all, &now_id);
        ref->lock = prev;
        ref->node.key = &prev->id;
        if(!rbtree_insert(now->smaller, &ref->node)) {
                free(ref->file);
                free(ref);
        }
}

/** read input file */
static void readinput(rbtree_type* all, char* file)
{
        FILE *in = fopen(file, "r");
        int fst;
        if(!in) {
                perror(file);
                exit(1);
        }
        printf("file %s", file);
        if(!read_header(in)) {
                fclose(in);
                return;
        }
        while(fread(&fst, sizeof(fst), 1, in) == 1) {
                if(fst == -1)
                        read_create(all, in);
                else    read_lock(all, in, fst);
        }
        fclose(in);
}

/** print cycle message */
static void found_cycle(struct lock_ref* visit, int level)
{
        struct lock_ref* p;
        int i = 0;
        errors_detected++;
        printf("Found inconsistent locking order of length %d\n", level);
        printf("for lock %d %d created %s %d\n", 
                visit->lock->id.thr, visit->lock->id.instance,
                visit->lock->create_file, visit->lock->create_line);
        printf("sequence is:\n");
        p = visit;
        while(p) {
                struct order_lock* next = 
                        p->lock->dfs_next?p->lock->dfs_next->lock:visit->lock;
                printf("[%d] is locked at line %s %d before lock %d %d\n",
                        i, p->file, p->line, next->id.thr, next->id.instance);
                printf("[%d] lock %d %d is created at %s %d\n",
                        i, next->id.thr, next->id.instance,
                        next->create_file, next->create_line); 
                i++;
                p = p->lock->dfs_next;
                if(p && p->lock == visit->lock)
                        break;
        }
}

/** Detect cycle by comparing visited now with all (unvisited) bigger nodes */
static int detect_cycle(struct lock_ref* visit, struct lock_ref* from)
{
        struct lock_ref* p = from;
        while(p) {
                if(p->lock == visit->lock)
                        return 1;
                p = p->lock->dfs_next;
        }
        return 0;
}

/** recursive function to depth first search for cycles.
 * @param visit: the lock visited at this step.
 *      its dfs_next pointer gives the visited lock up in recursion.
 *      same as lookfor at level 0.
 * @param level: depth of recursion. 0 is start.
 * @param from: search for matches from unvisited node upwards.
 */
static void search_cycle(struct lock_ref* visit, int level, 
        struct lock_ref* from)
{
        struct lock_ref* ref;
        /* check for cycle */
        if(detect_cycle(visit, from) && level != 0) {
                found_cycle(visit, level);
                fatal_exit("found lock order cycle");
        }
        /* recurse */
        if(!visit->lock->visited)
                from = visit;
        if(verb > 1) fprintf(stderr, "[%d] visit lock %u %u %s %d\n", level,
                        (unsigned)visit->lock->id.thr, 
                        (unsigned)visit->lock->id.instance,
                        visit->lock->create_file, visit->lock->create_line);
        RBTREE_FOR(ref, struct lock_ref*, visit->lock->smaller) {
                ref->lock->dfs_next = visit;
                search_cycle(ref, level+1, from);
        }
        visit->lock->visited = 1;
}

/** Check ordering of one lock */
static void check_order_lock(struct order_lock* lock)
{
        struct lock_ref start;
        if(lock->visited) return;

        start.node.key = &lock->id;
        start.lock = lock;
        start.file = lock->create_file;
        start.line = lock->create_line;

        if(!lock->create_file)
                log_err("lock %u %u does not have create info",
                        (unsigned)lock->id.thr, (unsigned)lock->id.instance);

        /* depth first search to find cycle with this lock at head */
        lock->dfs_next = NULL;
        search_cycle(&start, 0, &start);
}

/** Check ordering of locks */
static void check_order(rbtree_type* all_locks)
{
        /* check each lock */
        struct order_lock* lock;
        int i=0;
        RBTREE_FOR(lock, struct order_lock*, all_locks) {
                if(verb)
                    printf("[%d/%d] Checking lock %d %d %s %d\n",
                        i, (int)all_locks->count,
                        lock->id.thr, lock->id.instance, 
                        lock->create_file, lock->create_line);
                else if (i % ((all_locks->count/75)<1?1:all_locks->count/75) 
                        == 0) 
                    fprintf(stderr, ".");
                i++;
                check_order_lock(lock);
        }
        fprintf(stderr, "\n");
}

/** delete lock ref */
static void dellockref(rbnode_type* node, void* ATTR_UNUSED(arg))
{
        struct lock_ref* o = (struct lock_ref*)node;
        if(!o) return;
        free(o->file);
        free(o);
}

/** delete lock node */
static void delnode(rbnode_type* node, void* ATTR_UNUSED(arg))
{
        struct order_lock* o = (struct order_lock*)node;
        if(!o) return;
        free(o->create_file);
        if(o->smaller) {
                traverse_postorder(o->smaller, &dellockref, NULL);
                free(o->smaller);
        }
        free(o);
}

/** delete allocated memory */
static void locks_free(rbtree_type* all_locks)
{
        if(!all_locks)
                return;
        traverse_postorder(all_locks, &delnode, NULL);
        free(all_locks);
}

/** main program to verify all traces passed */
int
main(int argc, char* argv[])
{
        rbtree_type* all_locks;
        int i;
        time_t starttime = time(NULL);
#ifdef USE_THREAD_DEBUG
        /* do not overwrite the ublocktrace files with the ones generated
         * by this program (i.e. when the log code creates a lock) */
        check_locking_order = 0;
#endif
        if(argc <= 1) {
                usage();
                return 1;
        }
        checklock_start();
        log_init(NULL, 0, NULL);
        log_ident_set("lock-verify");
        /* init */
        all_locks = rbtree_create(order_lock_cmp);
        errors_detected = 0;

        /* read the input files */
        for(i=1; i<argc; i++) {
                readinput(all_locks, argv[i]);
        }

        /* check ordering */
        check_order(all_locks);

        /* do not free a thing, OS will do it */
        printf("checked %d locks in %d seconds with %d errors.\n", 
                (int)all_locks->count, (int)(time(NULL)-starttime),
                errors_detected);
        locks_free(all_locks);
        if(errors_detected) return 1;
        return 0;
}