--- /dev/null
+*.d
+*.o
+*.db
+imgdupe
--- /dev/null
+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.
--- /dev/null
+
+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
--- /dev/null
+# 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.
+
+----------------------------------------------------------------------
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include <wand/magick_wand.h>
+
+#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 */
+
--- /dev/null
+#ifndef DB_H_INCLUDED
+#define DB_H_INCLUDED
+
+#include <stdint.h>
+#include <pthread.h>
+
+#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 */
--- /dev/null
+#define _XOPEN_SOURCE 500 /* for nftw() */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <libgen.h>
+#include <ftw.h>
+#include <sched.h>
+
+#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 */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#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 */
--- /dev/null
+#ifndef QUEUE_H_INCLUDED
+#define QUEUE_H_INCLUDED
+
+#include <pthread.h>
+
+#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 */
--- /dev/null
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 */
--- /dev/null
+#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