/*
 * pcfdate - get the time from a pcfclock device and set the system time
 *
 * Copyright (C) 1999-2001 Andreas Voegele
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with this program in the file COPYING; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA.
 */

#if HAVE_CONFIG_H
#  include "config.h"
#endif

#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <locale.h>

char *program_name;
int quiet;
int set_time;
int utc;
int verbose;

/* The following offset is added to the system time.  The offset is
   (69*2500) since the radio clock transmits 69 bits with a period of
   2500 microseconds per bit. */
#define OFFSET (69*2500)

#define DATA_ERROR 1
#define READ_ERROR 2
#define OPEN_ERROR 3
#define OUT_OF_MEMORY_ERROR 4

void
print_error(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	fprintf(stderr, "%s: ", program_name);
	vfprintf(stderr, format, ap);
	va_end(ap);
}

int
read_and_set_time(const char *device)
{
	int fd;
	char timecode[18], *tz;
	ssize_t n;
	struct tm tm;
	time_t newtime;

	fd = open(device, O_RDONLY | O_NONBLOCK);
	if (fd == -1) {
		return OPEN_ERROR;
	}

	if ((n = read(fd, timecode, 18)) == -1) {
		print_error("%s: %s\n", device, strerror(errno));
		close(fd);
		return READ_ERROR;
	}

	close(fd);

	if (n != 18 || timecode[0] != 9) {
		print_error("%s: %s\n", device, strerror(EIO));
		return DATA_ERROR;
	}

	tm.tm_sec = timecode[3] * 10 + timecode[2];
	tm.tm_min = timecode[5] * 10 + timecode[4];
	tm.tm_hour = timecode[7] * 10 + timecode[6];
	tm.tm_mday = timecode[11] * 10 + timecode[10];
	tm.tm_mon = timecode[13] * 10 + timecode[12] - 1;
	tm.tm_year = timecode[15] * 10 + timecode[14];
	if (tm.tm_year < 99)
		tm.tm_year += 100;
	tm.tm_isdst = (timecode[8] & 1) ? 1 : (timecode[8] & 2) ? 0 : -1;

	/* Set the time zone temporarily to CET. */
	tz = getenv("TZ");
	if (tz) {
		if (!(tz = strdup(tz))) {
			print_error("%s\n", strerror(ENOMEM));
			return OUT_OF_MEMORY_ERROR;
		}
	}
	if (putenv("TZ=CET") == -1) {
		print_error("%s\n", strerror(errno));
		free(tz);
		return OUT_OF_MEMORY_ERROR;
	}

	/* Convert the time structure to calendar time representation. */
	newtime = mktime(&tm);

	/* Restore the old timezone. */
	if (tz) {
		static char Buf[256];

#if HAVE_SNPRINTF
		snprintf(Buf, sizeof(Buf), "TZ=%s", tz);
#else
		sprintf(Buf, "TZ=%s", tz);
#endif
		putenv(Buf);
		free(tz);
	} else {
		putenv("TZ");
	}

	if (newtime == (time_t) -1) {
		print_error("%s: %s\n", device, strerror(EIO));
		return DATA_ERROR;
	}

	/* Set the system time. */
	if (set_time) {
		struct timeval tv;

		tv.tv_sec = newtime;
		tv.tv_usec = timecode[16] * 31250;
		if (timecode[17] & 1)
			tv.tv_usec += 500000;

		if (tv.tv_usec + OFFSET >= 1000000) {
			tv.tv_usec += OFFSET - 1000000;
			++tv.tv_sec;
		} else {
			tv.tv_usec += OFFSET;
		}
		
		if (settimeofday(&tv, NULL) == -1) {
			print_error("%s\n", strerror(errno));
		}
	}

	/* Output the time. */
	if (!quiet) {
		struct tm *tp;
		char buf[256];

		tp = localtime(&newtime);
		strftime(buf, sizeof(buf), "%+", tp);
		fprintf(stdout, "%s", buf);
		if (verbose) {
		  /*
			fprintf(stdout, " - DST: \t%s\n", (
				(timecode[8]&3)==1 ? "Yes" :
				((timecode[8]&3)==2 ? "No" : "unknown" ))
			);
			fprintf(stdout, " - Sync:\t%s\n", (timecode[1] & 1 ? "Error" : "Ok"));
			fprintf(stdout, " - Battery:\t%s\n", (timecode[8] & 4 ? "Replace" : "Ok"));
		  */
		  /* Not too verbose: */
		  fprintf(stdout," [Sync: %s, Battery: %s]",
			 (timecode[1] & 1 ? "Error" : "Ok"),
			 (timecode[8] & 4 ? "Replace" : "Ok"));
		}
		fprintf(stdout, "\n");
	}

	return 0;
}

int
main(int argc, char *argv[])
{
	int rc = 1, error, c, i;
	char *p;

	program_name = argv[0];
	p = strrchr(program_name, '/');
	if (p)
		program_name = p + 1;

	setlocale(LC_ALL, "");
	
	while ((c = getopt(argc, argv, "qsuv")) != -1) {
		switch (c) {
		case 'q':
			quiet = 1;
			break;
		case 's':
			set_time = 1;
			break;
		case 'u':
			utc = 1;
			break;
		case 'v':
			verbose = 1;
			quiet = 0;
			break;
		}
	}

	if (utc) {
		if (putenv("TZ=UTC0") == -1) {
			print_error("%s\n", strerror(errno));
			return 1;
		}
	}
	
	if (optind < argc) {
		for (i = optind; i < argc; ++i) {
			error = read_and_set_time(argv[i]);
			if (error == OPEN_ERROR) {
				print_error("%s: %s\n", argv[i],
					    strerror(errno));
			} else if (error == 0) {
				rc = 0;
				break;
			}
		}
	} else {
		for (i = 0; i < 3; ++i) {
			char device[128];
			
			sprintf(device, "/dev/pcfclocks/%d", i);
			error = read_and_set_time(device);
			if (error == OPEN_ERROR) {
				sprintf(device, "/dev/pcfclock%d", i);
				error = read_and_set_time(device);
				if (error == OPEN_ERROR) {
					print_error("%s: %s\n", device,
						    strerror(errno));
				}
			}
			if (error == 0) {
				rc = 0;
				break;
			}
		}
	}

	return rc;
}


syntax highlighted by Code2HTML, v. 0.9.1