andrej@2178: /*
andrej@2178:   This file is part of Beremiz, a Integrated Development Environment for
andrej@2178:   programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@2178: 
andrej@2178:   See COPYING.runtime
andrej@2178: 
andrej@2178:   Copyright (C) 2018: Sergey Surkov <surkov.sv@summatechnology.ru>
andrej@2178:   Copyright (C) 2018: Andrey Skvortsov <andrej.skvortzov@gmail.com>
andrej@2178: 
andrej@2178: */
andrej@2178: 
andrej@2174: #ifndef HAVE_RETAIN
andrej@2174: #include <stdio.h>
andrej@2174: #include <stdint.h>
andrej@2174: #include <unistd.h>
andrej@2174: #include "iec_types.h"
andrej@2174: 
andrej@2227: int GetRetainSize(void);
andrej@2174: 
andrej@2174: /* Retain buffer.  */
andrej@2174: FILE *retain_buffer;
andrej@2174: const char rb_file[]      = "retain_buffer_file";
andrej@2174: const char rb_file_bckp[] = "retain_buffer_file.bak";
andrej@2174: 
andrej@2174: 
andrej@2174: /* Retain header struct.  */
andrej@2174: struct retain_info_t {
andrej@2174: 	uint32_t retain_size;
andrej@2174: 	uint32_t hash_size;
andrej@2174: 	uint8_t* hash;
andrej@2174: 	uint32_t header_offset;
andrej@2174: 	uint32_t header_crc;
andrej@2174: };
andrej@2174: 
andrej@2174: /* Init retain info structure.  */
andrej@2174: struct retain_info_t retain_info;
andrej@2174: 
andrej@2174: /* CRC lookup table and initial state.  */
andrej@2174: uint32_t crc32_table[256];
andrej@2174: uint32_t retain_crc;
andrej@2174: 
andrej@2174: 
andrej@2174: /* Generate CRC32 lookup table.  */
andrej@2174: void GenerateCRC32Table(void)
andrej@2174: {
andrej@2174: 	unsigned int i, j;
andrej@2174: 	/* Use CRC-32-IEEE 802.3 polynomial 0x04C11DB7 (bit reflected).  */
andrej@2174: 	uint32_t poly = 0xEDB88320;
andrej@2174: 
andrej@2174: 	for (i = 0; i <= 0xFF; i++)
andrej@2174: 	{
andrej@2174: 		uint32_t c = i;
andrej@2174: 		for (j = 0 ; j < 8 ; j++)
andrej@2174: 			c = (c & 1) ? (c >> 1 ) ^ poly : (c >> 1);
andrej@2174: 		crc32_table[i] = c;
andrej@2174: 	}
andrej@2174: }
andrej@2174: 
andrej@2174: 
andrej@2174: /* Calculate CRC32 for len bytes from pointer buf with init starting value.  */
andrej@2174: uint32_t GenerateCRC32Sum(const void* buf, unsigned int len, uint32_t init)
andrej@2174: {
andrej@2174: 	uint32_t crc = ~init;
andrej@2174: 	unsigned char* current = (unsigned char*) buf;
andrej@2174: 	while (len--)
andrej@2174: 		crc = crc32_table[(crc ^ *current++) & 0xFF] ^ (crc >> 8);
andrej@2174: 	return ~crc;
andrej@2174: }
andrej@2174: 
andrej@2174: /* Calc CRC32 for retain file byte by byte.  */
andrej@2174: int CheckFileCRC(FILE* file_buffer)
andrej@2174: {
andrej@2174: 	/* Set the magic constant for one-pass CRC calc according to ZIP CRC32.  */
andrej@2174: 	const uint32_t magic_number = 0x2144df1c;
andrej@2174: 
andrej@2174: 	/* CRC initial state.  */
andrej@2174: 	uint32_t calc_crc32 = 0;
andrej@2174: 	char data_block = 0;
andrej@2174: 
andrej@2174: 	while(!feof(file_buffer)){
andrej@2174: 		if (fread(&data_block, sizeof(data_block), 1, file_buffer))
andrej@2227: 			calc_crc32 = GenerateCRC32Sum(&data_block, sizeof(data_block), calc_crc32);
andrej@2174: 	}
andrej@2174: 
andrej@2174: 	/* Compare crc result with a magic number.  */
andrej@2174: 	return (calc_crc32 == magic_number) ? 1 : 0;
andrej@2174: }
andrej@2174: 
andrej@2174: /* Compare current hash with hash from file byte by byte.  */
andrej@2174: int CheckFilehash(void)
andrej@2174: {
andrej@2227: 	unsigned int k;
andrej@2174: 	int offset = sizeof(retain_info.retain_size);
andrej@2174: 
andrej@2174: 	rewind(retain_buffer);
andrej@2174: 	fseek(retain_buffer, offset , SEEK_SET);
andrej@2174: 
andrej@2174: 	uint32_t size;
andrej@2174: 	fread(&size, sizeof(size), 1, retain_buffer);
andrej@2174: 	if (size != retain_info.hash_size)
andrej@2174: 		return 0;
andrej@2174: 
andrej@2174: 	for(k = 0; k < retain_info.hash_size; k++){
andrej@2174: 		uint8_t file_digit;
andrej@2227: 		fread(&file_digit, sizeof(file_digit), 1, retain_buffer);
andrej@2174: 		if (file_digit != *(retain_info.hash+k))
andrej@2174: 			return 0;
andrej@2174: 	}
andrej@2174: 
andrej@2174: 	return 1;
andrej@2174: }
andrej@2174: 
andrej@2174: void InitRetain(void)
andrej@2174: {
andrej@2227: 	unsigned int i;
andrej@2174: 
andrej@2174: 	/* Generate CRC32 lookup table.  */
andrej@2174: 	GenerateCRC32Table();
andrej@2174: 
andrej@2174: 	/* Get retain size in bytes */
andrej@2174: 	retain_info.retain_size = GetRetainSize();
andrej@2174: 
andrej@2174: 	/* Hash stored in retain file as array of char in hex digits
andrej@2174: 	   (that's why we divide strlen in two).  */
andrej@2174: 	retain_info.hash_size = PLC_ID ? strlen(PLC_ID)/2 : 0;
andrej@2174: 	//retain_info.hash_size = 0;
andrej@2174: 	retain_info.hash = malloc(retain_info.hash_size);
andrej@2174: 
andrej@2174: 	/* Transform hash string into byte sequence.  */
andrej@2174: 	for (i = 0; i < retain_info.hash_size; i++) {
andrej@2174: 		int byte = 0;
andrej@2174: 		sscanf((PLC_ID + i*2), "%02X", &byte);
andrej@2174: 		retain_info.hash[i] = byte;
andrej@2174: 	}
andrej@2174: 
andrej@2174: 	/* Calc header offset.  */
andrej@2174: 	retain_info.header_offset = sizeof(retain_info.retain_size) + \
andrej@2174: 		sizeof(retain_info.hash_size) + \
andrej@2174: 		retain_info.hash_size;
andrej@2174: 
andrej@2174: 	/*  Set header CRC initial state.  */
andrej@2174: 	retain_info.header_crc = 0;
andrej@2174: 
andrej@2174: 	/* Calc crc for header.  */
andrej@2174: 	retain_info.header_crc = GenerateCRC32Sum(
andrej@2174: 		&retain_info.retain_size,
andrej@2174: 		sizeof(retain_info.retain_size),
andrej@2174: 		retain_info.header_crc);
andrej@2174: 
andrej@2174: 	retain_info.header_crc = GenerateCRC32Sum(
andrej@2174: 		&retain_info.hash_size,
andrej@2174: 		sizeof(retain_info.hash_size),
andrej@2174: 		retain_info.header_crc);
andrej@2174: 
andrej@2174: 	retain_info.header_crc = GenerateCRC32Sum(
andrej@2174: 		retain_info.hash,
andrej@2174: 		retain_info.hash_size,
andrej@2174: 		retain_info.header_crc);
andrej@2174: }
andrej@2174: 
andrej@2174: void CleanupRetain(void)
andrej@2174: {
andrej@2174: 	/* Free hash memory.  */
andrej@2174: 	free(retain_info.hash);
andrej@2174: }
andrej@2174: 
andrej@2174: int CheckRetainFile(const char * file)
andrej@2174: {
andrej@2174: 	retain_buffer = fopen(file, "rb");
andrej@2174: 	if (retain_buffer) {
andrej@2174: 		/* Check CRC32 and hash.  */
andrej@2174: 		if (CheckFileCRC(retain_buffer))
andrej@2174: 			if (CheckFilehash())
andrej@2174: 				return 1;
andrej@2174: 		fclose(retain_buffer);
andrej@2174: 		retain_buffer = NULL;
andrej@2174: 	}
andrej@2174: 	return 0;
andrej@2174: }
andrej@2174: 
andrej@2174: int CheckRetainBuffer(void)
andrej@2174: {
andrej@2174: 	retain_buffer = NULL;
andrej@2174: 	if (!retain_info.retain_size)
andrej@2174: 		return 1;
andrej@2174: 
andrej@2174: 	/* Check latest retain file.  */
andrej@2174: 	if (CheckRetainFile(rb_file))
andrej@2174: 		return 1;
andrej@2174: 
andrej@2174: 	/* Check if we have backup.  */
andrej@2174: 	if (CheckRetainFile(rb_file_bckp))
andrej@2174: 		return 1;
andrej@2174: 
andrej@2174: 	/* We don't have any valid retain buffer - nothing to remind.  */
andrej@2174: 	return 0;
andrej@2174: }
andrej@2174: 
andrej@2174: #ifndef FILE_RETAIN_SAVE_PERIOD_S
andrej@2174: #define FILE_RETAIN_SAVE_PERIOD_S 1.0
andrej@2174: #endif
andrej@2174: 
andrej@2174: static double CalcDiffSeconds(IEC_TIME* t1, IEC_TIME *t2)
andrej@2174: {
andrej@2174: 	IEC_TIME dt ={
andrej@2174: 		t1->tv_sec  - t2->tv_sec,
andrej@2174: 		t1->tv_nsec - t2->tv_nsec
andrej@2174: 	};
andrej@2174: 
andrej@2174: 	if ((dt.tv_nsec < -1000000000) || ((dt.tv_sec > 0) && (dt.tv_nsec < 0))){
andrej@2174: 		dt.tv_sec--;
andrej@2174: 		dt.tv_nsec += 1000000000;
andrej@2174: 	}
andrej@2174: 	if ((dt.tv_nsec > +1000000000) || ((dt.tv_sec < 0) && (dt.tv_nsec > 0))){
andrej@2174: 		dt.tv_sec++;
andrej@2174: 		dt.tv_nsec -= 1000000000;
andrej@2174: 	}
andrej@2174: 	return dt.tv_sec + 1e-9*dt.tv_nsec;
andrej@2174: }
andrej@2174: 
andrej@2174: 
andrej@2174: int RetainSaveNeeded(void)
andrej@2174: {
andrej@2174: 	int ret = 0;
andrej@2174: 	static IEC_TIME last_save;
andrej@2174: 	IEC_TIME now;
andrej@2174: 	double diff_s;
andrej@2174: 
andrej@2174: 	/* no retain */
andrej@2174: 	if (!retain_info.retain_size)
andrej@2174: 		return 0;
andrej@2174: 
andrej@2174: 	/* periodic retain flush to avoid high I/O load */
andrej@2174: 	PLC_GetTime(&now);
andrej@2174: 
andrej@2174: 	diff_s = CalcDiffSeconds(&now, &last_save);
andrej@2174: 
andrej@2174: 	if ((diff_s > FILE_RETAIN_SAVE_PERIOD_S) || ForceSaveRetainReq()) {
andrej@2174: 		ret = 1;
andrej@2174: 		last_save = now;
andrej@2174: 	}
andrej@2174: 	return ret;
andrej@2174: }
andrej@2174: 
andrej@2174: void ValidateRetainBuffer(void)
andrej@2174: {
andrej@2174: 	if (!retain_buffer)
andrej@2174: 		return;
andrej@2174: 
andrej@2174: 	/* Add retain data CRC to the end of buffer file.  */
andrej@2174: 	fseek(retain_buffer, 0, SEEK_END);
andrej@2227: 	fwrite(&retain_crc, sizeof(retain_crc), 1, retain_buffer);
andrej@2174: 
andrej@2174: 	/* Sync file buffer and close file.  */
andrej@2174: #ifdef __WIN32
andrej@2174: 	fflush(retain_buffer);
andrej@2174: #else
andrej@2174: 	fsync(fileno(retain_buffer));
andrej@2174: #endif
andrej@2174: 
andrej@2174: 	fclose(retain_buffer);
andrej@2174: 	retain_buffer = NULL;
andrej@2174: }
andrej@2174: 
andrej@2174: void InValidateRetainBuffer(void)
andrej@2174: {
andrej@2174: 	if (!RetainSaveNeeded())
andrej@2174: 		return;
andrej@2174: 
andrej@2174: 	/* Rename old retain file into *.bak if it exists.  */
andrej@2174: 	rename(rb_file, rb_file_bckp);
andrej@2174: 
andrej@2174: 	/* Set file CRC initial value.  */
andrej@2174: 	retain_crc = retain_info.header_crc;
andrej@2174: 
andrej@2174: 	/* Create new retain file.  */
andrej@2174: 	retain_buffer = fopen(rb_file, "wb+");
andrej@2174: 	if (!retain_buffer) {
andrej@2174: 		fprintf(stderr, "Failed to create retain file : %s\n", rb_file);
andrej@2174: 		return;
andrej@2174: 	}
andrej@2174: 
andrej@2174: 	/* Write header to the new file.  */
andrej@2174: 	fwrite(&retain_info.retain_size,
andrej@2174: 		sizeof(retain_info.retain_size), 1, retain_buffer);
andrej@2174: 	fwrite(&retain_info.hash_size,
andrej@2174: 		sizeof(retain_info.hash_size),   1, retain_buffer);
andrej@2174: 	fwrite(retain_info.hash ,
andrej@2174: 		sizeof(char), retain_info.hash_size, retain_buffer);
andrej@2174: }
andrej@2174: 
andrej@2174: void Retain(unsigned int offset, unsigned int count, void *p)
andrej@2174: {
andrej@2174: 	if (!retain_buffer)
andrej@2174: 		return;
andrej@2174: 
andrej@2174: 	/* Generate CRC 32 for each data block.  */
andrej@2174: 	retain_crc = GenerateCRC32Sum(p, count, retain_crc);
andrej@2174: 
andrej@2174: 	/* Save current var in file.  */
andrej@2174: 	fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
andrej@2174: 	fwrite(p, count, 1, retain_buffer);
andrej@2174: }
andrej@2174: 
andrej@2174: void Remind(unsigned int offset, unsigned int count, void *p)
andrej@2174: {
andrej@2174: 	/* Remind variable from file.  */
andrej@2174: 	fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
andrej@2174: 	fread((void *)p, count, 1, retain_buffer);
andrej@2174: }
andrej@2174: #endif // !HAVE_RETAIN