[Fingerforce-commits] [libfprint] 14/20: imgdev: perform 5 scans for enrollment

Didier Raboud odyx at alioth.debian.org
Sun Sep 29 13:43:38 UTC 2013


This is an automated email from the git hooks/post-receive script.

odyx pushed a commit to branch master
in repository libfprint.

commit e215b0509448e05bf7a352317bc3282a9d5fd437
Author: Vasily Khoruzhick <anarsoul at gmail.com>
Date:   Mon Feb 18 12:27:33 2013 +0300

    imgdev: perform 5 scans for enrollment
    
    This feature dramatically improves matching rate on devices with small
    sensors.
---
 libfprint/data.c           |  161 +++++++++++++++++++++++++++++++++++---------
 libfprint/drivers/upeke2.c |   12 ++--
 libfprint/drivers/upekts.c |   12 ++--
 libfprint/fp_internal.h    |   20 ++++--
 libfprint/img.c            |   78 +++++++++++++++------
 libfprint/imgdev.c         |   32 ++++++++-
 6 files changed, 247 insertions(+), 68 deletions(-)

diff --git a/libfprint/data.c b/libfprint/data.c
index bfbf8fb..3c138c3 100644
--- a/libfprint/data.c
+++ b/libfprint/data.c
@@ -95,21 +95,33 @@ static const char *finger_num_to_str(enum fp_finger finger)
 #endif
 
 static struct fp_print_data *print_data_new(uint16_t driver_id,
-	uint32_t devtype, enum fp_print_data_type type, size_t length)
+	uint32_t devtype, enum fp_print_data_type type)
 {
-	struct fp_print_data *data = g_malloc0(sizeof(*data) + length);
-	fp_dbg("length=%zd driver=%02x devtype=%04x", length, driver_id, devtype);
+	struct fp_print_data *data = g_malloc0(sizeof(*data));
+	fp_dbg("driver=%02x devtype=%04x", driver_id, devtype);
 	data->driver_id = driver_id;
 	data->devtype = devtype;
 	data->type = type;
-	data->length = length;
 	return data;
 }
 
-struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length)
+void fpi_print_data_item_free(struct fp_print_data_item *item)
+{
+	g_free(item);
+}
+
+struct fp_print_data_item *fpi_print_data_item_new(size_t length)
+{
+	struct fp_print_data_item *item = g_malloc(sizeof(*item) + length);
+	item->length = length;
+
+	return item;
+}
+
+struct fp_print_data *fpi_print_data_new(struct fp_dev *dev)
 {
 	return print_data_new(dev->drv->id, dev->devtype,
-		fpi_driver_get_data_type(dev->drv), length);
+		fpi_driver_get_data_type(dev->drv));
 }
 
 /** \ingroup print_data
@@ -124,27 +136,115 @@ struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length)
 API_EXPORTED size_t fp_print_data_get_data(struct fp_print_data *data,
 	unsigned char **ret)
 {
-	struct fpi_print_data_fp1 *buf;
-	size_t buflen;
+	struct fpi_print_data_fp2 *out_data;
+	struct fpi_print_data_item_fp2 *out_item;
+	struct fp_print_data_item *item;
+	size_t buflen = 0;
+	GSList *list_item;
+	unsigned char *buf;
 
 	fp_dbg("");
 
-	buflen = sizeof(*buf) + data->length;
-	buf = malloc(buflen);
-	if (!buf)
-		return 0;
-
-	*ret = (unsigned char *) buf;
-	buf->prefix[0] = 'F';
-	buf->prefix[1] = 'P';
-	buf->prefix[2] = '1';
-	buf->driver_id = GUINT16_TO_LE(data->driver_id);
-	buf->devtype = GUINT32_TO_LE(data->devtype);
-	buf->data_type = data->type;
-	memcpy(buf->data, data->data, data->length);
+	list_item = data->prints;
+	while (list_item) {
+		item = list_item->data;
+		buflen += sizeof(*out_item);
+		buflen += item->length;
+		list_item = g_slist_next(list_item);
+	}
+
+	buflen += sizeof(*out_data);
+	out_data = g_malloc(buflen);
+
+	*ret = (unsigned char *) out_data;
+	buf = out_data->data;
+	out_data->prefix[0] = 'F';
+	out_data->prefix[1] = 'P';
+	out_data->prefix[2] = '2';
+	out_data->driver_id = GUINT16_TO_LE(data->driver_id);
+	out_data->devtype = GUINT32_TO_LE(data->devtype);
+	out_data->data_type = data->type;
+
+	list_item = data->prints;
+	while (list_item) {
+		item = list_item->data;
+		out_item = (struct fpi_print_data_item_fp2 *)buf;
+		out_item->length = GUINT32_TO_LE(item->length);
+		/* FIXME: fp_print_data_item->data content is not endianess agnostic */
+		memcpy(out_item->data, item->data, item->length);
+		buf += sizeof(*out_item);
+		buf += item->length;
+		list_item = g_slist_next(list_item);
+	}
+
 	return buflen;
 }
 
+static struct fp_print_data *fpi_print_data_from_fp1_data(unsigned char *buf,
+	size_t buflen)
+{
+	size_t print_data_len;
+	struct fp_print_data *data;
+	struct fp_print_data_item *item;
+	struct fpi_print_data_fp2 *raw = (struct fpi_print_data_fp2 *) buf;
+
+	print_data_len = buflen - sizeof(*raw);
+	data = print_data_new(GUINT16_FROM_LE(raw->driver_id),
+		GUINT32_FROM_LE(raw->devtype), raw->data_type);
+	item = fpi_print_data_item_new(print_data_len);
+	/* FIXME: fp_print_data->data content is not endianess agnostic */
+	memcpy(item->data, raw->data, print_data_len);
+	data->prints = g_slist_prepend(data->prints, item);
+
+	return data;
+}
+
+static struct fp_print_data *fpi_print_data_from_fp2_data(unsigned char *buf,
+	size_t buflen)
+{
+	size_t total_data_len, item_len;
+	struct fp_print_data *data;
+	struct fp_print_data_item *item;
+	struct fpi_print_data_fp2 *raw = (struct fpi_print_data_fp2 *) buf;
+	unsigned char *raw_buf;
+	struct fpi_print_data_item_fp2 *raw_item;
+
+	total_data_len = buflen - sizeof(*raw);
+	data = print_data_new(GUINT16_FROM_LE(raw->driver_id),
+		GUINT32_FROM_LE(raw->devtype), raw->data_type);
+	raw_buf = raw->data;
+	while (total_data_len) {
+		if (total_data_len < sizeof(*raw_item))
+			break;
+		total_data_len -= sizeof(*raw_item);
+
+		raw_item = (struct fpi_print_data_item_fp2 *)raw_buf;
+		item_len = GUINT32_FROM_LE(raw_item->length);
+		fp_dbg("item len %d, total_data_len %d", item_len, total_data_len);
+		if (total_data_len < item_len) {
+			fp_err("corrupted fingerprint data");
+			break;
+		}
+		total_data_len -= item_len;
+
+		item = fpi_print_data_item_new(item_len);
+		/* FIXME: fp_print_data->data content is not endianess agnostic */
+		memcpy(item->data, raw_item->data, item_len);
+		data->prints = g_slist_prepend(data->prints, item);
+
+		raw_buf += sizeof(*raw_item);
+		raw_buf += item_len;
+	}
+
+	if (g_slist_length(data->prints) == 0) {
+		fp_print_data_free(data);
+		data = NULL;
+	}
+
+	return data;
+
+}
+
 /** \ingroup print_data
  * Load a stored print from a data buffer. The contents of said buffer must
  * be the untouched contents of a buffer previously supplied to you by the
@@ -157,24 +257,21 @@ API_EXPORTED size_t fp_print_data_get_data(struct fp_print_data *data,
 API_EXPORTED struct fp_print_data *fp_print_data_from_data(unsigned char *buf,
 	size_t buflen)
 {
-	struct fpi_print_data_fp1 *raw = (struct fpi_print_data_fp1 *) buf;
-	size_t print_data_len;
-	struct fp_print_data *data;
+	struct fpi_print_data_fp2 *raw = (struct fpi_print_data_fp2 *) buf;
 
 	fp_dbg("buffer size %zd", buflen);
 	if (buflen < sizeof(*raw))
 		return NULL;
 
-	if (strncmp(raw->prefix, "FP1", 3) != 0) {
+	if (strncmp(raw->prefix, "FP1", 3) == 0) {
+		return fpi_print_data_from_fp1_data(buf, buflen);
+	} else if (strncmp(raw->prefix, "FP2", 3) == 0) {
+		return fpi_print_data_from_fp2_data(buf, buflen);
+	} else {
 		fp_dbg("bad header prefix");
-		return NULL;
 	}
 
-	print_data_len = buflen - sizeof(*raw);
-	data = print_data_new(GUINT16_FROM_LE(raw->driver_id),
-		GUINT32_FROM_LE(raw->devtype), raw->data_type, print_data_len);
-	memcpy(data->data, raw->data, print_data_len);
-	return data;
+	return NULL;
 }
 
 static char *get_path_to_storedir(uint16_t driver_id, uint32_t devtype)
@@ -405,6 +502,8 @@ API_EXPORTED int fp_print_data_from_dscv_print(struct fp_dscv_print *print,
  */
 API_EXPORTED void fp_print_data_free(struct fp_print_data *data)
 {
+	if (data)
+		g_slist_free_full(data->prints, (GDestroyNotify)fpi_print_data_item_free);
 	g_free(data);
 }
 
diff --git a/libfprint/drivers/upeke2.c b/libfprint/drivers/upeke2.c
index 83fe93f..f685205 100644
--- a/libfprint/drivers/upeke2.c
+++ b/libfprint/drivers/upeke2.c
@@ -1072,6 +1072,7 @@ static void e_handle_resp02(struct fp_dev *dev, unsigned char *data,
 	size_t data_len)
 {
 	struct fp_print_data *fdata = NULL;
+	struct fp_print_data_item *item = NULL;
 	int result = -EPROTO;
 
 	if (data_len < sizeof(scan_comp)) {
@@ -1080,9 +1081,11 @@ static void e_handle_resp02(struct fp_dev *dev, unsigned char *data,
 		fp_err("unrecognised data prefix %x %x %x %x %x",
 			data[0], data[1], data[2], data[3], data[4]);
 	} else {
-		fdata = fpi_print_data_new(dev, data_len - sizeof(scan_comp));
-		memcpy(fdata->data, data + sizeof(scan_comp),
+		fdata = fpi_print_data_new(dev);
+		item = fpi_print_data_item_new(data_len - sizeof(scan_comp));
+		memcpy(item->data, data + sizeof(scan_comp),
 			data_len - sizeof(scan_comp));
+		fdata->prints = g_slist_prepend(fdata->prints, item);
 
 		result = FP_ENROLL_COMPLETE;
 	}
@@ -1244,12 +1247,13 @@ static void verify_start_sm_run_state(struct fpi_ssm *ssm)
 		break;
 	case VERIFY_INIT: ;
 		struct fp_print_data *print = dev->verify_data;
-		size_t data_len = sizeof(verify_hdr) + print->length;
+		struct fp_print_data_item *item = print->prints->data;
+		size_t data_len = sizeof(verify_hdr) + item->length;
 		unsigned char *data = g_malloc(data_len);
 		struct libusb_transfer *transfer;
 
 		memcpy(data, verify_hdr, sizeof(verify_hdr));
-		memcpy(data + sizeof(verify_hdr), print->data, print->length);
+		memcpy(data + sizeof(verify_hdr), item->data, item->length);
 		transfer = alloc_send_cmd28_transfer(dev, 0x03, data, data_len,
 			verify_init_2803_cb, ssm);
 		g_free(data);
diff --git a/libfprint/drivers/upekts.c b/libfprint/drivers/upekts.c
index b347949..c191a2d 100644
--- a/libfprint/drivers/upekts.c
+++ b/libfprint/drivers/upekts.c
@@ -1077,6 +1077,7 @@ static void e_handle_resp02(struct fp_dev *dev, unsigned char *data,
 	size_t data_len)
 {
 	struct fp_print_data *fdata = NULL;
+	struct fp_print_data_item *item = NULL;
 	int result = -EPROTO;
 
 	if (data_len < sizeof(scan_comp)) {
@@ -1085,9 +1086,11 @@ static void e_handle_resp02(struct fp_dev *dev, unsigned char *data,
 		fp_err("unrecognised data prefix %x %x %x %x %x",
 			data[0], data[1], data[2], data[3], data[4]);
 	} else {
-		fdata = fpi_print_data_new(dev, data_len - sizeof(scan_comp));
-		memcpy(fdata->data, data + sizeof(scan_comp),
+		fdata = fpi_print_data_new(dev);
+		item = fpi_print_data_item_new(data_len - sizeof(scan_comp));
+		memcpy(item->data, data + sizeof(scan_comp),
 			data_len - sizeof(scan_comp));
+		fdata->prints = g_slist_prepend(fdata->prints, item);
 
 		result = FP_ENROLL_COMPLETE;
 	}
@@ -1249,12 +1252,13 @@ static void verify_start_sm_run_state(struct fpi_ssm *ssm)
 		break;
 	case VERIFY_INIT: ;
 		struct fp_print_data *print = dev->verify_data;
-		size_t data_len = sizeof(verify_hdr) + print->length;
+		struct fp_print_data_item *item = print->prints->data;
+		size_t data_len = sizeof(verify_hdr) + item->length;
 		unsigned char *data = g_malloc(data_len);
 		struct libusb_transfer *transfer;
 
 		memcpy(data, verify_hdr, sizeof(verify_hdr));
-		memcpy(data + sizeof(verify_hdr), print->data, print->length);
+		memcpy(data + sizeof(verify_hdr), item->data, item->length);
 		transfer = alloc_send_cmd28_transfer(dev, 0x03, data, data_len,
 			verify_init_2803_cb, ssm);
 		g_free(data);
diff --git a/libfprint/fp_internal.h b/libfprint/fp_internal.h
index 42d38f1..8af0282 100644
--- a/libfprint/fp_internal.h
+++ b/libfprint/fp_internal.h
@@ -179,7 +179,9 @@ struct fp_img_dev {
 	int action_state;
 
 	struct fp_print_data *acquire_data;
+	struct fp_print_data *enroll_data;
 	struct fp_img *acquire_img;
+	int enroll_stage;
 	int action_result;
 
 	/* FIXME: better place to put this? */
@@ -325,15 +327,19 @@ enum fp_print_data_type {
 	PRINT_DATA_NBIS_MINUTIAE,
 };
 
+struct fp_print_data_item {
+	size_t length;
+	unsigned char data[0];
+};
+
 struct fp_print_data {
 	uint16_t driver_id;
 	uint32_t devtype;
 	enum fp_print_data_type type;
-	size_t length;
-	unsigned char data[0];
+	GSList *prints;
 };
 
-struct fpi_print_data_fp1 {
+struct fpi_print_data_fp2 {
 	char prefix[3];
 	uint16_t driver_id;
 	uint32_t devtype;
@@ -341,8 +347,14 @@ struct fpi_print_data_fp1 {
 	unsigned char data[0];
 } __attribute__((__packed__));
 
+struct fpi_print_data_item_fp2 {
+	uint32_t length;
+	unsigned char data[0];
+} __attribute__((__packed__));
+
 void fpi_data_exit(void);
-struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length);
+struct fp_print_data *fpi_print_data_new(struct fp_dev *dev);
+struct fp_print_data_item *fpi_print_data_item_new(size_t length);
 gboolean fpi_print_data_compatible(uint16_t driver_id1, uint32_t devtype1,
 	enum fp_print_data_type type1, uint16_t driver_id2, uint32_t devtype2,
 	enum fp_print_data_type type2);
diff --git a/libfprint/img.c b/libfprint/img.c
index b1d32ed..3c91d93 100644
--- a/libfprint/img.c
+++ b/libfprint/img.c
@@ -313,6 +313,7 @@ int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
 	struct fp_print_data **ret)
 {
 	struct fp_print_data *print;
+	struct fp_print_data_item *item;
 	int r;
 
 	if (!img->minutiae) {
@@ -327,9 +328,11 @@ int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
 
 	/* FIXME: space is wasted if we dont hit the max minutiae count. would
 	 * be good to make this dynamic. */
-	print = fpi_print_data_new(imgdev->dev, sizeof(struct xyt_struct));
+	print = fpi_print_data_new(imgdev->dev);
+	item = fpi_print_data_item_new(sizeof(struct xyt_struct));
 	print->type = PRINT_DATA_NBIS_MINUTIAE;
-	minutiae_to_xyt(img->minutiae, img->width, img->height, print->data);
+	minutiae_to_xyt(img->minutiae, img->width, img->height, item->data);
+	print->prints = g_slist_prepend(print->prints, item);
 
 	/* FIXME: the print buffer at this point is endian-specific, and will
 	 * only work when loaded onto machines with identical endianness. not good!
@@ -342,42 +345,73 @@ int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
 int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
 	struct fp_print_data *new_print)
 {
-	struct xyt_struct *gstruct = (struct xyt_struct *) enrolled_print->data;
-	struct xyt_struct *pstruct = (struct xyt_struct *) new_print->data;
-	GTimer *timer;
-	int r;
+	int score, max_score = 0, probe_len;
+	struct xyt_struct *pstruct = NULL;
+	struct xyt_struct *gstruct = NULL;
+	struct fp_print_data_item *data_item;
+	GSList *list_item;
 
 	if (enrolled_print->type != PRINT_DATA_NBIS_MINUTIAE ||
-			new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
+	     new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
 		fp_err("invalid print format");
 		return -EINVAL;
 	}
 
-	timer = g_timer_new();
-	r = bozorth_main(pstruct, gstruct);
-	g_timer_stop(timer);
-	fp_dbg("bozorth processing took %f seconds, score=%d",
-		g_timer_elapsed(timer, NULL), r);
-	g_timer_destroy(timer);
+	if (g_slist_length(new_print->prints) != 1) {
+		fp_err("new_print contains more than one sample, is it enrolled print?");
+		return -EINVAL;
+	}
 
-	return r;
+	data_item = new_print->prints->data;
+	pstruct = (struct xyt_struct *)data_item->data;
+
+	probe_len = bozorth_probe_init(pstruct);
+	list_item = enrolled_print->prints;
+	do {
+		data_item = list_item->data;
+		gstruct = (struct xyt_struct *)data_item->data;
+		score = bozorth_to_gallery(probe_len, pstruct, gstruct);
+		fp_dbg("score %d", score);
+		max_score = max(score, max_score);
+		list_item = g_slist_next(list_item);
+	} while (list_item);
+
+	return max_score;
 }
 
 int fpi_img_compare_print_data_to_gallery(struct fp_print_data *print,
 	struct fp_print_data **gallery, int match_threshold, size_t *match_offset)
 {
-	struct xyt_struct *pstruct = (struct xyt_struct *) print->data;
+	struct xyt_struct *pstruct;
+	struct xyt_struct *gstruct;
 	struct fp_print_data *gallery_print;
-	int probe_len = bozorth_probe_init(pstruct);
+	struct fp_print_data_item *data_item;
+	int probe_len;
 	size_t i = 0;
+	int r;
+	GSList *list_item;
+
+	if (g_slist_length(print->prints) != 1) {
+		fp_err("new_print contains more than one sample, is it enrolled print?");
+		return -EINVAL;
+	}
+
+	data_item = print->prints->data;
+	pstruct = (struct xyt_struct *)data_item->data;
 
+	probe_len = bozorth_probe_init(pstruct);
 	while ((gallery_print = gallery[i++])) {
-		struct xyt_struct *gstruct = (struct xyt_struct *) gallery_print->data;
-		int r = bozorth_to_gallery(probe_len, pstruct, gstruct);
-		if (r >= match_threshold) {
-			*match_offset = i - 1;
-			return FP_VERIFY_MATCH;
-		}
+		list_item = gallery_print->prints;
+		do {
+			data_item = list_item->data;
+			gstruct = (struct xyt_struct *)data_item->data;
+			r = bozorth_to_gallery(probe_len, pstruct, gstruct);
+			if (r >= match_threshold) {
+				*match_offset = i - 1;
+				return FP_VERIFY_MATCH;
+			}
+			list_item = g_slist_next(list_item);
+		} while (list_item);
 	}
 	return FP_VERIFY_NO_MATCH;
 }
diff --git a/libfprint/imgdev.c b/libfprint/imgdev.c
index f83ea11..f960ee3 100644
--- a/libfprint/imgdev.c
+++ b/libfprint/imgdev.c
@@ -25,6 +25,7 @@
 
 #define MIN_ACCEPTABLE_MINUTIAE 10
 #define BOZORTH3_DEFAULT_THRESHOLD 40
+#define IMG_ENROLL_STAGES 5
 
 static int img_dev_open(struct fp_dev *dev, unsigned long driver_data)
 {
@@ -33,8 +34,9 @@ static int img_dev_open(struct fp_dev *dev, unsigned long driver_data)
 	int r = 0;
 
 	imgdev->dev = dev;
+	imgdev->enroll_stage = 0;
 	dev->priv = imgdev;
-	dev->nr_enroll_stages = 1;
+	dev->nr_enroll_stages = IMG_ENROLL_STAGES;
 
 	/* for consistency in driver code, allow udev access through imgdev */
 	imgdev->udev = dev->udev;
@@ -144,7 +146,13 @@ void fpi_imgdev_report_finger_status(struct fp_img_dev *imgdev,
 	switch (imgdev->action) {
 	case IMG_ACTION_ENROLL:
 		fp_dbg("reporting enroll result");
-		fpi_drvcb_enroll_stage_completed(imgdev->dev, r, data, img);
+		data = imgdev->enroll_data;
+		if (r == FP_ENROLL_COMPLETE) {
+			imgdev->enroll_data = NULL;
+		}
+		fpi_drvcb_enroll_stage_completed(imgdev->dev, r,
+			r == FP_ENROLL_COMPLETE ? data : NULL,
+			img);
 		/* the callback can cancel enrollment, so recheck current
 		 * action and the status to see if retry is needed */
 		if (imgdev->action == IMG_ACTION_ENROLL &&
@@ -253,7 +261,22 @@ void fpi_imgdev_image_captured(struct fp_img_dev *imgdev, struct fp_img *img)
 	imgdev->acquire_data = print;
 	switch (imgdev->action) {
 	case IMG_ACTION_ENROLL:
-		imgdev->action_result = FP_ENROLL_COMPLETE;
+		if (!imgdev->enroll_data) {
+			imgdev->enroll_data = fpi_print_data_new(imgdev->dev);
+		}
+		BUG_ON(g_slist_length(print->prints) != 1);
+		/* Move print data from acquire data into enroll_data */
+		imgdev->enroll_data->prints =
+			g_slist_prepend(imgdev->enroll_data->prints, print->prints->data);
+		print->prints = g_slist_remove(print->prints, print->prints->data);
+
+		fp_print_data_free(imgdev->acquire_data);
+		imgdev->acquire_data = NULL;
+		imgdev->enroll_stage++;
+		if (imgdev->enroll_stage == imgdev->dev->nr_enroll_stages)
+			imgdev->action_result = FP_ENROLL_COMPLETE;
+		else
+			imgdev->action_result = FP_ENROLL_PASS;
 		break;
 	case IMG_ACTION_VERIFY:
 		verify_process_img(imgdev);
@@ -402,6 +425,7 @@ static int generic_acquire_start(struct fp_dev *dev, int action)
 	fp_dbg("action %d", action);
 	imgdev->action = action;
 	imgdev->action_state = IMG_ACQUIRE_STATE_ACTIVATING;
+	imgdev->enroll_stage = 0;
 
 	r = dev_activate(imgdev, IMGDEV_STATE_AWAIT_FINGER_ON);
 	if (r < 0)
@@ -417,8 +441,10 @@ static void generic_acquire_stop(struct fp_img_dev *imgdev)
 	dev_deactivate(imgdev);
 
 	fp_print_data_free(imgdev->acquire_data);
+	fp_print_data_free(imgdev->enroll_data);
 	fp_img_free(imgdev->acquire_img);
 	imgdev->acquire_data = NULL;
+	imgdev->enroll_data = NULL;
 	imgdev->acquire_img = NULL;
 	imgdev->action_result = 0;
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/fingerforce/libfprint.git



More information about the Fingerforce-commits mailing list