/** BEGIN COPYRIGHT BLOCK * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; version 2 of the License. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /* dnp_simulation.c - this file varifies the correctness of dnp algorithm by generating random sequences of operations, applying the algorithm and outputing the result usage: dnp_sim [-h] [-n ] [-v] [-f ] -h - print usage information. -n - how many simulations to perform; default - 1. -v - verbose mode (prints full entry state after each operation execution) -f - file where results are stored; by default results are printed to the screen. -o - file that contains operation sequence to execute; by default, random sequence is generated. */ #include #include #include #include #include #include #define MAX_OPS 12 /* maximum number of operations in a simulation */ #define MAX_VALS 10 /* maximum number of values is entry or dn */ #define NOT_PRESENT -1 /* data types */ typedef struct value_state { int value_index; /* value */ int presense_csn; /* last time at which we know the value was present */ int distinguished_csn; /* last time at which we know the value was distinguished */ int delete_csn; /* last attempt to delete this value */ int non_distinguished_csns [MAX_OPS];/* list of times at which value became non-distinguished */ int present; /* flag that tells whether the value iscurrently present */ } Value_State; typedef struct entry_state { int dn_index; int dn_csn; Value_State values[MAX_VALS]; /* values of the attribute */ int attr_delete_csn; /* last attempt to delete the entire attribute */ } Entry_State; typedef enum { OP_ADD_VALUE, OP_RENAME_ENTRY, OP_DELETE_VALUE, OP_DELETE_ATTR, OP_END } Operation_Type; typedef struct operation { Operation_Type type; /* operation type */ int csn; /* operation type */ int value_index; /* value to add, remove or rename from */ int old_dn_index; /* new dn - rename only */ }Operation; typedef struct simulator_params { int runs; /* number of runs */ FILE *fout; /* output file */ int value_count; /* number of values */ int verbose; /* verbose mode */ Operation *ops; /* operation sequence to apply */ int op_count; }Simulator_Params; /* gloabl data */ Simulator_Params sim; char *g_values[] = { "v", "u", "w", NULL }; /* forward declarations */ /* initialization */ void process_cmd (int argc, char **argv); void set_default_sim_params (); int count_values (); void parse_operations_file (char *name); void parse_operation (char *line, int pos); int value2index (char *value); void print_usage (); /* simulation run */ void run_simulation (); void generate_operations (Operation **ops, int *op_count); void generate_operation (Operation *op, int csn, int *last_dn_index); int* generate_operation_order (int op_count, int seq_num); void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); void init_entry_state (Entry_State *entry); void init_value_state (Value_State *val, int seq_num); void apply_operation (Entry_State *entry, Operation *op); void free_operations (Operation **ops); int ** new_perm_table (int op_count); void free_perm_table (int ***perm_table, int op_count); int get_perm_count (int op_count); void generate_perm_table (int *elements, int element_count, int static_part, int **perm_table); void apply_add_operation (Entry_State *entry, Operation *op); void apply_value_delete_operation (Entry_State *entry, Operation *op); void apply_attr_delete_operation (Entry_State *entry, Operation *op); void apply_rename_operation (Entry_State *entry, Operation *op); void make_value_distinguished (int op_csn, Entry_State *entry, int value_index); void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index); void purge_value_state (Value_State *value); void purge_non_distinguished_csns (Value_State *value); void resolve_value_state (Entry_State *entry, int value_index); int value_distinguished_at_delete (Value_State *value, int attr_delete_csn); int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); /* data tracing */ void dump_operations (Operation *ops, int op_count, int *order); void dump_operation (Operation *op); void dump_perm_table (int **perm_table, int op_count); void dump_entry_state (Entry_State *entry); void dump_value_state (Value_State *value); void dump_list (int *list); /* misc functions */ int max_val (int a, int b); int is_list_empty (int *list); int min_list_val (int *list); int list_equal (int *list1, int *list2); int main (int argc, char **argv) { int i; process_cmd (argc, argv); for (i = 0; i < sim.runs; i++) { fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); run_simulation (); fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); } if (sim.fout != stdout) fclose (sim.fout); return 0; } void process_cmd (int argc, char **argv) { int i; set_default_sim_params (); if (argc == 1) { return; } if (strcmp (argv[1], "-h") == 0) /* print help */ { print_usage (); exit (0); } i = 1; while (i < argc) { if (strcmp (argv[i], "-v") == 0) /* verbose mode */ { sim.verbose = 1; i ++; } else if (strcmp (argv[i], "-n") == 0) { if (i < argc - 1) { int runs = atoi (argv[i + 1]); if (runs > 0) sim.runs = runs; i+=2; } else { /* ONREPL print warning */ i++; } } else if (strcmp (argv[i], "-f") == 0) /* output file */ { if (i < argc - 1) { FILE *f = fopen (argv[i + 1], "w"); if (f == 0) { printf ("failed to open output file; error - %s, using stdout\n", strerror(errno)); } else { /* ONREPL print warning */ sim.fout = f; } i += 2; } else i++; } else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ { if (i < argc - 1) { parse_operations_file (argv[i+1]); i += 2; } else { /* ONREPL print warning */ i ++; } } else /* unknown option */ { printf ("unknown option - %s; ignored\n", argv[i]); i ++; } } } void set_default_sim_params () { memset (&sim, 0, sizeof (sim)); sim.runs = 1; sim.fout = stdout; sim.value_count = count_values (); } /* file format: add delete delete attribute rename to */ void parse_operations_file (char *name) { FILE *file = fopen (name, "r"); char line [256]; int i; if (file == NULL) { printf ("failed to open operations file %s: error = %d\n", name, errno); print_usage (); exit (1); } i = 0; while (fgets (line, sizeof (line), file)) { if (i == 0) { /* read operation count */ sim.op_count = atoi (line); if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) { printf ("invalid operation count - %d; value must be between 1 and %d\n", sim.op_count, MAX_OPS/2); print_usage (); exit (1); } else { sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); } } else { if (strlen (line) == 0) /* skip empty lines */ continue; parse_operation (line, i); } i ++; } } void parse_operation (char *line, int i) { sim.ops [i - 1].csn = i; if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; if (strncmp (line, "add", 3) == 0) { sim.ops [i - 1].type = OP_ADD_VALUE; sim.ops [i - 1].value_index = value2index (&line[4]); } else if (strncmp (line, "delete attribute", 16) == 0) { sim.ops [i - 1].type = OP_DELETE_ATTR; } else if (strncmp (line, "delete", 6) == 0) { sim.ops [i - 1].type = OP_DELETE_VALUE; sim.ops [i - 1].value_index = value2index (&line[7]); } else if (strncmp (line, "rename", 6) == 0) { char *tok; sim.ops [i - 1].type = OP_RENAME_ENTRY; /* strtok() is not MT safe, but it is okay to call here because this is a program test */ tok = strtok (&line[7], " "); sim.ops [i - 1].old_dn_index = value2index (tok); /* skip to */ tok = strtok (NULL, " "); tok = strtok (NULL, " "); sim.ops [i - 1].value_index = value2index (tok); } else { /* invalid line */ printf ("invalid operation: %s\n", line); exit (1); } } int value2index (char *value) { int i; for (i = 0; i < sim.value_count; i++) { if (strcmp (g_values[i], value) == 0) return i; } return -1; } void print_usage () { printf ("usage: dnp_sim [-h] [-n ] [-v] [-f ]\n" "\t-h - print usage information\n" "\t-n ; default - 1\n" "\t-v - verbose mode\n" "\t-f - by default, results are printed to the screen\n" "\t-o - file that contains operation sequence to execute;\n" "\tby default, random sequence is generated.\n"); } int count_values () { int i; for (i = 0; g_values[i]; i++); return i; } void run_simulation () { int *order; int i; int perm_count; Entry_State entry_first, entry_current; int error = 0; init_entry_state (&entry_first); fprintf (sim.fout, "initial entry state :\n"); dump_entry_state (&entry_first); if (sim.ops == NULL) { generate_operations (&sim.ops, &sim.op_count); } fprintf (sim.fout, "initial operation set:\n"); dump_operations (sim.ops, sim.op_count, NULL/* order */); perm_count = get_perm_count (sim.op_count); for (i = 0; i < perm_count; i++) { fprintf (sim.fout, "--------------------------------\n"); fprintf (sim.fout, "simulation run %d\n", i + 1); fprintf (sim.fout, "--------------------------------\n"); order = generate_operation_order (sim.op_count, i); if (i == 0) apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); else { apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); error |= compare_entry_state (&entry_first, &entry_current, i + 1); } } switch (error) { case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); break; case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " "the exact state does not match\n"); break; case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); break; } free_operations (&sim.ops); } void generate_operations (Operation **ops, int *op_count) { int i; int last_dn_index = 0; /* generate number operations in the sequence */ *op_count = slapi_rand () % (MAX_OPS / 2) + 1; *ops = (Operation *)malloc (*op_count * sizeof (Operation)); for (i = 0; i < *op_count; i ++) { generate_operation (&((*ops)[i]), i + 1, &last_dn_index); } } void generate_operation (Operation *op, int csn, int *last_dn_index) { /* generate operation type */ op->type = slapi_rand () % OP_END; /* generate value to which operation applies */ op->value_index = slapi_rand () % sim.value_count; op->csn = csn; /* generate new distinguished value */ if (op->type == OP_RENAME_ENTRY) { op->old_dn_index = *last_dn_index; while (op->value_index == op->old_dn_index) op->value_index = slapi_rand () % sim.value_count; *last_dn_index = op->value_index; } } int* generate_operation_order (int op_count, int seq_num) { static int **perm_table = NULL; /* first request - generate pemutation table */ if (seq_num == 0) { int elements [MAX_OPS]; int i; if (perm_table) free_perm_table (&perm_table, op_count); perm_table = new_perm_table (op_count); for (i = 0; i < op_count; i++) elements [i] = i; generate_perm_table (elements, op_count, 0 /* static part */, perm_table); /* dump_perm_table (perm_table, op_count);*/ } return perm_table [seq_num]; } void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) { int i; init_entry_state (entry); if (!sim.verbose) { if (!sim.verbose) { fprintf (sim.fout, "operation_sequence for this run:\n"); dump_operations (ops, op_count, order); } } for (i = 0; i < op_count; i++) { apply_operation (entry, &(ops [order[i]])); } if (!sim.verbose) { fprintf (sim.fout, "final entry state :\n"); dump_entry_state (entry); } } void init_entry_state (Entry_State *entry) { int i; memset (entry, 0, sizeof (*entry)); entry->attr_delete_csn = NOT_PRESENT; for (i = 0; i < sim.value_count; i++) { init_value_state (&(entry->values[i]), i); } } void init_value_state (Value_State *val, int seq_num) { memset (val, 0, sizeof (*val)); val->value_index = seq_num; val->present = 1; val->delete_csn = NOT_PRESENT; if (seq_num > 0) /* only first value is distinguished */ val->distinguished_csn = -1; } void apply_operation (Entry_State *entry, Operation *op) { switch (op->type) { case OP_ADD_VALUE: apply_add_operation (entry, op); break; case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); break; case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); break; case OP_RENAME_ENTRY: apply_rename_operation (entry, op); break; } if (sim.verbose) { fprintf (sim.fout, "operation: "); dump_operation (op); fprintf (sim.fout, "\n"); dump_entry_state (entry); } } void free_operations (Operation **ops) { free (*ops); *ops = NULL; } int **new_perm_table (int op_count) { int i; int **perm_table; int perm_count = get_perm_count (op_count); perm_table = (int**)malloc (perm_count * sizeof (int*)); for (i = 0; i < perm_count; i ++) perm_table [i] = (int*) malloc (op_count * sizeof (int)); return perm_table; } void free_perm_table (int ***perm_table, int op_count) { int i; int perm_count = get_perm_count (op_count); for (i = 0; i < perm_count; i ++) free ((*perm_table)[i]); free (*perm_table); *perm_table = NULL; } void generate_perm_table (int *elements, int element_count, int static_part, int **perm_table) { int i; int elements_copy [MAX_OPS]; int start_pos; if (element_count - 1 == static_part) { memcpy (*perm_table, elements, element_count * sizeof (int)); return; } start_pos = 0; for (i = 0; i < element_count - static_part; i ++) { memcpy (elements_copy, elements, element_count * sizeof (int)); elements_copy [static_part] = elements [static_part + i]; elements_copy [static_part + i] = elements [static_part]; generate_perm_table (elements_copy, element_count, static_part + 1, &perm_table [start_pos]); start_pos += get_perm_count (element_count - static_part - 1); } } int get_perm_count (int op_count) { int i; int perm_count = 1; for (i = 2; i <= op_count; i ++) perm_count *= i; return perm_count; } void apply_add_operation (Entry_State *entry, Operation *op) { if (entry->values[op->value_index].presense_csn < op->csn) { entry->values[op->value_index].presense_csn = op->csn; entry->values[op->value_index].present = 1; resolve_value_state (entry, op->value_index); } } void apply_value_delete_operation (Entry_State *entry, Operation *op) { if (entry->values[op->value_index].delete_csn < op->csn) { entry->values[op->value_index].delete_csn = op->csn; resolve_value_state (entry, op->value_index); } } void apply_attr_delete_operation (Entry_State *entry, Operation *op) { int i; if (entry->attr_delete_csn < op->csn) { entry->attr_delete_csn = op->csn; for (i = 0; i < sim.value_count; i++) { resolve_value_state (entry, i); } } } void apply_rename_operation (Entry_State *entry, Operation *op) { if (entry->dn_csn < op->csn) { entry->dn_index = op->value_index; entry->dn_csn = op->csn; } make_value_non_distinguished (op->csn, entry, op->old_dn_index); make_value_distinguished (op->csn, entry, op->value_index); } void make_value_distinguished (int op_csn, Entry_State *entry, int value_index) { Value_State *value = &(entry->values[value_index]); if (value->distinguished_csn < op_csn) { value->distinguished_csn = op_csn; if (value->presense_csn < op_csn) { value->present = 1; value->presense_csn = op_csn; } resolve_value_state (entry, value_index); } } void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index) { int i = 0; int index; Value_State *value = &(entry->values[value_index]); if (op_csn < value->distinguished_csn) return; /* insert into sorted list */ while (value->non_distinguished_csns[i] && value->non_distinguished_csns[i] < op_csn) i++; if (value->non_distinguished_csns[i] == 0) value->non_distinguished_csns[i] = op_csn; else { index = i; while (value->non_distinguished_csns[i]) i++; memcpy (&(value->non_distinguished_csns[index + 1]), &(value->non_distinguished_csns[index]), (i - index) * sizeof (int)); value->non_distinguished_csns[index] = op_csn; } resolve_value_state (entry, value_index); } void purge_value_state (Value_State *value) { /* value state information can be purged once a value was readed/made distinguished because at that point we know that the value existed/was distinguished */ purge_non_distinguished_csns (value); if (value->delete_csn < max_val (value->distinguished_csn, value->presense_csn)) value->delete_csn = NOT_PRESENT; } void purge_non_distinguished_csns (Value_State *value) { int i = 0; int index; while (value->non_distinguished_csns[i] && value->non_distinguished_csns[i] < value->distinguished_csn) i ++; if (i > 0) { index = i-1; while (value->non_distinguished_csns[i]) i ++; if (i > index + 1) { memcpy (value->non_distinguished_csns, &value->non_distinguished_csns[index+1], (i - index - 1) * sizeof (int)); memset (&(value->non_distinguished_csns[index+1]), 0, sizeof (int) * (i - index - 1)); } else { memset (value->non_distinguished_csns, 0, sizeof (int) * i); } } } int is_list_empty (int *list) { return (list[0] == 0); } int min_list_val (int *list) { return (list [0]); } void resolve_value_state (Entry_State *entry, int value_index) { Value_State *value = &(entry->values[value_index]); purge_value_state (value); /* no deletes that effect the state */ if (max_val (value->delete_csn, entry->attr_delete_csn) < max_val (value->distinguished_csn, value->presense_csn)) return; if (value->present) /* check if it should be removed based on the current state */ { if (!value_distinguished_at_delete (value, entry->attr_delete_csn)) { /* note that we keep presence csn because we might have to restore the value in the future */ value->present = 0; } } else /* not present - check if it should be restored */ { if (value_distinguished_at_delete (value, entry->attr_delete_csn)) { value->present = 1; } } } /* Note we can't trim distinguished_csn (even during regular trimming) because in some cases we would not be able to figure out whether a value was distinguished or not at the time of deletion. Example 1: Example2: csn order operation csn order operation 1 1 make V distinguished 1 1 make V distinguished 3 3 delete V 2 2 make V non distinguished 4 2 make V non-distinguished 3 4 delete V 4 3 make V non distinguished (on another server) if the csns up to 2 were triimed, when delete operation is received, the state is exactly the same in both examples but in example one delete should not go through while in example 2 it should */ int value_distinguished_at_delete (Value_State *value, int attr_delete_csn) { if (value->distinguished_csn >= 0 && (is_list_empty (value->non_distinguished_csns) || min_list_val (value->non_distinguished_csns) > max_val (value->delete_csn, attr_delete_csn))) return 1; else return 0; } int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) { int j; int error = 0; /* first - quick check for present / not present */ for (j = 0; j < sim.value_count; j++) { if (entry1->values[j].present != entry2->values[j].present) { fprintf (sim.fout, "value %s is %s present in the first run but %s present in the %d run\n", g_values[j], entry1->values[j].present ? "" : "not", entry2->values[j].present ? "" : "not", run); error = 1; } } if (error) return 3; /* compare value state */ error = 0; if (entry1->attr_delete_csn != entry2->attr_delete_csn) { fprintf (sim.fout, "attribute delete csn is %d for run 1 " "but is %d for run %d\n", entry1->attr_delete_csn, entry2->attr_delete_csn, run); error = 1; } for (j = 0; j < sim.value_count; j++) { if (entry1->values[j].presense_csn != entry2->values[j].presense_csn) { fprintf (sim.fout, "presence csn for value %s is %d in run 1 " "but is %d in run %d\n", g_values[j], entry1->values[j].presense_csn, entry2->values[j].presense_csn, run); error = 1; } if (entry1->values[j].distinguished_csn != entry2->values[j].distinguished_csn) { fprintf (sim.fout, "distinguished csn for value %s is %d in run 1 " "but is %d in run %d\n", g_values[j], entry1->values[j].distinguished_csn, entry2->values[j].distinguished_csn, run); error = 1; } if (entry1->values[j].delete_csn != entry2->values[j].delete_csn) { fprintf (sim.fout, "delete csn for value %s is %d in run 1 " "but is %d in run %d\n", g_values[j], entry1->values[j].delete_csn, entry2->values[j].delete_csn, run); error = 1; } if (!list_equal (entry1->values[j].non_distinguished_csns, entry2->values[j].non_distinguished_csns)) { fprintf (sim.fout, "pending list mismatch for valye %s in runs 1 and %d\n", g_values[j], run); dump_list (entry1->values[j].non_distinguished_csns); dump_list (entry2->values[j].non_distinguished_csns); } } if (error != 0) { return 1; } else return 0; } void dump_operations (Operation *ops, int op_count, int *order) { int index; int i; for (i = 0; i < op_count; i ++) { if (order == NULL) /* current order */ index = i; else index = order [i]; dump_operation (&ops[index]); } fprintf (sim.fout, "\n"); } void dump_operation (Operation *op) { switch (op->type) { case OP_ADD_VALUE: fprintf (sim.fout, "\t%d add value %s\n", op->csn, g_values [op->value_index]); break; case OP_DELETE_VALUE: fprintf (sim.fout, "\t%d delete value %s\n", op->csn, g_values [op->value_index]); break; case OP_DELETE_ATTR: fprintf (sim.fout, "\t%d delete attribute\n", op->csn); break; case OP_RENAME_ENTRY: fprintf (sim.fout, "\t%d rename entry from %s to %s\n", op->csn, g_values [op->old_dn_index], g_values [op->value_index]); break; } } void dump_perm_table (int **perm_table, int op_count) { int i, j; int perm_count = get_perm_count (op_count); for (i = 0; i < op_count; i++) { for (j = 0; j < perm_count; j++) { fprintf (sim.fout, "%d ", perm_table [j][i]); } fprintf (sim.fout, "\n"); } } void dump_entry_state (Entry_State *entry) { int i; fprintf (sim.fout, "\tentry dn: %s; dn csn - %d\n", g_values [entry->dn_index], entry->dn_csn); if (sim.verbose) fprintf (sim.fout, "\n"); for (i = 0; i < sim.value_count; i++) { dump_value_state (&(entry->values[i])); } fprintf (sim.fout, "\n"); } void dump_value_state (Value_State *value) { fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index], value->present ? "present" : "not present"); if (sim.verbose) { fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presense_csn); fprintf (sim.fout, "\t\tdistinguished csn: %d\n", value->distinguished_csn); fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn); fprintf (sim.fout, "\t\tnon distinguished csns: "); dump_list (value->non_distinguished_csns); fprintf (sim.fout, "\n"); } } void dump_list (int *list) { int i = 0; while (list[i]) { fprintf (sim.fout, "%d ", list[i]); i ++; } fprintf (sim.fout, "\n"); } /* misc functions */ int max_val (int a, int b) { if (a >= b) return a; else return b; } int list_equal (int *list1, int *list2) { int i; i = 0; while (list1[i] && list2[i]) { if (list1[i] != list2[i]) return 0; i ++; } if (list1[i] != list2[i]) return 0; else return 1; }