From: Urban Wallasch Date: Sat, 5 Jun 2021 15:46:15 +0000 (+0200) Subject: * Initial commit. X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=da04a7e6ef13c178457c00f4f711a3b6b517e6f0;p=imgdupe.git * Initial commit. --- da04a7e6ef13c178457c00f4f711a3b6b517e6f0 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