aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-04-04 13:37:31 +0200
committerMattias Andrée <maandree@kth.se>2021-04-04 13:37:31 +0200
commitd831e279cb856ec720ed770ce9d0c39eb347b7e1 (patch)
tree51dac38fdba5dfdee06a46fa7bed3dc0f01c035f
parentmisc (diff)
downloadcontacts-d831e279cb856ec720ed770ce9d0c39eb347b7e1.tar.gz
contacts-d831e279cb856ec720ed770ce9d0c39eb347b7e1.tar.bz2
contacts-d831e279cb856ec720ed770ce9d0c39eb347b7e1.tar.xz
m + add list-birthdays and set-contact-birthday
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--Makefile37
-rw-r--r--TODO5
-rw-r--r--common.h18
-rw-r--r--config.mk2
-rw-r--r--contacts.c.in4
-rw-r--r--get-contact-birthday.c21
-rw-r--r--list-birthdays.c252
-rw-r--r--multicall-hardlinks.mk12
-rw-r--r--multicall-symlinks.mk5
-rw-r--r--set-contact-birthday.c131
-rw-r--r--singlecall.mk10
11 files changed, 444 insertions, 53 deletions
diff --git a/Makefile b/Makefile
index 5ea2d5b..e4ef9ab 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,12 @@
CONFIGFILE = config.mk
include $(CONFIGFILE)
+CALLTYPE = multicall-hardlinks
+# multicall-hardlinks = multiple hardlinks of the same multicall binary is installed
+# multicall-symlinks = multiple links to a multicall binary named $(PREFIX)/bin/contacts are installed
+# singlecall = separate binaries are install for each command (greatly wastes space when statically linked)
+include $(CALLTYPE).mk
+
BIN =\
add-contact\
@@ -28,6 +34,7 @@ BIN =\
get-contact-photos\
get-contact-sites\
is-contact-ice\
+ list-birthdays\
list-chat-contacts\
list-contact-groups\
list-contact-organisations\
@@ -36,6 +43,7 @@ BIN =\
list-organisation-contacts\
print-contact\
remove-contact\
+ set-contact-birthday\
set-contact-chats\
set-contact-emails\
set-contact-gender\
@@ -56,7 +64,6 @@ OBJ = $(BIN:=.o)
BOBJ = $(BIN:=.bo)
-all: $(BIN) contacts
$(OBJ): $(@:.o=.c) $(HDR)
$(BOBJ): $(@:.bo=.c) $(HDR)
@@ -64,7 +71,7 @@ $(BOBJ): $(@:.bo=.c) $(HDR)
$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
.c.bo:
- $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -Dmain="$$(printf '%s\n' $*_main | tr - _)" -DMULTICALL_BINARY
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -Dmain="$$(printf '%s\n' main__$* | tr - _)" -DMULTICALL_BINARY
contacts: contacts.o $(BOBJ)
$(CC) -o $@ $@.o $(BOBJ) $(LDFLAGS)
@@ -210,30 +217,6 @@ set-contact-photos: set-contact-photos.o
set-contact-sites: set-contact-sites.o
$(CC) -o $@ $@.o $(LDFLAGS)
-install: $(BIN)
- mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
- cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/"
-
-install-mcb: contacts
- mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
- set -- $(BIN) &&\
- cp -- "$$1" "$(DESTDIR)$(PREFIX)/bin/$$1" &&\
- linkto="$$1" &&\
- shift 1 &&\
- cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
- for f; do\
- ln -- "$$linkto" "$$f" || exit 1;\
- done
-
-install-mcb-symlinks: contacts
- mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
- mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
- cp -- contacts "$(DESTDIR)$(PREFIX)/lib/"
- cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
- for f in $(BIN); do\
- ln -s -- ../lib/contacts "$$f" || exit 1;\
- done
-
uninstall:
-cd -- "$(DESTDIR)$(PREFIX)/bin" && rm -f -- $(BIN)
-rm -f -- "$(DESTDIR)$(PREFIX)/lib/contacts"
@@ -244,4 +227,4 @@ clean:
.SUFFIXES:
.SUFFIXES: .c .o .bo
-.PHONY: all install install-mcb install-mcb-symlinks uninstall clean
+.PHONY: all install uninstall clean
diff --git a/TODO b/TODO
index f0c6588..fd5b32e 100644
--- a/TODO
+++ b/TODO
@@ -2,9 +2,6 @@ Add tools for checking context for contact info
Add tools for .blocks
Add tools for .addresses
-list-birthdays
-set-contact-birthday
-
Test find-contact-by-chat
Test find-contact-by-email
Test find-contact-by-name
@@ -13,7 +10,6 @@ Test find-contact-by-organisation
Test find-contact-by-pgpkey
Test find-contact-by-photo
Test find-contact-by-site
-Test get-contact-birthday
Test get-contact-chats
Test get-contact-emails
Test get-contact-name
@@ -22,7 +18,6 @@ Test get-contact-organisations
Test get-contact-pgpkeys
Test get-contact-photos
Test get-contact-sites
-Test list-birthdays
Test list-chat-contacts
Test list-contact-organisations
Test list-organisation-contacts
diff --git a/common.h b/common.h
index ca6db51..3fcd407 100644
--- a/common.h
+++ b/common.h
@@ -1,4 +1,8 @@
/* See LICENSE file for copyright and license details. */
+#ifdef MULTICALL_BINARY
+# define LIBSIMPLY_CONFIG_MULTICALL_BINARY
+#endif
+
#include <libcontacts.h>
#include <libsimple.h>
#include <libsimple-arg.h>
@@ -7,17 +11,3 @@
#ifndef BUFSIZ
# define BUFSIZ 4096
#endif
-
-
-#ifdef MULTICALL_BINARY
-# undef NUSAGE
-# define NUSAGE(STATUS, SYNOPSIS)\
- static _LIBSIMPLE_NORETURN void\
- usage(void)\
- {\
- const char *syn = SYNOPSIS ? SYNOPSIS : "";\
- fprintf(stderr, "usage: %s%s%s\n", argv0, *syn ? " " : "", syn);\
- exit(STATUS);\
- }\
- extern char *argv0
-#endif
diff --git a/config.mk b/config.mk
index 7145091..2c119c1 100644
--- a/config.mk
+++ b/config.mk
@@ -5,4 +5,4 @@ CC = cc
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -I../libcontacts
CFLAGS = -std=c99 -Wall -O2
-LDFLAGS = -L../libcontacts -lcontacts -lsimple
+LDFLAGS = -s -L../libcontacts -lcontacts -lsimple
diff --git a/contacts.c.in b/contacts.c.in
index 5e470ac..b297951 100644
--- a/contacts.c.in
+++ b/contacts.c.in
@@ -2,7 +2,7 @@
#include <stdio.h>
#include <string.h>
-#define X(NAM) int NAM##_main(int, char *[]);
+#define X(NAM) int main__##NAM(int, char *[]);
LIST_COMMANDS
#undef X
@@ -21,7 +21,7 @@ main(int argc, char *argv[])
#define X(NAM)\
if (!strcmp(name, #NAM))\
- return NAM##_main(argc, argv);
+ return main__##NAM(argc, argv);
LIST_COMMANDS;
#undef X
diff --git a/get-contact-birthday.c b/get-contact-birthday.c
index 29be768..173d6a2 100644
--- a/get-contact-birthday.c
+++ b/get-contact-birthday.c
@@ -18,7 +18,8 @@ get_age(struct libcontacts_birthday *bday, const struct tm *now)
static void
print_birthdate(struct libcontacts_birthday *bday, const struct tm *now)
{
- int age;
+ static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int age, days = 0, y, m, d;
if (bday->year)
printf("%04u-", bday->year);
else
@@ -39,7 +40,20 @@ print_birthdate(struct libcontacts_birthday *bday, const struct tm *now)
}
if (bday->year && bday->month && bday->day) {
age = get_age(bday, now);
- printf(", %i %s old today", age, age == 1 ? "year" : "years");
+ y = (int)bday->year - 1900;
+ m = (int)bday->month - 1;
+ d = (int)bday->day;
+ while (m != now->tm_mon || d > now->tm_mday) {
+ days += days_in_month[m] - (d - 1);
+ days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0));
+ d = 1;
+ if (++m == 12) {
+ y += 1;
+ m = 0;
+ }
+ }
+ days += now->tm_mday - d;
+ printf(", %i %s and %i %s old", age, age == 1 ? "year" : "years", days, days == 1 ? "day" : "days");
}
printf("\n");
}
@@ -70,7 +84,7 @@ print_birthday(struct libcontacts_birthday *bday, const struct tm *now)
bday->month += 1;
}
}
- printf("(%02u)%.3s-%02u (", (unsigned)bday->month % 12,
+ printf("(%02u)%.3s-%02u (", (unsigned)bday->month,
&"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)], (unsigned)bday->day);
when.tm_year = now->tm_year + next_year;
when.tm_mon = (bday->month - 1) % 12;
@@ -97,7 +111,6 @@ print_birthday(struct libcontacts_birthday *bday, const struct tm *now)
days += when.tm_mday - d;
printf("in %i %s)\n", days, days == 1 ? "day" : "days");
}
-
}
int
diff --git a/list-birthdays.c b/list-birthdays.c
new file mode 100644
index 0000000..950df2a
--- /dev/null
+++ b/list-birthdays.c
@@ -0,0 +1,252 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+USAGE("[-n] (-L | contact-id ...)");
+
+
+static int
+get_age(struct libcontacts_birthday *bday, const struct tm *now)
+{
+ int age = now->tm_year + 1900 - (int)bday->year;
+ if (now->tm_mon + 1 < bday->month)
+ age -= 1;
+ else if (now->tm_mon + 1 == bday->month)
+ age -= (now->tm_mday < bday->day);
+ return age;
+}
+
+static void
+print_birthdate(struct libcontacts_birthday *bday, const struct tm *now)
+{
+ static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int age, days = 0, y, m, d;
+ if (bday->year)
+ printf("%04u-", bday->year);
+ else
+ printf("??""??""-");
+ if (bday->month) {
+ printf("(%02u)%.3s-", (unsigned)bday->month,
+ &"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)]);
+ } else {
+ printf("(??"")??""?-");
+ }
+ if (bday->day)
+ printf("%02u", (unsigned)bday->day);
+ else
+ printf("??");
+ if ((bday->month - 1) % 12 == 1 && bday->day == 29) {
+ bday->day -= bday->before_on_common;
+ printf(" (%s on common years)", bday->before_on_common ? "(02)Feb-28" : "(03)Mar-01");
+ }
+ if (bday->year && bday->month && bday->day) {
+ age = get_age(bday, now);
+ y = (int)bday->year - 1900;
+ m = (int)bday->month - 1;
+ d = (int)bday->day;
+ while (m != now->tm_mon || d > now->tm_mday) {
+ days += days_in_month[m] - (d - 1);
+ days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0));
+ d = 1;
+ if (++m == 12) {
+ y += 1;
+ m = 0;
+ }
+ }
+ days += now->tm_mday - d;
+ printf(", %i %s and %i %s old", age, age == 1 ? "year" : "years", days, days == 1 ? "day" : "days");
+ }
+ printf("\n");
+}
+
+static void
+print_birthday(struct libcontacts_birthday *bday, const struct tm *now)
+{
+ static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int next_year = 0, leap_year = 0, year, age, days = 0, y, m, d;
+ struct tm when;
+ if (now->tm_mon + 1 > bday->month)
+ next_year = 1;
+ else if (now->tm_mon + 1 == bday->month)
+ next_year = (now->tm_mday > bday->day);
+ if (bday->year) {
+ printf("%04i-", now->tm_year + next_year + 1900);
+ } else {
+ printf("??""??""-");
+ }
+ year = now->tm_year + next_year + 1900;
+ if (year % 4 == 0 && (year % 100 || year % 400 == 0))
+ leap_year = 1;
+ if (((bday->month - 1) % 12 == 1 && bday->day == 29) && leap_year) {
+ if (bday->before_on_common) {
+ bday->day -= 1;
+ } else {
+ bday->day = 1;
+ bday->month += 1;
+ }
+ }
+ printf("(%02u)%.3s-%02u (", (unsigned)bday->month,
+ &"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)], (unsigned)bday->day);
+ when.tm_year = now->tm_year + next_year;
+ when.tm_mon = (bday->month - 1) % 12;
+ when.tm_mday = bday->day;
+ if (bday->year) {
+ age = get_age(bday, &when);
+ printf("%i %s ", age, age == 1 ? "year" : "years");
+ }
+ if (when.tm_mon == now->tm_mon && when.tm_mday == now->tm_mday) {
+ printf("today)\n");
+ } else {
+ y = now->tm_year;
+ m = now->tm_mon;
+ d = now->tm_mday;
+ while (m != when.tm_mon || d > when.tm_mday) {
+ days += days_in_month[m] - (d - 1);
+ days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0));
+ d = 1;
+ if (++m == 12) {
+ y += 1;
+ m = 0;
+ }
+ }
+ days += when.tm_mday - d;
+ printf("in %i %s)\n", days, days == 1 ? "day" : "days");
+ }
+}
+
+static int
+compare_by_birthdate(const void *apv, const void *bpv)
+{
+ struct libcontacts_contact *const *ap = apv, *const *bp = bpv;
+ const struct libcontacts_contact *a = *ap, *b = *bp;
+ if (!a->birthday != !b->birthday)
+ return !a->birthday ? -1 : +1;
+ if (!a->birthday)
+ return 0;
+ if (a->birthday->year != b->birthday->year)
+ return a->birthday->year < b->birthday->year ? -1 : +1;
+ if (a->birthday->month != b->birthday->month)
+ return a->birthday->month < b->birthday->month ? -1 : +1;
+ if (a->birthday->day != b->birthday->day)
+ return a->birthday->day < b->birthday->day ? -1 : +1;
+ if (a->birthday->before_on_common != b->birthday->before_on_common)
+ return a->birthday->before_on_common ? -1 : +1;
+ return 0;
+}
+
+static struct tm *now;
+
+static int
+compare_by_birthday(const void *apv, const void *bpv)
+{
+ struct libcontacts_contact *const *ap = apv, *const *bp = bpv;
+ const struct libcontacts_contact *a = *ap, *b = *bp;
+ int ac, bc;
+ if (!a->birthday != !b->birthday)
+ return !a->birthday ? -1 : +1;
+ if (!a->birthday)
+ return 0;
+ if (!a->birthday->month != !b->birthday->month)
+ return !a->birthday->month ? -1 : +1;
+ if (!a->birthday->month)
+ return 0;
+ if (!a->birthday->day != !b->birthday->day)
+ return !a->birthday->day ? -1 : +1;
+ if (!a->birthday->day)
+ return 0;
+ ac = (12 + (a->birthday->month - 1 - now->tm_mon) % 12) % 12;
+ bc = (12 + (b->birthday->month - 1 - now->tm_mon) % 12) % 12;
+ if (ac != bc)
+ return ac - bc;
+ ac = (31 + (a->birthday->day - now->tm_mday) % 31) % 31;
+ bc = (31 + (b->birthday->day - now->tm_mday) % 31) % 31;
+ if (ac != bc)
+ return ac - bc;
+ if (a->birthday->before_on_common != b->birthday->before_on_common)
+ return a->birthday->before_on_common ? -1 : +1;
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int next = 0, list = 0;
+ struct passwd *user;
+ struct libcontacts_contact **contacts;
+ time_t tim;
+ int ret = 0;
+ size_t i;
+
+ ARGBEGIN {
+ case 'n':
+ next = 1;
+ break;
+ case 'L':
+ list = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (list ? argc : !argc)
+ usage();
+
+ for (i = 0; argv[i]; i++)
+ if (!*argv[i] || strchr(argv[i], '/'))
+ usage();
+
+ errno = 0;
+ user = getpwuid(getuid());
+ if (!user)
+ eprintf("getpwuid: %s\n", errno ? strerror(errno) : "user does not exist");
+
+ tim = time(NULL);
+ now = localtime(&tim);
+ if (!now)
+ eprintf("localtime:");
+
+ if (list) {
+ if (libcontacts_load_contacts(&contacts, user))
+ eprintf("libcontacts_load_contacts:");
+ for (i = 0; contacts[i]; i++);
+ } else {
+ contacts = ecalloc((size_t)argc + 1, sizeof(*contacts));
+ for (i = 0; *argv; argv++) {
+ contacts[i] = emalloc(sizeof(**contacts));
+ if (libcontacts_load_contact(*argv, contacts[i], user)) {
+ weprintf("libcontacts_load_contact %s: %s\n", *argv,
+ errno ? strerror(errno) : "contact file is malformatted");
+ ret = 1;
+ free(contacts[i]);
+ } else {
+ i++;
+ }
+ }
+ contacts[i] = NULL;
+ }
+
+ if (next)
+ qsort(contacts, i, sizeof(*contacts), compare_by_birthday);
+ else
+ qsort(contacts, i, sizeof(*contacts), compare_by_birthdate);
+
+ for (i = 0; contacts[i]; i++) {
+ if (contacts[i]->birthday) {
+ if (next) {
+ if (argc != 1)
+ printf("%s: ", contacts[i]->id);
+ print_birthday(contacts[i]->birthday, now);
+ } else if (contacts[i]->birthday->month && contacts[i]->birthday->year) {
+ if (argc != 1)
+ printf("%s: ", contacts[i]->id);
+ print_birthdate(contacts[i]->birthday, now);
+ }
+ }
+ libcontacts_contact_destroy(contacts[i]);
+ free(contacts[i]);
+ }
+
+ if (fflush(stdout) || ferror(stdout) || fclose(stdout))
+ eprintf("printf:");
+ free(contacts);
+ return ret;
+}
diff --git a/multicall-hardlinks.mk b/multicall-hardlinks.mk
new file mode 100644
index 0000000..be6ca66
--- /dev/null
+++ b/multicall-hardlinks.mk
@@ -0,0 +1,12 @@
+all: contacts
+
+install: contacts
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ set -- $(BIN) &&\
+ cp -- "$$1" "$(DESTDIR)$(PREFIX)/bin/$$1" &&\
+ linkto="$$1" &&\
+ shift 1 &&\
+ cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
+ for f; do\
+ ln -- "$$linkto" "$$f" || exit 1;\
+ done
diff --git a/multicall-symlinks.mk b/multicall-symlinks.mk
new file mode 100644
index 0000000..314cecd
--- /dev/null
+++ b/multicall-symlinks.mk
@@ -0,0 +1,5 @@
+all: contacts
+
+install: $(BIN)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/"
diff --git a/set-contact-birthday.c b/set-contact-birthday.c
new file mode 100644
index 0000000..535e635
--- /dev/null
+++ b/set-contact-birthday.c
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+/* Date components are in different options to avoid date format confusion */
+USAGE("([-y year | -Y] [-m month | -M] [-d day | -D] [-b | -B] | -u) contact-id ...");
+
+
+static int
+getintarg(const char *arg)
+{
+ int ret = 0;
+ for (; isdigit(*arg); arg++) {
+ if (ret > (INT_MAX - (*arg & 15)) / 10)
+ usage();
+ ret = ret * 10 + (*arg & 15);
+ }
+ if (*arg)
+ usage();
+ return ret;
+}
+
+int
+main(int argc, char *argv[])
+{
+ static int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int year = 0, month = 0, day = 0, before_on_common = -1, remove = 0;
+ struct passwd *user;
+ struct libcontacts_contact contact;
+ int ret = 0;
+ size_t i;
+
+ ARGBEGIN {
+ case 'y':
+ if (year)
+ usage();
+ year = getintarg(ARG());
+ break;
+ case 'Y':
+ if (year)
+ usage();
+ year = -1;
+ break;
+ case 'm':
+ if (month)
+ usage();
+ month = getintarg(ARG());
+ break;
+ case 'M':
+ if (month)
+ usage();
+ month = -1;
+ break;
+ case 'd':
+ if (day)
+ usage();
+ day = getintarg(ARG());
+ break;
+ case 'D':
+ if (day)
+ usage();
+ day = -1;
+ break;
+ case 'b':
+ if (before_on_common == 0)
+ usage();
+ before_on_common = 1;
+ case 'B':
+ if (before_on_common == 1)
+ usage();
+ before_on_common = 0;
+ case 'u':
+ remove = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if ((year | month | day | (before_on_common + 1)) && remove)
+ usage();
+ if (year % 4 == 0 && (year % 100 || year % 400 == 0)) /* 0 is a leap year */
+ days_in_month[1] += 1;
+ if ((month && month > 12) || day > days_in_month[month - 1])
+ usage();
+ if (!argc)
+ usage();
+
+ for (i = 0; argv[i]; i++)
+ if (!*argv[i] || strchr(argv[i], '/'))
+ usage();
+
+ errno = 0;
+ user = getpwuid(getuid());
+ if (!user)
+ eprintf("getpwuid: %s\n", errno ? strerror(errno) : "user does not exist");
+
+ for (; *argv; argv++) {
+ if (libcontacts_load_contact(*argv, &contact, user)) {
+ weprintf("libcontacts_load_contact %s: %s\n", *argv, errno ? strerror(errno) : "contact file is malformatted");
+ ret = 1;
+ } else {
+ if (remove) {
+ if (!contact.birthday)
+ goto next;
+ libcontacts_birthday_destroy(contact.birthday);
+ free(contact.birthday);
+ contact.birthday = NULL;
+ } else {
+ if (!contact.birthday)
+ contact.birthday = ecalloc(1, sizeof(*contact.birthday));
+ if (year)
+ contact.birthday->year = year < 0 ? 0 : (unsigned int)year;
+ if (month)
+ contact.birthday->month = month < 0 ? 0 : (unsigned char)month;
+ if (day)
+ contact.birthday->day = day < 0 ? 0 : (unsigned char)day;
+ if (before_on_common >= 0)
+ contact.birthday->before_on_common = before_on_common;
+ }
+ if (libcontacts_save_contact(&contact, user)) {
+ weprintf("libcontacts_save_contact %s:", *argv);
+ ret = 1;
+ }
+ next:
+ libcontacts_contact_destroy(&contact);
+ }
+ }
+
+ if (fflush(stdout) || ferror(stdout) || fclose(stdout))
+ eprintf("printf:");
+ return ret;
+}
diff --git a/singlecall.mk b/singlecall.mk
new file mode 100644
index 0000000..1e429f4
--- /dev/null
+++ b/singlecall.mk
@@ -0,0 +1,10 @@
+all: $(BIN)
+
+install: contacts
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- contacts "$(DESTDIR)$(PREFIX)/lib/"
+ cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
+ for f in $(BIN); do\
+ ln -s -- ../lib/contacts "$$f" || exit 1;\
+ done