From dfb56d6bef75283bf17170cee566169857f0abe7 Mon Sep 17 00:00:00 2001 From: mar Date: Mon, 20 Jun 1988 12:18:33 +0000 Subject: [PATCH] Initial revision --- gen/Makefile | 37 +++ gen/hesiod.qc | 704 ++++++++++++++++++++++++++++++++++++++++++++++++++ gen/util.c | 113 ++++++++ 3 files changed, 854 insertions(+) create mode 100644 gen/Makefile create mode 100644 gen/hesiod.qc create mode 100644 gen/util.c diff --git a/gen/Makefile b/gen/Makefile new file mode 100644 index 00000000..bf28cfe4 --- /dev/null +++ b/gen/Makefile @@ -0,0 +1,37 @@ +# $Header$ + +INGLIB=/usr/rtingres/lib/libqlib /usr/rtingres/lib/compatlib +CFLAGS= -I../include +.SUFFIXES: .qc + +.qc.c: + rm -f $*.c + /usr/rtingres/bin/eqc -p $* + +all: passwd.gen aliases.gen hesiod.gen + +passwd.gen: passwd.o util.o + cc -o passwd.gen passwd.o util.o ${INGLIB} + +passwd.o: passwd.c + +aliases.gen: aliases.o util.o + cc -o aliases.gen aliases.o util.o ../lib/libsms.a ${INGLIB} + +aliases.o: aliases.c + +hesiod.gen: hesiod.o util.o + cc -o hesiod.gen hesiod.o util.o ../lib/libsms.a ${INGLIB} + +hesiod.o: hesiod.c + +clean: + rm -f *.o + rm -f passwd.c passwd.gen + rm -f aliases.c aliases.gen + rm -f hesiod.c hesiod.gen + +install: all + install passwd.gen ../bin + install aliases.gen ../bin + install hesiod.gen ../bin diff --git a/gen/hesiod.qc b/gen/hesiod.qc new file mode 100644 index 00000000..5e667218 --- /dev/null +++ b/gen/hesiod.qc @@ -0,0 +1,704 @@ +/* $Header$ + * + * This generates the zone files necessary to load a hesiod server. + * The following zones are generated: + */ + +#include +#include +#include +#include +#include +#include + +#define HESIOD_DIR "/u1/sms/dcm/hesiod" + +#define min(x,y) ((x) < (y) ? (x) : (y)) + +char *malloc(), *strsave(); +char *ingres_date_and_time(), *ingres_time(), *ingres_date(); + +main(argc, argv) +int argc; +char **argv; +{ + char cmd[64]; + struct stat sb; + int changed = 0; + + if (argc > 2) { + fprintf(stderr, "usage: %s [outfile]\n", argv[0]); + exit(-1); + } + +## ingres sms +## set lockmode session where readlock = nolock + + changed = do_passwd(); + changed += do_pobox(); + changed += do_groups(); + changed += do_filsys(); + changed += do_cluster(); + changed += do_printers(); + changed += do_services(); + +## exit + + if (!changed) { + fprintf(stderr, "No files updated.\n"); + if (argc == 2 && stat(argv[1], &sb) == 0) + exit(0); + } + + if (argc == 2) { + sprintf(cmd, "cd %s; tar cf %s .", HESIOD_DIR, argv[1]); + exit(system(cmd)); + } + + exit(0); +} + + +do_passwd() +##{ + FILE *pout, *uout; + char poutf[64], uoutf[64]; + struct stat psb, usb; + time_t ftime; +## char login[9], shell[33], fullname[33], oa[17], op[13], hp[17], *filetime; +## int uid, flag; + + sprintf(poutf, "%s/passwd.db", HESIOD_DIR); + sprintf(uoutf, "%s/uid.db", HESIOD_DIR); + + if (stat(poutf, &psb) == 0 && stat(uoutf, &usb) == 0) { + ftime = min(psb.st_mtime, usb.st_mtime); + filetime = ingres_date_and_time(ftime); +## retrieve (flag = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "users" + if (flag < 0) { + fprintf(stderr, "Files passwd.db and uid.db do not need to be rebuilt.\n"); + return(0); + } + } + + pout = fopen(poutf, "w"); + if (!pout) { + perror("cannot open passwd.db for write"); + exit(-1); + } + uout = fopen(uoutf, "w"); + if (!uout) { + perror("cannot open uid.db for write"); + exit(-1); + } + + fprintf(stderr, "Building passwd.db and uid.db\n"); + +## range of u is users +## retrieve (login = u.#login, uid = u.#uid, shell = u.#shell, +## fullname = u.#fullname, oa = u.office_addr, +## op = u.office_phone, hp = u.home_phone) +## where u.status != 0 sort by #login { + trim(login); + trim(fullname); + trim(oa); + trim(op); + trim(hp); + trim(shell); + fprintf(pout, "%s.passwd\tHS UNSPECA \"%s:*:%d:101:%s,%s,%s,%s:/mit/%s:%s\"\n", + login, login, uid, fullname, oa, op, hp, login, shell); + fprintf(uout, "%d.uid\tHS CNAME %s.passwd\n", uid, login); +## } + + if (fclose(pout) || fclose(uout)) { + fprintf(stderr, "Unsuccessful file close of passwd.db or uid.db\n"); + exit(-1); + } + return(1); +##} + + +do_pobox() +##{ + FILE *out; + char outf[64]; + struct stat sb; + time_t ftime; +## char login[9], mach[33], *filetime; +## int flag1, flag2; + + sprintf(outf, "%s/pobox.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "users" +## retrieve (flag2 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "machine" + if (flag1 < 0 && flag2 < 0) { + fprintf(stderr,"File pobox.db does not need to be rebuilt.\n"); + return(0); + } + } + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open pobox.db for write"); + exit(-1); + } + + fprintf(stderr, "Building pobox.db\n"); + +## range of u is users +## range of m is machine +## retrieve (login = u.#login, mach = m.#name) +## where u.potype = "POP" and m.mach_id = u.pop_id { + trim(login); + trim(mach); + fprintf(out, "%s.pobox\tHS UNSPECA \"POP %s %s\"\n", + login, mach, login); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of pobox.db\n"); + exit(-1); + } + return(1); +##} + + +do_groups() +##{ + FILE *iout, *gout, *lout; + char ioutf[64], goutf[64], loutf[64], buf[256]; + char **groups; + struct stat isb, gsb, lsb; + time_t ftime; + struct save_queue *sq, *sq_create(); + register int first; +## char name[33], *filetime; +## int gid, id, lid, flag1, flag2, flag3, maxid; + + /* open files */ + sprintf(ioutf, "%s/gid.db", HESIOD_DIR); + sprintf(goutf, "%s/group.db", HESIOD_DIR); + sprintf(loutf, "%s/grplist.db", HESIOD_DIR); + + if (stat(ioutf, &isb) == 0 && stat(goutf, &gsb) == 0 && stat(loutf, &lsb) == 0) { + ftime = min(isb.st_mtime, min(gsb.st_mtime, lsb.st_mtime)); + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "users" +## retrieve (flag2 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "list" +## retrieve (flag3 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "members" + if (flag1 < 0 && flag2 < 0 && flag3 < 0) { + fprintf(stderr, "Files gid.db, group.db and grplist.db do not need to be rebuilt.\n"); + return(0); + } + } + + iout = fopen(ioutf, "w"); + if (!iout) { + perror("cannot open gid.db for write"); + exit(-1); + } + gout = fopen(goutf, "w"); + if (!gout) { + perror("cannot open group.db for write"); + exit(-1); + } + lout = fopen(loutf, "w"); + if (!lout) { + perror("cannot open grplist.db for write"); + exit(-1); + } + + fprintf(stderr, "Building gid.db, group.db, and grplist.db\n"); + + /* make space for group list */ +## range of l is list +## retrieve (maxid = max(l.#list_id)) + groups = (char **)malloc((maxid + 1) * sizeof(char *)); + if (groups == NULL) { + fprintf(stderr, "unable to malloc space for groups\n"); + exit(1); + } + bzero(groups, (maxid + 1) * sizeof(char *)); + + /* retrieve simple groups */ +## retrieve (name = l.#name, gid = l.#gid, lid = l.list_id) +## where l.group != 0 { + trim(name); + sprintf(buf, "%s:%d", name, gid); + groups[lid] = strsave(buf); + fprintf(iout, "%d.gid\tHS CNAME %s.group\n", gid, name); + fprintf(gout, "%s.group\tHS UNSPECA \"%s:*:%d:\"\n", + name, name, gid); +## } + + /* get special cases: lists that aren't groups themselves but are + * members of groups. */ + sq = sq_create(); +## range of m is members +## retrieve (name = l.#name, gid = l.#gid, lid = l.list_id) +## where l.group = 0 and m.member_type = "LIST" and +## m.member_id = l.list_id and m.list_id = list.list_id and +## list.group != 0 { + trim(name); + sprintf(buf, "%s:%d", name, gid); + groups[lid] = strsave(buf); + sq_save_data(sq, lid); +## } + while (sq_get_data(sq, &id)) { +## repeat retrieve (name = l.#name, gid = l.#gid, lid = l.list_id) +## where l.group = 0 and m.member_type = "LIST" and +## m.member_id = l.list_id and m.list_id = @id { + trim(name); + sprintf(buf, "%s:%d", name, gid); + groups[lid] = strsave(buf); + sq_save_unique_data(sq, lid); +## } + } + sq_destroy(sq); + + /* now do grplists */ + sq = sq_create(); +## range of u is users +## retrieve (id = u.users_id) where u.status != 0 { + sq_save_data(sq, id); +## } + while (sq_get_data(sq, &id)) { +## repeat retrieve (name = u.login) where u.users_id = @id + trim(name); + first = 1; +## repeat retrieve (lid = m.list_id) +## where m.member_type = "USER" and m.member_id = @id { + if (groups[lid]) { + if (first) + fprintf(lout, "%s.grplist\tHS UNSPECA \"%s",name,groups[lid]); + else + fprintf(lout, ":%s", groups[lid]); + first = 0; + } +## } + if (!first) + fprintf(lout, "\"\n"); + } + sq_destroy(sq); + for (id = 0; id < maxid; id++) + if (groups[id]) + free(groups[id]); + free(groups); + + if (fclose(iout) || fclose(gout) || fclose(lout)) { + fprintf(stderr, "Unsuccessful close of gid.db, group.db, or grplist.db\n"); + exit(-1); + } + return(1); +##} + + +do_filsys() +##{ + FILE *out; + char outf[64]; + struct stat sb; + time_t ftime; +## char name[33], type[9], loc[33], mach[33], access[2], mount[33], trans[257]; +## char *filetime; +## int flag1, flag2, flag3; + + sprintf(outf, "%s/filsys.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "filesys" +## retrieve (flag2 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "machine" +## retrieve (flag3 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "alias" + if (flag1 < 0 && flag2 < 0 && flag3 < 0) { + fprintf(stderr, "File filsys.db does not need to be rebuilt.\n"); + return(0); + } + } + + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open filsys.db for write"); + exit(-1); + } + + fprintf(stderr, "Building filsys.db\n"); + +## range of f is filesys +## range of m is machine +## retrieve (name = f.label, type = f.#type, loc = f.#name, mach = m.#name, +## access = f.#access, mount = f.#mount) +## where m.mach_id = f.mach_id { + trim(name); + trim(type); + trim(loc); + trim(mach); + trim(access); + trim(mount); + fprintf(out, "%s.filsys\tHS UNSPECA \"%s %s %s %s %s\"\n", + name, type, loc, mach, access, mount); +## } + +## range of a is alias +## retrieve (name = a.#name, trans = a.#trans) where a.#type = "FILESYS" { + trim(name); + trim(trans); + fprintf(out, "%s.filsys\tHS CNAME %s.filsys\n", name, trans); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of filsys.db\n"); + exit(-1); + } + return(1); +##} + + +/* + * Modified from sys/types.h: + */ +int setsize; /* = howmany(setbits, NSETBITS) */ + +typedef long set_mask; +#define NSETBITS (sizeof(set_mask) * NBBY) /* bits per mask */ +#ifndef howmany +#define howmany(x, y) (((x)+((y)-1))/(y)) +#endif + +#define SET_SET(n, p) ((p)[(n)/NSETBITS] |= (1 << ((n) % NSETBITS))) +#define SET_CLR(n, p) ((p)[(n)/NSETBITS] &= ~(1 << ((n) % NSETBITS))) +#define SET_ISSET(n, p) ((p)[(n)/NSETBITS] & (1 << ((n) % NSETBITS))) +#define SET_CREATE() ((set_mask *)malloc(setsize * sizeof(set_mask))) +#define SET_ZERO(p) bzero((char *)(p), setsize * sizeof(set_mask)) +#define SET_CMP(p1, p2) (bcmp((p1), (p2), setsize * sizeof(set_mask))) + + +do_cluster() +##{ + FILE *out; + char outf[64]; + struct stat sb; + time_t ftime; +## int flag1, flag2, flag3, flag4, maxmach, maxclu, mid, cid, id; +## char name[33], label[17], data[33], mach[33], *filetime; + set_mask **machs, *ms, *ps; + + sprintf(outf, "%s/cluster.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "cluster" +## retrieve (flag2 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "machine" +## retrieve (flag3 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "mcmap" +## retrieve (flag4 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "svc" + if (flag1 < 0 && flag2 < 0 && flag3 < 0 && flag4 < 0) { + fprintf(stderr, "File cluster.db does not need to be rebuilt.\n"); + return(0); + } + } + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open cluster.db for write"); + exit(-1); + } + + fprintf(stderr, "Building cluster.db\n"); + +## range of c is cluster +## retrieve (maxclu = max(c.clu_id)) + setsize = howmany(maxclu, NSETBITS); +## range of m is machine +## retrieve (maxmach = max(m.mach_id)) + machs = (set_mask **)malloc((maxmach + 1) * sizeof(set_mask **)); + bzero(machs, (maxmach + 1) * sizeof(int)); + +## range of p is mcmap +## retrieve (mid = p.mach_id, cid = p.clu_id) { + if (!(ms = machs[mid])) { + ms = machs[mid] = SET_CREATE(); + SET_ZERO(ms); + } + SET_SET(cid, ms); +## } + +## range of d is svc + for (mid = 1; mid < maxmach; mid++) { + if (!machs[mid]) + continue; + ms = machs[mid]; + fprintf(out, "smsinternal-%d.cluster", mid); + for (cid = 1; cid < maxclu; cid++) { + if (SET_ISSET(cid, ms)) { +## repeat retrieve (label = d.serv_label, data = d.serv_cluster) +## where d.clu_id = @cid { + trim(label); + trim(data); + fprintf(out, "\tHS UNSPECA \"%s %s\"\n", label, data); +## } + } + } + +## repeat retrieve (mach = m.#name) where m.mach_id = @mid + trim(mach); + fprintf(out, "%s.cluster\tHS CNAME smmsinternal-%d.cluster\n", + mach, mid); + for (id = mid + 1; id < maxmach; id++) { + if ((ps = machs[id]) && !SET_CMP(ms, ps)) { + free(ps); + machs[id] = NULL; +## repeat retrieve (mach = m.#name) where m.mach_id = @id + trim(mach); + fprintf(out, "%s.cluster\tHS CNAME smmsinternal-%d.cluster\n", + mach, mid); + } + } + free(ms); + machs[mid] = NULL; + } + free(machs); + +## retrieve (name = c.#name, label = d.serv_label, data = d.serv_cluster) +## where c.clu_id = d.clu_id { + trim(name); + trim(label); + trim(data); + fprintf(out, "%s.cluster\tHS UNSPECA \"%s %s\"\n", + name, label, data); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of cluster.db\n"); + exit(-1); + } + return(1); +##} + + +do_printers() +##{ + FILE *out; + char outf[64]; + struct stat sb; + time_t ftime; +## char name[33], pcap[513], *filetime; +## int flag; + + sprintf(outf, "%s/printcap.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "printcap" + if (flag < 0) { + fprintf(stderr, "File printcap.db does not need to be rebuilt.\n"); + return(0); + } + } + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open printcap.db for write"); + exit(-1); + } + + fprintf(stderr, "Building printcap.db\n"); + +## range of p is printcap +## retrieve (name = p.#name, pcap = p.#pcap) { + trim(name); + trim(pcap); + fprintf(out, "%s.pcap\tHS UNSPECA \"%s\"\n", name, pcap); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of pcap.db\n"); + exit(-1); + } + return(1); +##} + + +do_services() +##{ + FILE *out; + char outf[64]; + struct stat sb; + time_t ftime; +## char mach[33], service[17], protocol[9], *filetime; +## int port, flag1, flag2; + + sprintf(outf, "%s/sloc.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "serverhosts" +## retrieve (flag2 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "machine" + if (flag1 < 0 && flag2 < 0) { + fprintf(stderr, "File sloc.db does not need to be rebuilt.\n"); + goto next; + } + } + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open sloc.db for write"); + exit(-1); + } + + fprintf(stderr, "Building sloc.db\n"); + +## range of s is serverhosts +## retrieve (service = s.#service, mach = m.name) +## where m.mach_id = s.mach_id { + trim(service); + trim(mach); + fprintf(out, "%s.sloc\tHS UNSPECA %s\n", service, mach); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of sloc.db\n"); + exit(-1); + } + + next: + sprintf(outf, "%s/service.db", HESIOD_DIR); + + if (stat(outf, &sb) == 0) { + ftime = sb.st_mtime; + filetime = ingres_date_and_time(ftime); +## retrieve (flag1 = int4(interval("min", tblstats.modtime - filetime))) +## where tblstats.table = "services" + if (flag1 < 0) { + fprintf(stderr, "File service.db does not need to be rebuilt.\n"); + return(0); + } + } + + out = fopen(outf, "w"); + if (!out) { + perror("cannot open service.db for write"); + exit(-1); + } + + fprintf(stderr, "Building service.db\n"); + +## range of s is services +## retrieve (service = s.name, protocol = lowercase(s.#protocol), +## port = s.#port) { + trim(service); + trim(protocol); + fprintf(out, "%s.service\tHS UNSPECA \"%s %s %d\"\n", + service, service, protocol, port); +## } + + if (fclose(out)) { + fprintf(stderr, "Unsuccessful close of service.db\n"); + exit(-1); + } + return(1); +##} + + +trim(s) +register char *s; +{ + register char *p; + + for (p = s; *s; s++) + if (*s != ' ') + p = s; + if (p != s) { + if (*p == ' ') + *p = 0; + else + p[1] = 0; + } +} + + + +static char *month_name[] = { + "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", + "nov", "dec" + }; + + +char *ingres_date_and_time(l) +long l; +{ + char *ans = NULL, *date, *time; + + if ((date = ingres_date(l)) && (time = ingres_time(l))) { + char buf[BUFSIZ]; + sprintf(buf, "%s %s", date, time); + ans = strsave(buf); + } + if (date) + free(date); + if (time) + free(time); + return ans; +} + +char *ingres_time(t) + long t; +{ + struct tm *tm; + + if (t == (long) 0) + (void) time(&t); + + if ((tm = localtime(&t)) == (struct tm *) NULL) { + return NULL; + } else { + char buf[BUFSIZ]; + + sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, + tm->tm_sec); + return strsave(buf); + } +} + +char *ingres_date(t) + long t; +{ + struct tm *tm; + + if (t == (long) 0) + (void) time(&t); + + if ((tm = localtime(&t)) == (struct tm *) NULL) { + return NULL; + } else { + char buf[BUFSIZ]; + + sprintf(buf, "%02d-%3.3s-%04d", tm->tm_mday, + month_name[tm->tm_mon], 1900 + tm->tm_year); + return strsave(buf); + } +} + diff --git a/gen/util.c b/gen/util.c new file mode 100644 index 00000000..153987c9 --- /dev/null +++ b/gen/util.c @@ -0,0 +1,113 @@ +/* $Header$ + * + * Utility routines used by the SMS extraction programs. + */ + + +#include +#include + +char *malloc(); + + +/* Trim trailing spaces from a string by replacing one of them with a null. + */ + +trim(s) +register char *s; +{ + register char *p; + + for (p = s; *s; s++) + if (*s != ' ') + p = s; + if (p != s) { + if (*p == ' ') + *p = 0; + else + p[1] = 0; + } +} + + +/* return a "saved" copy of the string */ + +char *strsave(s) +register char *s; +{ + register char *r; + + r = malloc(strlen(s) + 1); + strcpy(r, s); + return(r); +} + + + +/* ingres_date_and_time: passed a unix time_t, returns a string that ingres + * can parse to obtain that time. + */ + +static char *month_name[] = { + "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", + "nov", "dec" + }; + + +char *ingres_date(), *ingres_time(); + +char *ingres_date_and_time(l) +long l; +{ + char *ans = NULL, *date, *time; + + if ((date = ingres_date(l)) && (time = ingres_time(l))) { + char buf[BUFSIZ]; + sprintf(buf, "%s %s", date, time); + ans = strsave(buf); + } + if (date) + free(date); + if (time) + free(time); + return ans; +} + +char *ingres_time(t) + long t; +{ + struct tm *tm; + + if (t == (long) 0) + (void) time(&t); + + if ((tm = localtime(&t)) == (struct tm *) NULL) { + return NULL; + } else { + char buf[BUFSIZ]; + + sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, + tm->tm_sec); + return strsave(buf); + } +} + +char *ingres_date(t) + long t; +{ + struct tm *tm; + + if (t == (long) 0) + (void) time(&t); + + if ((tm = localtime(&t)) == (struct tm *) NULL) { + return NULL; + } else { + char buf[BUFSIZ]; + + sprintf(buf, "%02d-%3.3s-%04d", tm->tm_mday, + month_name[tm->tm_mon], 1900 + tm->tm_year); + return strsave(buf); + } +} + -- 2.45.2