Project

General

Profile

Download (6.61 KB) Statistics
| Branch: | Tag: | Revision:
/**
* Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl
*
*/
#include <assert.h>
#include <sys/stat.h>
#include <pthread.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "putils.h"
#include "malloc.h"
/* parallel diff of a directory heirarchy that also checks ownership and attributes */

struct diff_task {
char *relative_path;
struct diff_task *next;
};

// Worker state
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct diff_task *tasks = NULL;
enum { n_workers = 32};
size_t n_working = 0; // When tasks==NULL and n_working==0, we are done
pthread_t workers[n_workers];

#define PMAX 4096
const char *src_dir;
const char *dst_dir;

int check_ownership = 1;

// From the qsort man page:
static int cmpstringp(const void *p1, const void *p2) {
// The actual arguments to this function are "pointers to pointers
// to char", but strcmp(3) arguments are "pointers to char", hence
// the following cast plus dereference.
return strcmp(* (char * const *) p1, * (char * const *) p2);
}

static void collect_and_sort_pathnames(const char *abs_path,
// output:
size_t *np,
char ***paths_p)
// Collect all the pathnames in a directory, except for . and .., and
// return them in sorted order.
{
size_t n = 0;
size_t limit = 2;
char **MALLOC_N(paths, limit);
DIR *dir = opendir(abs_path);
assert(dir);
struct dirent *dentry;
while ((dentry = readdir(dir))) {
if (strcmp(dentry->d_name, ".") == 0) continue;
if (strcmp(dentry->d_name, "..") == 0) continue;
if (n >= limit) {
limit *= 2;
REALLOC(paths, limit);
}
assert(n < limit);
paths[n++] = strdup(dentry->d_name);
}
closedir(dir);
qsort(paths, n, sizeof(char*), cmpstringp);
*np = n;
*paths_p = paths;
}

static void diff_r(const char *relative_path) {
char src_path[PMAX];
{
int n = snprintf(src_path, PMAX, "%s/%s", src_dir, relative_path);
assert(n < PMAX);
}
char dst_path[PMAX];
{
int n = snprintf(dst_path, PMAX, "%s/%s", dst_dir, relative_path);
assert(n < PMAX);
}
struct stat src_stat, dst_stat;
{
int src_r = lstat(src_path, &src_stat);
assert (src_r == 0);
}
{
int dst_r = lstat(dst_path, &dst_stat);
assert(dst_r == 0);
}
assert((src_stat.st_mode & S_IFMT) == (dst_stat.st_mode & S_IFMT));
{
mode_t mask = 0777;
if ((src_stat.st_mode & mask) != (dst_stat.st_mode & mask)) {
fprintf(stderr, "masks differ %s and %s\n", src_path, dst_path);
assert(0);
}
}
if (check_ownership) {
if (src_stat.st_uid != dst_stat.st_uid) {
fprintf(stderr, "uid doesn't match for %s and %s\n", src_path, dst_path);
}
assert(src_stat.st_uid == dst_stat.st_uid);
assert(src_stat.st_gid == dst_stat.st_gid);
}
if (S_ISREG(dst_stat.st_mode)) {
assert(src_stat.st_size == dst_stat.st_size);
FILE *src_f = fopen(src_path, "r");
FILE *dst_f = fopen(dst_path, "r");
if (src_f == NULL || dst_f == NULL) {
fprintf(stderr, "Failed to open one or more files\n");
return;
}
for (size_t i = 0 ; i < (size_t)(src_stat.st_size); i++) {
int src_c = getc(src_f); assert(src_c != EOF);
int dst_c = getc(dst_f); assert(dst_c != EOF);
assert(src_c == dst_c);
}
fclose(src_f);
fclose(dst_f);
} else if (S_ISDIR(src_stat.st_mode)) {
size_t sn, dn;
char **sps, **dps;
collect_and_sort_pathnames(src_path, &sn, &sps);
collect_and_sort_pathnames(dst_path, &dn, &dps);
assert(sn == dn);
for (size_t i = 0; i < sn; i++) {
assert(strcmp(sps[i], dps[i]) == 0);
struct diff_task *MALLOC(task);
char sub_rel_path[PMAX];
{
int n = snprintf(sub_rel_path, PMAX, "%s/%s", relative_path, sps[i]);
assert(n < PMAX);
}
char *srp = strdup(sub_rel_path);
pthread_mutex_lock(&mutex);
*task = (struct diff_task){.relative_path = srp,
.next = tasks};
tasks = task;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
} else if (S_ISLNK(src_stat.st_mode)) {
char slname[PMAX];
ssize_t slen = readlink(src_path, slname, PMAX);
assert(slen >= 0 && slen < PMAX);
char dlname[PMAX];
ssize_t dlen = readlink(dst_path, dlname, PMAX);
assert(dlen >= 0 && dlen < PMAX);
assert(slen == dlen);
slname[slen] = 0;
dlname[dlen] = 0;
if (0 != strcmp(slname, dlname)) {
fprintf(stderr, "%s -> %s is not the same as %s -> %s\n", src_path, slname, dst_path, dlname);
assert(0);
}
}
}

static void* run_worker(void *d) {
pthread_mutex_lock(&mutex);
while (1) {
// Lock is held here.
while (!tasks && n_working) {
pthread_cond_wait(&cond, &mutex);
}
// Either there is a task or n_working==0
if (tasks) {
struct diff_task *task = tasks;
tasks = task->next;
n_working++;
pthread_mutex_unlock(&mutex);
diff_r(task->relative_path);
free(task->relative_path);
free(task);
pthread_mutex_lock(&mutex);
n_working--;
if (tasks == NULL && n_working == 0) {
pthread_cond_broadcast(&cond);
}
} else if (n_working == 0) {
// No tasks and nothing to do.
pthread_mutex_unlock(&mutex);
return d;
}
}
}

static void run_workers(void) {
MALLOC(tasks);
*tasks = (struct diff_task){.relative_path = strdup(""),
.next = NULL};
n_working = 0;
for (size_t i = 0; i < n_workers; i++) {
pthread_create(&workers[i], NULL, run_worker, &workers[i]);
}
for (size_t i = 0; i < n_workers; i++) {
void *result;
pthread_join(workers[i], &result);
}
}

int main (int argc, const char *argv[]) {
assert(argc == 3);
src_dir = argv[1];
dst_dir = argv[2];
run_workers();
return 0;
}
(3-3/29)