From da04a7e6ef13c178457c00f4f711a3b6b517e6f0 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Sat, 5 Jun 2021 17:46:15 +0200 Subject: [PATCH 1/1] * Initial commit. --- .gitignore | 4 + LICENSE | 29 ++++++ Makefile | 41 ++++++++ README.md | 11 +++ db.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++ db.h | 68 +++++++++++++ main.c | 244 +++++++++++++++++++++++++++++++++++++++++++++ queue.c | 83 ++++++++++++++++ queue.h | 28 ++++++ util.c | 32 ++++++ util.h | 21 ++++ 11 files changed, 843 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 db.c create mode 100644 db.h create mode 100644 main.c create mode 100644 queue.c create mode 100644 queue.h create mode 100644 util.c create mode 100644 util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58be549 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.d +*.o +*.db +imgdupe diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df44e12 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Urban Wallasch +All rights reserved. + +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 copyright holder 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dd519c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ + +PROJECT := imgdupe + +BIN := $(PROJECT) +SRC := $(wildcard *.c) +OBJ := $(SRC:%.c=%.o) +DEP := $(OBJ:%.o=%.d) +SELF := $(lastword $(MAKEFILE_LIST)) + +CC ?= gcc +CFLAGS := -W -Wall -Wextra -O2 -std=gnu99 -pthread -MMD -MP +CFLAGS += $(shell pkg-config --cflags GraphicsMagickWand) +CFLAGS += -DDEBUG + +LD := $(CC) +LDFLAGS := +LIBS := -lpthread $(shell pkg-config --libs GraphicsMagickWand) + +STRIP := strip +RM := rm -f + +.PHONY: all clean distclean + +all: $(BIN) + +$(BIN): $(OBJ) $(SELF) + $(LD) $(LDFLAGS) $(OBJ) $(LIBS) -o $(BIN) + $(STRIP) $(BIN) + +%.o: %.c $(SELF) + $(CC) -c $(CFLAGS) -o $*.o $*.c + +clean: + $(RM) $(BIN) $(OBJ) $(DEP) + +distclean: clean + $(RM) config.h + +-include $(DEP) + +# EOF diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4cf95e --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Imdupe + +Imdupe is a tool to find potentially duplicate images in a directory tree. + + +## License + +FFpreview is distributed under the Modified ("3-clause") BSD License. +See `LICENSE` file for more information. + +---------------------------------------------------------------------- diff --git a/db.c b/db.c new file mode 100644 index 0000000..e2cbf97 --- /dev/null +++ b/db.c @@ -0,0 +1,282 @@ +#include +#include +#include + +#include + +#include + +#include "util.h" +#include "db.h" + + +/* primitive yet fast string hashing */ +static inline int_fast16_t nhash(const char *s) { + int_fast16_t h = 0; + while ( *s ) { + h <<= 1; + h |= (h >> NHASH_BITS) & 1; + h ^= *s++; + } + return h & NHASH_MASK; +} + +/* hamming weight of a 64 bit entity */ +static inline int popcnt64(uint64_t x) { + x -= (x >> 1) & 0x5555555555555555ULL; + x = (x & 0x3333333333333333ULL) + ((x >> 2) & 0x3333333333333333ULL); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL; + return (x * 0x0101010101010101ULL) >> 56; +} + +/* hamming distance of two perceptual hashes */ +static inline int hdist(uint64_t *h1, uint64_t*h2) { + return popcnt64(h1[0]^h2[0]) + popcnt64(h1[1]^h2[1]) + + popcnt64(h1[2]^h2[2]) + popcnt64(h1[3]^h2[3]); +} + +/* perceptual hash function */ +static int phash(db_entry_t *p, double blur) { + int rc = -1; + unsigned char pix[PHASH_BITS]; + MagickWand *wand, *wand2; + + if ( NULL == (wand = NewMagickWand()) ) + goto ERR1; + if ( !MagickReadImage(wand, p->fname) ) + goto ERR2; + wand2 = MagickFlattenImages(wand); + MagickSetImageColorspace(wand2, GRAYColorspace); + MagickResizeImage(wand2, 16, 16, GaussianFilter, blur); + MagickNormalizeImage(wand2); + MagickGetImagePixels(wand2, 0, 0, 16, 16, "I", CharPixel, pix); + uint64_t h = 0; + int hidx = 0; + p->hamw = 0; + for ( size_t i = 0; i < sizeof pix; ) { + h = (h << 1) | (pix[i] > 0x7f); + ++i; + if ( 0 == i % 64 ) { + p->phash[hidx++] = h; + p->hamw += popcnt64(h); /* hamming weight of hash */ + } + } + rc = 0; + DestroyMagickWand(wand2); + ERR2: + DestroyMagickWand(wand); + ERR1: + return rc; +} + + +/* database handling functions */ + +static inline int db_lock(db_t *db) { + return pthread_mutex_lock(&db->mtx); +} + +static inline int db_unlock(db_t *db) { + return pthread_mutex_unlock(&db->mtx); +} + +db_entry_t *db_entry_new(const char *fname) { + db_entry_t *entry = s_malloc(sizeof *entry); + memset(entry, 0, sizeof *entry); + entry->fname = NULL != fname ? s_strdup(fname) : "?"; + return entry; +} + +db_t *db_init(void) { + db_t *db = s_malloc(sizeof *db); + memset(db->p_ent, 0, sizeof db->p_ent); + memset(db->n_ent, 0, sizeof db->n_ent); + pthread_mutex_init(&db->mtx, NULL); + InitializeMagick(NULL); + return db; +} + +void db_destroy(db_t **pdb) { + DestroyMagick(); + db_clear(*pdb); + pthread_mutex_destroy(&(*pdb)->mtx); + s_free(*pdb); + *pdb = NULL; +} + +int db_clear(db_t *db) { + int cnt = 0; + db_lock(db); + for ( size_t i = 0; i < ARR_SIZE(db->n_ent); ++i ) { + db_entry_t *p, *next; + p = db->n_ent[i]; + while ( NULL != p ) { + ++cnt; + next = p->inext; + free(p->fname); + free(p); + p = next; + } + } + memset(db->p_ent, 0, sizeof db->p_ent); + memset(db->n_ent, 0, sizeof db->n_ent); + db_unlock(db); + return cnt; +} + +int db_prune(db_t *db) { + int cnt = 0; + db_lock(db); + for ( size_t i = 0; i < ARR_SIZE(db->n_ent); ++i ) { + db_entry_t *p; + p = db->n_ent[i]; + while ( NULL != p ) { + if ( !DB_ENTRY_ISDELETED(p) && 0 != access(p->fname, R_OK) ) { + dprintf("prune: '%s'\n", p->fname); + DB_ENTRY_DELETE(p); + } + p = p->inext; + } + } + db_unlock(db); + return cnt; +} + +db_entry_t *db_find(db_t *db, const char *fname) { + db_entry_t *p; + int h = nhash(fname); + db_lock(db); + p = db->n_ent[h]; + while ( NULL != p ) { + if ( !DB_ENTRY_ISDELETED(p) && 0 == strcmp(fname, p->fname) ) + break; + p = p->inext; + } + db_unlock(db); + return p; +} + +int db_insert(db_t *db, db_entry_t *entry, int replace, double blur) { + /* entry already in db? */ + db_entry_t *en = db_find(db, entry->fname); + if ( NULL != en ) { + if ( !replace ) + return 1; + DB_ENTRY_DELETE(en); + } + /* phash the image; nhash the file name */ + if ( 0 != phash(entry, blur) ) + return -1; + entry->nhash = nhash(entry->fname); + /* drop entry in the respective hash table buckets */ + db_lock(db); + entry->inext = db->n_ent[entry->nhash]; + db->n_ent[entry->nhash] = entry; + entry->pnext = db->p_ent[entry->hamw]; + db->p_ent[entry->hamw] = entry; + db_unlock(db); + return 0; +} + +int db_write(db_t *db, const char *dbf) { + FILE *fp; + int cnt = 0; + + if ( NULL == (fp = fopen(dbf, "wb")) ) + return -1; + db_lock(db); + for ( size_t i = 0; i < ARR_SIZE(db->n_ent); ++i ) { + db_entry_t *p; + for ( p = db->n_ent[i]; NULL != p; p = p->inext ) { + if ( DB_ENTRY_ISDELETED(p) ) + continue; + ++cnt; + fwrite((void *)&p->phash, sizeof p->phash, 1, fp); + fwrite((void *)&p->nhash, sizeof p->nhash, 1, fp); + fwrite((void *)&p->flags, sizeof p->flags, 1, fp); + fwrite((void *)&p->hamw, sizeof p->hamw, 1, fp); + fwrite((void *)p->fname, strlen(p->fname) + 1, 1, fp); + } + } + db_unlock(db); + fclose(fp); + return cnt; +} + +int db_read(db_t *db, const char *dbf) { + int cnt = 0, c; + size_t i; + char buf[4000]; + FILE *fp; + db_entry_t *p; + + if ( NULL == (fp = fopen(dbf, "rb")) ) + return -1; + db_lock(db); + while ( !feof(fp) ) { + p = db_entry_new(NULL); + fread((void *)&p->phash, sizeof p->phash, 1, fp); + fread((void *)&p->nhash, sizeof p->nhash, 1, fp); + fread((void *)&p->flags, sizeof p->flags, 1, fp); + fread((void *)&p->hamw, sizeof p->hamw, 1, fp); + i = 0; + do { + c = fgetc(fp); + buf[i++] = c; + } while ( '\0' != c && EOF != c && i < sizeof buf ); + if ( '\0' != c ) { + s_free(p); + break; + } + p->fname = s_strdup(buf); + p->inext = db->n_ent[p->nhash]; + db->n_ent[p->nhash] = p; + p->pnext = db->p_ent[p->hamw]; + db->p_ent[p->hamw] = p; + ++cnt; + } + db_unlock(db); + return cnt; +} + +/* find duplicate or similar images */ +int db_find_dupes(db_t *db, int thresh, int(*cb)(db_entry_t *dupes) ) { + db_lock(db); + for ( size_t i = 0; i < ARR_SIZE(db->p_ent); ++i ) { + db_entry_t *p, *cmp, *dupes; + for ( p = db->p_ent[i]; NULL != p; p = p->pnext ) { + if ( DB_ENTRY_ISDELETED(p) ) + continue; + dupes = NULL; + for ( cmp = p->pnext; NULL != cmp; cmp = cmp->pnext ) { + if ( !DB_ENTRY_ISDELETED(cmp) + && thresh <= hdist(cmp->phash, p->phash) ) { + cmp->aux = dupes; + dupes = cmp; + } + } + if ( thresh < 256 && i < PHASH_BITS ) { + /* for lower thresh also look in the next bucket over */ + for ( cmp = db->p_ent[i+1]; NULL != cmp; cmp = cmp->pnext ) { + if ( !DB_ENTRY_ISDELETED(cmp) + && thresh <= 256 - hdist(cmp->phash, p->phash) ) { + cmp->aux = dupes; + dupes = cmp; + } + } + } + if ( NULL != dupes ) { + p->aux = dupes; + dupes = p; + if ( 0 != cb(dupes) ) + goto BREAK; + } + } + } + BREAK: + db_unlock(db); + return 0; +} + +/* EOF */ + diff --git a/db.h b/db.h new file mode 100644 index 0000000..6a6b3b1 --- /dev/null +++ b/db.h @@ -0,0 +1,68 @@ +#ifndef DB_H_INCLUDED +#define DB_H_INCLUDED + +#include +#include + +#define NHASH_BITS 10 +#define NHASH_SIZE (1 << NHASH_BITS) +#define NHASH_MASK (NHASH_SIZE-1) + +#define PHASH_BITS 256 + + +enum db_entry_flags { + DB_ENTRY_FLAG_DEL = 1, + DB_ENTRY_FLAG_MARK = 2, +}; + +#define DB_ENTRY_DELETE(p) ((p)->flags |= DB_ENTRY_FLAG_DEL) +#define DB_ENTRY_ISDELETED(p) ((p)->flags & DB_ENTRY_FLAG_DEL) + +#define DB_ENTRY_MARK(p) ((p)->flags |= DB_ENTRY_FLAG_MARK) +#define DB_ENTRY_UNMARK(p) ((p)->flags &= ~DB_ENTRY_FLAG_MARK) +#define DB_ENTRY_ISMARKED(p) ((p)->flags & DB_ENTRY_FLAG_MARK) + + +typedef + struct db_entry_struct + db_entry_t; + +struct db_entry_struct { + uint64_t phash[4]; + uint16_t nhash; + uint16_t flags; + uint16_t hamw; + char *fname; + db_entry_t *pnext; + db_entry_t *inext; + db_entry_t *aux; +}; + + +typedef + struct db_struct + db_t; + +struct db_struct { + db_entry_t *p_ent[PHASH_BITS + 1]; + db_entry_t *n_ent[NHASH_SIZE]; + pthread_mutex_t mtx; +}; + + +extern db_entry_t *db_entry_new(const char *fname); +extern db_t *db_init(void); +extern void db_destroy(db_t **pdb); +extern int db_clear(db_t *db); +extern int db_prune(db_t *db); +extern db_entry_t *db_find(db_t *db, const char *fname); +extern int db_insert(db_t *db, db_entry_t *entry, int replace, double blur); +extern int db_update(db_t *db, db_entry_t *entry); +extern int db_write(db_t *db, const char *dbf); +extern int db_read(db_t *db, const char *dbf); +extern int db_find_dupes(db_t *db, int thresh, int(*cb)(db_entry_t *dupes) ); + +#endif //ndef DB_H_INCLUDED + +/* EOF */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..2a82483 --- /dev/null +++ b/main.c @@ -0,0 +1,244 @@ +#define _XOPEN_SOURCE 500 /* for nftw() */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "util.h" +#include "db.h" +#include "queue.h" + + +/* configuration */ +static struct { + char *db_outfile; + int db_prune; + int rescan; + double blur; + int thresh; + int max_fd; + int nthreads; +} cfg = { + .db_outfile = NULL, + .db_prune = 0, + .rescan = 0, + .blur = 1.5, + .thresh = 256, + .max_fd = 1000, + .nthreads = 4, +}; + +/* queue used during directory scan; static for scan_dir_cb() */ +static queue_t *q; + + +/* worker thread info */ +typedef struct { + pthread_t tid; + int i; + queue_t *q; + db_t *db; +} thread_info_t; + +/* worker thread function for building db */ +void *worker(void *arg) { + int rc; + thread_info_t *thrinf = arg; + db_entry_t *entry = NULL; + + while ( NULL != entry || !q_complete(thrinf->q) ) { + entry = q_deq(thrinf->q); + if ( NULL == entry ) { + sched_yield(); + continue; + } + /* insert entry in db hash tables */ + rc = db_insert(thrinf->db, entry, cfg.rescan, cfg.blur); + if ( 0 != rc ) { + if ( 0 > rc ) + eprintf("not fingerprinting '%s'\n", entry->fname); + s_free(entry->fname); + s_free(entry); + } + } + return thrinf; +} + +/* callback for nftw directory tree walker */ +int scan_dir_cb(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) { + (void)sb; + (void)ftwbuf; + if (FTW_F == typeflag) { + db_entry_t *entry; + entry = db_entry_new(fpath); + q_enq(q, entry); + } + return 0; +} + +/* scan directory and build db */ +int scan_dir(const char *dir, queue_t *q, db_t *db) { + int rc = -1; + char *dirpath; + + /* start worker threads */ + thread_info_t thrinf[cfg.nthreads]; + pthread_attr_t attr; + pthread_attr_init(&attr); + for ( int i = 0; i < cfg.nthreads; ++i ) { + thrinf[i].i = i; + thrinf[i].q = q; + thrinf[i].db = db; + pthread_create(&thrinf[i].tid, &attr, worker, &thrinf[i]); + } + pthread_attr_destroy(&attr); + /* main thread: walk directory tree */ + errno = 0; + if ( NULL != (dirpath = realpath(dir, NULL)) ) { + rc = nftw(dirpath, scan_dir_cb, cfg.max_fd, FTW_PHYS); + s_free(dirpath); + } + else + eprintf("%s: '%s'\n", strerror(errno), dir); + /* wait for workers to finish */ + q_set_complete(q); + for ( int i = 0; i < cfg.nthreads; ++i ) + pthread_join(thrinf[i].tid, NULL); + return rc; +} + +/* callback for db_find_dupes() */ +static int find_dupes_cb(db_entry_t *dupes) { + printf("VIEW"); + db_entry_t *p; + for ( p = dupes; NULL != p; p = p->aux ) { + printf(" '%s'", p->fname); + } + puts(""); + return 0; +} + +static void usage(char *pname, int ec) { + char *prog = basename(pname); + printf("%s - find potentially duplicate images\n", prog); + printf("USAGE: %s [OPTIONS] DIR ...\n", prog); + printf("OPTIONS:\n" + " -h display this help text and exit\n" + " -b float blur factor; needs -r when changed between runs; default: 1.5\n" + " -f file fingerprint database file\n" + " -m file merge additional fingerprint file\n" + " -p prune missing files from database\n" + " -r rescan files already in database\n" + " -t n similarity threshold in bits (0..256) or percent; default: 256\n" + " -T num number of scan threads (default: 4)\n" + ); + exit(ec); +} + +/* main function */ +int main(int argc, char *argv[]) { + int c, rc = 0, n; + db_t *db ; + + db = db_init(); + while ( ( c = getopt( argc, argv, "+:hb:f:m:prt:T:" ) ) != -1 ) { + switch (c) { + case 'h': + usage(argv[0], EXIT_SUCCESS); + break; + case 'b': + n = atoi(optarg); + cfg.blur = n < 1 ? 1 : n; + break; + case 'f': + cfg.db_outfile = optarg; + /* fall through */ + case 'm': + dprintf("loading '%s'\n", optarg); + n = db_read(db, optarg); + dprintf("loaded %d entries\n", n>0?n:0); + break; + case 'p': + cfg.db_prune = 1; + break; + case 'r': + cfg.rescan = 1; + break; + case 't': { + char *ep; + n = strtol(optarg, &ep, 10); + if ( '%' == *ep ) { + n = n < 0 ? 0 : n > 100 ? 100 : n; + n = 256 * n / 100; + } + cfg.thresh = n < 0 ? 0 : n > 256 ? 256 : n; + } + break; + case 'T': + n = atoi(optarg); + cfg.nthreads = n > 0 ? n : 1; + break; + case ':': + eprintf("ERROR: missing argument for option '-%c'\n", optopt); + usage(argv[0], EXIT_FAILURE); + break; + case '?': + eprintf("ERROR: unknown option '-%c'\n", optopt); + usage( argv[0], EXIT_FAILURE ); + break; + default: + eprintf("WARNING: option '-%c' not implemented, programmer PEBKAC.\n", c); + break; + } + } + + if ( cfg.db_prune ) { + dprintf("pruning\n"); + db_prune(db); + } + + q = q_init(); + if (optind >= argc) { + dprintf("scanning '.'\n"); + rc = scan_dir(".", q, db); + } else { + while ( optind < argc && 0 == rc ) { + dprintf("scanning '%s'\n", argv[optind]); + rc = scan_dir(argv[optind++], q, db); + } + } + q_destroy(&q); + if ( 0 != rc ) + goto DONE; + + if ( NULL != cfg.db_outfile ) { + int cnt; + cnt = db_write(db, cfg.db_outfile); + if ( 0 <= cnt ) + dprintf("%d entries written\n", cnt); + else + eprintf("writing '%s' failed\n", cfg.db_outfile); + } + + dprintf("searching dupes ...\n"); + printf("#!/bin/bash\n"); + printf(". ~/bin/imgmultiview.inc\n"); + rc = db_find_dupes(db, cfg.thresh, find_dupes_cb); + printf("END\n"); + + DONE: + db_destroy(&db); + dprintf("done.\n"); + exit(rc ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* EOF */ diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..240c9c6 --- /dev/null +++ b/queue.c @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "util.h" +#include "queue.h" + + +static inline int q_lock(queue_t *q) { + return pthread_mutex_lock(&q->mtx); +} + +static inline int q_unlock(queue_t *q) { + return pthread_mutex_unlock(&q->mtx); +} + +queue_t *q_init(void) { + queue_t *q = s_malloc(sizeof (queue_t)); + q->penq = q->pdeq = NULL; + q->complete = 0; + pthread_mutex_init(&q->mtx, NULL); + return q; +} + +void q_destroy(queue_t **pq) { + queue_t *q = *pq; + q_lock(q); + db_entry_t *p = q->pdeq, *next; + while ( NULL != p ) { + next = p->aux; + free(p->fname); + free(p); + p = next; + } + q->pdeq = q->penq = NULL; + q_unlock(q); + pthread_mutex_destroy(&q->mtx); + s_free(q); + *pq = NULL; +} + +int q_enq(queue_t *q, db_entry_t *entry) { + q_lock(q); + if ( NULL == q->penq ) /* insert in empty queue */ + q->penq = q->pdeq = entry; + else { + q->penq->aux = entry; + q->penq = entry; + } + q_unlock(q); + return 0; +} + +db_entry_t *q_deq(queue_t *q) { + db_entry_t *entry; + q_lock(q); + entry = q->pdeq; + if ( NULL != entry ) { + if ( q->pdeq == q->penq ) /* queue becomes empty */ + q->pdeq = q->penq = NULL; + else + q->pdeq = q->pdeq->aux; + entry->aux = NULL; + } + q_unlock(q); + return entry; +} + +int q_complete(queue_t *q) { + int qcpl = 0; + q_lock(q); + qcpl = q->complete; + q_unlock(q); + return qcpl; +} + +void q_set_complete(queue_t *q) { + q_lock(q); + q->complete = 1; + q_unlock(q); +} + +/* EOF */ diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..11ab79d --- /dev/null +++ b/queue.h @@ -0,0 +1,28 @@ +#ifndef QUEUE_H_INCLUDED +#define QUEUE_H_INCLUDED + +#include + +#include "db.h" + +typedef + struct queue_struct + queue_t; + +struct queue_struct { + db_entry_t *penq; + db_entry_t *pdeq; + int complete; + pthread_mutex_t mtx; +}; + +extern queue_t *q_init(void); +extern void q_destroy(queue_t **pq); +extern int q_enq(queue_t *q, db_entry_t *entry); +extern db_entry_t *q_deq(queue_t *q); +extern int q_complete(queue_t *q); +extern void q_set_complete(queue_t *q); + +#endif //ndef QUEUE_H_INCLUDED + +/* EOF */ diff --git a/util.c b/util.c new file mode 100644 index 0000000..f43cef6 --- /dev/null +++ b/util.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include "util.h" + +void die(int eno) { + if ( eno ) + eprintf("%s\n", strerror(eno)); + exit(EXIT_FAILURE); +} + +void *s_malloc(size_t sz) { + void *m = malloc(sz); + if ( NULL == m ) + die(errno); + return m; +} + +void *s_strdup(const char *s) { + void *d = strdup(s); + if ( NULL == d ) + die(errno); + return d; +} + +void s_free(void *p){ + free(p); +} + +/* EOF */ diff --git a/util.h b/util.h new file mode 100644 index 0000000..7890c17 --- /dev/null +++ b/util.h @@ -0,0 +1,21 @@ +#ifndef UTIL_H_INCLUDED +#define UTIL_H_INCLUDED + +#define ARR_SIZE(a) (sizeof(a)/sizeof(*a)) + +#define eprintf(...) fprintf(stderr, __VA_ARGS__) + +#ifdef DEBUG + #define dprintf(...) do { \ + fprintf(stderr, "(%s|%s|%d) ", __FILE__, __func__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); } while(0) +#else + #define dprintf(...) +#endif + +extern void die(int eno); +extern void *s_malloc(size_t sz); +extern void *s_strdup(const char *s); +extern void s_free(void *p); + +#endif //ndef UTIL_H_INCLUDED -- 2.30.2