123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright 2019, Nick Piggin, Gautham R. Shenoy, Aneesh Kumar K.V, IBM Corp.
- */
- /*
- *
- * Test tlbie/mtpidr race. We have 4 threads doing flush/load/compare/store
- * sequence in a loop. The same threads also rung a context switch task
- * that does sched_yield() in loop.
- *
- * The snapshot thread mark the mmap area PROT_READ in between, make a copy
- * and copy it back to the original area. This helps us to detect if any
- * store continued to happen after we marked the memory PROT_READ.
- */
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <sys/mman.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <sys/stat.h>
- #include <sys/time.h>
- #include <linux/futex.h>
- #include <unistd.h>
- #include <asm/unistd.h>
- #include <string.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <sched.h>
- #include <time.h>
- #include <stdarg.h>
- #include <sched.h>
- #include <pthread.h>
- #include <signal.h>
- #include <sys/prctl.h>
- static inline void dcbf(volatile unsigned int *addr)
- {
- __asm__ __volatile__ ("dcbf %y0; sync" : : "Z"(*(unsigned char *)addr) : "memory");
- }
- static void err_msg(char *msg)
- {
- time_t now;
- time(&now);
- printf("=================================\n");
- printf(" Error: %s\n", msg);
- printf(" %s", ctime(&now));
- printf("=================================\n");
- exit(1);
- }
- static char *map1;
- static char *map2;
- static pid_t rim_process_pid;
- /*
- * A "rim-sequence" is defined to be the sequence of the following
- * operations performed on a memory word:
- * 1) FLUSH the contents of that word.
- * 2) LOAD the contents of that word.
- * 3) COMPARE the contents of that word with the content that was
- * previously stored at that word
- * 4) STORE new content into that word.
- *
- * The threads in this test that perform the rim-sequence are termed
- * as rim_threads.
- */
- /*
- * A "corruption" is defined to be the failed COMPARE operation in a
- * rim-sequence.
- *
- * A rim_thread that detects a corruption informs about it to all the
- * other rim_threads, and the mem_snapshot thread.
- */
- static volatile unsigned int corruption_found;
- /*
- * This defines the maximum number of rim_threads in this test.
- *
- * The THREAD_ID_BITS denote the number of bits required
- * to represent the thread_ids [0..MAX_THREADS - 1].
- * We are being a bit paranoid here and set it to 8 bits,
- * though 6 bits suffice.
- *
- */
- #define MAX_THREADS 64
- #define THREAD_ID_BITS 8
- #define THREAD_ID_MASK ((1 << THREAD_ID_BITS) - 1)
- static unsigned int rim_thread_ids[MAX_THREADS];
- static pthread_t rim_threads[MAX_THREADS];
- /*
- * Each rim_thread works on an exclusive "chunk" of size
- * RIM_CHUNK_SIZE.
- *
- * The ith rim_thread works on the ith chunk.
- *
- * The ith chunk begins at
- * map1 + (i * RIM_CHUNK_SIZE)
- */
- #define RIM_CHUNK_SIZE 1024
- #define BITS_PER_BYTE 8
- #define WORD_SIZE (sizeof(unsigned int))
- #define WORD_BITS (WORD_SIZE * BITS_PER_BYTE)
- #define WORDS_PER_CHUNK (RIM_CHUNK_SIZE/WORD_SIZE)
- static inline char *compute_chunk_start_addr(unsigned int thread_id)
- {
- char *chunk_start;
- chunk_start = (char *)((unsigned long)map1 +
- (thread_id * RIM_CHUNK_SIZE));
- return chunk_start;
- }
- /*
- * The "word-offset" of a word-aligned address inside a chunk, is
- * defined to be the number of words that precede the address in that
- * chunk.
- *
- * WORD_OFFSET_BITS denote the number of bits required to represent
- * the word-offsets of all the word-aligned addresses of a chunk.
- */
- #define WORD_OFFSET_BITS (__builtin_ctz(WORDS_PER_CHUNK))
- #define WORD_OFFSET_MASK ((1 << WORD_OFFSET_BITS) - 1)
- static inline unsigned int compute_word_offset(char *start, unsigned int *addr)
- {
- unsigned int delta_bytes, ret;
- delta_bytes = (unsigned long)addr - (unsigned long)start;
- ret = delta_bytes/WORD_SIZE;
- return ret;
- }
- /*
- * A "sweep" is defined to be the sequential execution of the
- * rim-sequence by a rim_thread on its chunk one word at a time,
- * starting from the first word of its chunk and ending with the last
- * word of its chunk.
- *
- * Each sweep of a rim_thread is uniquely identified by a sweep_id.
- * SWEEP_ID_BITS denote the number of bits required to represent
- * the sweep_ids of rim_threads.
- *
- * As to why SWEEP_ID_BITS are computed as a function of THREAD_ID_BITS,
- * WORD_OFFSET_BITS, and WORD_BITS, see the "store-pattern" below.
- */
- #define SWEEP_ID_BITS (WORD_BITS - (THREAD_ID_BITS + WORD_OFFSET_BITS))
- #define SWEEP_ID_MASK ((1 << SWEEP_ID_BITS) - 1)
- /*
- * A "store-pattern" is the word-pattern that is stored into a word
- * location in the 4)STORE step of the rim-sequence.
- *
- * In the store-pattern, we shall encode:
- *
- * - The thread-id of the rim_thread performing the store
- * (The most significant THREAD_ID_BITS)
- *
- * - The word-offset of the address into which the store is being
- * performed (The next WORD_OFFSET_BITS)
- *
- * - The sweep_id of the current sweep in which the store is
- * being performed. (The lower SWEEP_ID_BITS)
- *
- * Store Pattern: 32 bits
- * |------------------|--------------------|---------------------------------|
- * | Thread id | Word offset | sweep_id |
- * |------------------|--------------------|---------------------------------|
- * THREAD_ID_BITS WORD_OFFSET_BITS SWEEP_ID_BITS
- *
- * In the store pattern, the (Thread-id + Word-offset) uniquely identify the
- * address to which the store is being performed i.e,
- * address == map1 +
- * (Thread-id * RIM_CHUNK_SIZE) + (Word-offset * WORD_SIZE)
- *
- * And the sweep_id in the store pattern identifies the time when the
- * store was performed by the rim_thread.
- *
- * We shall use this property in the 3)COMPARE step of the
- * rim-sequence.
- */
- #define SWEEP_ID_SHIFT 0
- #define WORD_OFFSET_SHIFT (SWEEP_ID_BITS)
- #define THREAD_ID_SHIFT (WORD_OFFSET_BITS + SWEEP_ID_BITS)
- /*
- * Compute the store pattern for a given thread with id @tid, at
- * location @addr in the sweep identified by @sweep_id
- */
- static inline unsigned int compute_store_pattern(unsigned int tid,
- unsigned int *addr,
- unsigned int sweep_id)
- {
- unsigned int ret = 0;
- char *start = compute_chunk_start_addr(tid);
- unsigned int word_offset = compute_word_offset(start, addr);
- ret += (tid & THREAD_ID_MASK) << THREAD_ID_SHIFT;
- ret += (word_offset & WORD_OFFSET_MASK) << WORD_OFFSET_SHIFT;
- ret += (sweep_id & SWEEP_ID_MASK) << SWEEP_ID_SHIFT;
- return ret;
- }
- /* Extract the thread-id from the given store-pattern */
- static inline unsigned int extract_tid(unsigned int pattern)
- {
- unsigned int ret;
- ret = (pattern >> THREAD_ID_SHIFT) & THREAD_ID_MASK;
- return ret;
- }
- /* Extract the word-offset from the given store-pattern */
- static inline unsigned int extract_word_offset(unsigned int pattern)
- {
- unsigned int ret;
- ret = (pattern >> WORD_OFFSET_SHIFT) & WORD_OFFSET_MASK;
- return ret;
- }
- /* Extract the sweep-id from the given store-pattern */
- static inline unsigned int extract_sweep_id(unsigned int pattern)
- {
- unsigned int ret;
- ret = (pattern >> SWEEP_ID_SHIFT) & SWEEP_ID_MASK;
- return ret;
- }
- /************************************************************
- * *
- * Logging the output of the verification *
- * *
- ************************************************************/
- #define LOGDIR_NAME_SIZE 100
- static char logdir[LOGDIR_NAME_SIZE];
- static FILE *fp[MAX_THREADS];
- static const char logfilename[] ="Thread-%02d-Chunk";
- static inline void start_verification_log(unsigned int tid,
- unsigned int *addr,
- unsigned int cur_sweep_id,
- unsigned int prev_sweep_id)
- {
- FILE *f;
- char logfile[30];
- char path[LOGDIR_NAME_SIZE + 30];
- char separator[2] = "/";
- char *chunk_start = compute_chunk_start_addr(tid);
- unsigned int size = RIM_CHUNK_SIZE;
- sprintf(logfile, logfilename, tid);
- strcpy(path, logdir);
- strcat(path, separator);
- strcat(path, logfile);
- f = fopen(path, "w");
- if (!f) {
- err_msg("Unable to create logfile\n");
- }
- fp[tid] = f;
- fprintf(f, "----------------------------------------------------------\n");
- fprintf(f, "PID = %d\n", rim_process_pid);
- fprintf(f, "Thread id = %02d\n", tid);
- fprintf(f, "Chunk Start Addr = 0x%016lx\n", (unsigned long)chunk_start);
- fprintf(f, "Chunk Size = %d\n", size);
- fprintf(f, "Next Store Addr = 0x%016lx\n", (unsigned long)addr);
- fprintf(f, "Current sweep-id = 0x%08x\n", cur_sweep_id);
- fprintf(f, "Previous sweep-id = 0x%08x\n", prev_sweep_id);
- fprintf(f, "----------------------------------------------------------\n");
- }
- static inline void log_anamoly(unsigned int tid, unsigned int *addr,
- unsigned int expected, unsigned int observed)
- {
- FILE *f = fp[tid];
- fprintf(f, "Thread %02d: Addr 0x%lx: Expected 0x%x, Observed 0x%x\n",
- tid, (unsigned long)addr, expected, observed);
- fprintf(f, "Thread %02d: Expected Thread id = %02d\n", tid, extract_tid(expected));
- fprintf(f, "Thread %02d: Observed Thread id = %02d\n", tid, extract_tid(observed));
- fprintf(f, "Thread %02d: Expected Word offset = %03d\n", tid, extract_word_offset(expected));
- fprintf(f, "Thread %02d: Observed Word offset = %03d\n", tid, extract_word_offset(observed));
- fprintf(f, "Thread %02d: Expected sweep-id = 0x%x\n", tid, extract_sweep_id(expected));
- fprintf(f, "Thread %02d: Observed sweep-id = 0x%x\n", tid, extract_sweep_id(observed));
- fprintf(f, "----------------------------------------------------------\n");
- }
- static inline void end_verification_log(unsigned int tid, unsigned nr_anamolies)
- {
- FILE *f = fp[tid];
- char logfile[30];
- char path[LOGDIR_NAME_SIZE + 30];
- char separator[] = "/";
- fclose(f);
- if (nr_anamolies == 0) {
- remove(path);
- return;
- }
- sprintf(logfile, logfilename, tid);
- strcpy(path, logdir);
- strcat(path, separator);
- strcat(path, logfile);
- printf("Thread %02d chunk has %d corrupted words. For details check %s\n",
- tid, nr_anamolies, path);
- }
- /*
- * When a COMPARE step of a rim-sequence fails, the rim_thread informs
- * everyone else via the shared_memory pointed to by
- * corruption_found variable. On seeing this, every thread verifies the
- * content of its chunk as follows.
- *
- * Suppose a thread identified with @tid was about to store (but not
- * yet stored) to @next_store_addr in its current sweep identified
- * @cur_sweep_id. Let @prev_sweep_id indicate the previous sweep_id.
- *
- * This implies that for all the addresses @addr < @next_store_addr,
- * Thread @tid has already performed a store as part of its current
- * sweep. Hence we expect the content of such @addr to be:
- * |-------------------------------------------------|
- * | tid | word_offset(addr) | cur_sweep_id |
- * |-------------------------------------------------|
- *
- * Since Thread @tid is yet to perform stores on address
- * @next_store_addr and above, we expect the content of such an
- * address @addr to be:
- * |-------------------------------------------------|
- * | tid | word_offset(addr) | prev_sweep_id |
- * |-------------------------------------------------|
- *
- * The verifier function @verify_chunk does this verification and logs
- * any anamolies that it finds.
- */
- static void verify_chunk(unsigned int tid, unsigned int *next_store_addr,
- unsigned int cur_sweep_id,
- unsigned int prev_sweep_id)
- {
- unsigned int *iter_ptr;
- unsigned int size = RIM_CHUNK_SIZE;
- unsigned int expected;
- unsigned int observed;
- char *chunk_start = compute_chunk_start_addr(tid);
- int nr_anamolies = 0;
- start_verification_log(tid, next_store_addr,
- cur_sweep_id, prev_sweep_id);
- for (iter_ptr = (unsigned int *)chunk_start;
- (unsigned long)iter_ptr < (unsigned long)chunk_start + size;
- iter_ptr++) {
- unsigned int expected_sweep_id;
- if (iter_ptr < next_store_addr) {
- expected_sweep_id = cur_sweep_id;
- } else {
- expected_sweep_id = prev_sweep_id;
- }
- expected = compute_store_pattern(tid, iter_ptr, expected_sweep_id);
- dcbf((volatile unsigned int*)iter_ptr); //Flush before reading
- observed = *iter_ptr;
- if (observed != expected) {
- nr_anamolies++;
- log_anamoly(tid, iter_ptr, expected, observed);
- }
- }
- end_verification_log(tid, nr_anamolies);
- }
- static void set_pthread_cpu(pthread_t th, int cpu)
- {
- cpu_set_t run_cpu_mask;
- struct sched_param param;
- CPU_ZERO(&run_cpu_mask);
- CPU_SET(cpu, &run_cpu_mask);
- pthread_setaffinity_np(th, sizeof(cpu_set_t), &run_cpu_mask);
- param.sched_priority = 1;
- if (0 && sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
- /* haven't reproduced with this setting, it kills random preemption which may be a factor */
- fprintf(stderr, "could not set SCHED_FIFO, run as root?\n");
- }
- }
- static void set_mycpu(int cpu)
- {
- cpu_set_t run_cpu_mask;
- struct sched_param param;
- CPU_ZERO(&run_cpu_mask);
- CPU_SET(cpu, &run_cpu_mask);
- sched_setaffinity(0, sizeof(cpu_set_t), &run_cpu_mask);
- param.sched_priority = 1;
- if (0 && sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
- fprintf(stderr, "could not set SCHED_FIFO, run as root?\n");
- }
- }
- static volatile int segv_wait;
- static void segv_handler(int signo, siginfo_t *info, void *extra)
- {
- while (segv_wait) {
- sched_yield();
- }
- }
- static void set_segv_handler(void)
- {
- struct sigaction sa;
- sa.sa_flags = SA_SIGINFO;
- sa.sa_sigaction = segv_handler;
- if (sigaction(SIGSEGV, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
- }
- int timeout = 0;
- /*
- * This function is executed by every rim_thread.
- *
- * This function performs sweeps over the exclusive chunks of the
- * rim_threads executing the rim-sequence one word at a time.
- */
- static void *rim_fn(void *arg)
- {
- unsigned int tid = *((unsigned int *)arg);
- int size = RIM_CHUNK_SIZE;
- char *chunk_start = compute_chunk_start_addr(tid);
- unsigned int prev_sweep_id;
- unsigned int cur_sweep_id = 0;
- /* word access */
- unsigned int pattern = cur_sweep_id;
- unsigned int *pattern_ptr = &pattern;
- unsigned int *w_ptr, read_data;
- set_segv_handler();
- /*
- * Let us initialize the chunk:
- *
- * Each word-aligned address addr in the chunk,
- * is initialized to :
- * |-------------------------------------------------|
- * | tid | word_offset(addr) | 0 |
- * |-------------------------------------------------|
- */
- for (w_ptr = (unsigned int *)chunk_start;
- (unsigned long)w_ptr < (unsigned long)(chunk_start) + size;
- w_ptr++) {
- *pattern_ptr = compute_store_pattern(tid, w_ptr, cur_sweep_id);
- *w_ptr = *pattern_ptr;
- }
- while (!corruption_found && !timeout) {
- prev_sweep_id = cur_sweep_id;
- cur_sweep_id = cur_sweep_id + 1;
- for (w_ptr = (unsigned int *)chunk_start;
- (unsigned long)w_ptr < (unsigned long)(chunk_start) + size;
- w_ptr++) {
- unsigned int old_pattern;
- /*
- * Compute the pattern that we would have
- * stored at this location in the previous
- * sweep.
- */
- old_pattern = compute_store_pattern(tid, w_ptr, prev_sweep_id);
- /*
- * FLUSH:Ensure that we flush the contents of
- * the cache before loading
- */
- dcbf((volatile unsigned int*)w_ptr); //Flush
- /* LOAD: Read the value */
- read_data = *w_ptr; //Load
- /*
- * COMPARE: Is it the same as what we had stored
- * in the previous sweep ? It better be!
- */
- if (read_data != old_pattern) {
- /* No it isn't! Tell everyone */
- corruption_found = 1;
- }
- /*
- * Before performing a store, let us check if
- * any rim_thread has found a corruption.
- */
- if (corruption_found || timeout) {
- /*
- * Yes. Someone (including us!) has found
- * a corruption :(
- *
- * Let us verify that our chunk is
- * correct.
- */
- /* But first, let us allow the dust to settle down! */
- verify_chunk(tid, w_ptr, cur_sweep_id, prev_sweep_id);
- return 0;
- }
- /*
- * Compute the new pattern that we are going
- * to write to this location
- */
- *pattern_ptr = compute_store_pattern(tid, w_ptr, cur_sweep_id);
- /*
- * STORE: Now let us write this pattern into
- * the location
- */
- *w_ptr = *pattern_ptr;
- }
- }
- return NULL;
- }
- static unsigned long start_cpu = 0;
- static unsigned long nrthreads = 4;
- static pthread_t mem_snapshot_thread;
- static void *mem_snapshot_fn(void *arg)
- {
- int page_size = getpagesize();
- size_t size = page_size;
- void *tmp = malloc(size);
- while (!corruption_found && !timeout) {
- /* Stop memory migration once corruption is found */
- segv_wait = 1;
- mprotect(map1, size, PROT_READ);
- /*
- * Load from the working alias (map1). Loading from map2
- * also fails.
- */
- memcpy(tmp, map1, size);
- /*
- * Stores must go via map2 which has write permissions, but
- * the corrupted data tends to be seen in the snapshot buffer,
- * so corruption does not appear to be introduced at the
- * copy-back via map2 alias here.
- */
- memcpy(map2, tmp, size);
- /*
- * Before releasing other threads, must ensure the copy
- * back to
- */
- asm volatile("sync" ::: "memory");
- mprotect(map1, size, PROT_READ|PROT_WRITE);
- asm volatile("sync" ::: "memory");
- segv_wait = 0;
- usleep(1); /* This value makes a big difference */
- }
- return 0;
- }
- void alrm_sighandler(int sig)
- {
- timeout = 1;
- }
- int main(int argc, char *argv[])
- {
- int c;
- int page_size = getpagesize();
- time_t now;
- int i, dir_error;
- pthread_attr_t attr;
- key_t shm_key = (key_t) getpid();
- int shmid, run_time = 20 * 60;
- struct sigaction sa_alrm;
- snprintf(logdir, LOGDIR_NAME_SIZE,
- "/tmp/logdir-%u", (unsigned int)getpid());
- while ((c = getopt(argc, argv, "r:hn:l:t:")) != -1) {
- switch(c) {
- case 'r':
- start_cpu = strtoul(optarg, NULL, 10);
- break;
- case 'h':
- printf("%s [-r <start_cpu>] [-n <nrthreads>] [-l <logdir>] [-t <timeout>]\n", argv[0]);
- exit(0);
- break;
- case 'n':
- nrthreads = strtoul(optarg, NULL, 10);
- break;
- case 'l':
- strncpy(logdir, optarg, LOGDIR_NAME_SIZE - 1);
- break;
- case 't':
- run_time = strtoul(optarg, NULL, 10);
- break;
- default:
- printf("invalid option\n");
- exit(0);
- break;
- }
- }
- if (nrthreads > MAX_THREADS)
- nrthreads = MAX_THREADS;
- shmid = shmget(shm_key, page_size, IPC_CREAT|0666);
- if (shmid < 0) {
- err_msg("Failed shmget\n");
- }
- map1 = shmat(shmid, NULL, 0);
- if (map1 == (void *) -1) {
- err_msg("Failed shmat");
- }
- map2 = shmat(shmid, NULL, 0);
- if (map2 == (void *) -1) {
- err_msg("Failed shmat");
- }
- dir_error = mkdir(logdir, 0755);
- if (dir_error) {
- err_msg("Failed mkdir");
- }
- printf("start_cpu list:%lu\n", start_cpu);
- printf("number of worker threads:%lu + 1 snapshot thread\n", nrthreads);
- printf("Allocated address:0x%016lx + secondary map:0x%016lx\n", (unsigned long)map1, (unsigned long)map2);
- printf("logdir at : %s\n", logdir);
- printf("Timeout: %d seconds\n", run_time);
- time(&now);
- printf("=================================\n");
- printf(" Starting Test\n");
- printf(" %s", ctime(&now));
- printf("=================================\n");
- for (i = 0; i < nrthreads; i++) {
- if (1 && !fork()) {
- prctl(PR_SET_PDEATHSIG, SIGKILL);
- set_mycpu(start_cpu + i);
- for (;;)
- sched_yield();
- exit(0);
- }
- }
- sa_alrm.sa_handler = &alrm_sighandler;
- sigemptyset(&sa_alrm.sa_mask);
- sa_alrm.sa_flags = 0;
- if (sigaction(SIGALRM, &sa_alrm, 0) == -1) {
- err_msg("Failed signal handler registration\n");
- }
- alarm(run_time);
- pthread_attr_init(&attr);
- for (i = 0; i < nrthreads; i++) {
- rim_thread_ids[i] = i;
- pthread_create(&rim_threads[i], &attr, rim_fn, &rim_thread_ids[i]);
- set_pthread_cpu(rim_threads[i], start_cpu + i);
- }
- pthread_create(&mem_snapshot_thread, &attr, mem_snapshot_fn, map1);
- set_pthread_cpu(mem_snapshot_thread, start_cpu + i);
- pthread_join(mem_snapshot_thread, NULL);
- for (i = 0; i < nrthreads; i++) {
- pthread_join(rim_threads[i], NULL);
- }
- if (!timeout) {
- time(&now);
- printf("=================================\n");
- printf(" Data Corruption Detected\n");
- printf(" %s", ctime(&now));
- printf(" See logfiles in %s\n", logdir);
- printf("=================================\n");
- return 1;
- }
- return 0;
- }
|