/*
 * Copyright (C) 2025 Jakub Kruszona-Zawadzki, Saglabs SA
 * 
 * This file is part of MooseFS.
 * 
 * MooseFS 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, version 2 (only).
 * 
 * MooseFS 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 MooseFS; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA
 * or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <inttypes.h>

#include "labelparser.h"
#include "MFSCommunication.h"
#include "datapack.h"
#include "idstr.h"

#define MAX_INDEX 0x7FFFFFFF
#define MAX_CHUNKS_PER_FILE (MAX_INDEX+1)

#define MAXSCLASSNLENG 256
#define MAX_EC_REDUNDANCY_LEVEL 9

static int hide_nonprintable = 0;

static inline char dispchar(uint8_t c) {
	if (hide_nonprintable) {
		return (c>=32 && c<=126)?c:'.';
	} else {
		return c;
	}
}

static inline void makestrip(char strip[16],uint32_t ip) {
	snprintf(strip,16,"%03"PRIu8".%03"PRIu8".%03"PRIu8".%03"PRIu8,(uint8_t)(ip>>24),(uint8_t)(ip>>16),(uint8_t)(ip>>8),(uint8_t)ip);
	strip[15]=0;
}

void print_name(FILE *in,uint32_t nleng) {
	uint8_t buff[1024];
	uint32_t x,y,i;
	size_t happy;
	while (nleng>0) {
		y = (nleng>1024)?1024:nleng;
		x = fread(buff,1,y,in);
		if (hide_nonprintable) {
			for (i=0 ; i<x ; i++) {
				if (buff[i]<32 || buff[i]>127) {
					buff[i]='.';
				}
			}
		}
		happy = fwrite(buff,1,x,stdout);
		(void)happy;
		if (x!=y) {
			return;
		}
		nleng -= x;
	}
}

void print_hex(FILE *in,uint32_t vleng) {
	uint8_t buff[1024];
	uint32_t x,y,i;
	while (vleng>0) {
		y = (vleng>1024)?1024:vleng;
		x = fread(buff,1,y,in);
		for (i=0 ; i<x ; i++) {
			printf("%02"PRIX8,buff[i]);
		}
		if (x!=y) {
			return;
		}
		vleng -= x;
	}
}

int chunk_load(FILE *fd,uint8_t mver) {
	uint8_t hdr[8];
	uint8_t loadbuff[18];
	uint8_t pairsbuff[(4+1)*255+1];
	const uint8_t *ptr;
	int32_t r,recsize,dynsize;
	uint64_t chunkid,nextchunkid;
	uint32_t version,lockedto;
	uint8_t archflag;
	uint16_t pairs;
	uint8_t sclassid;
	uint32_t fcount;
	int i;

	if (mver>0x12) {
		fprintf(stderr,"loading chunks: unsupported format\n");
		return -1;
	}

	if (fread(hdr,1,8,fd)!=8) {
		return -1;
	}
	ptr = hdr;
	nextchunkid = get64bit(&ptr);
	recsize = (mver==0x10)?16:(mver==0x11)?17:18;
	printf("# nextchunkid: %016"PRIX64"\n",nextchunkid);
	for (;;) {
		r = fread(loadbuff,1,recsize,fd);
		(void)r;
		ptr = loadbuff;
		chunkid = get64bit(&ptr);
		version = get32bit(&ptr);
		lockedto = get32bit(&ptr);
		if (mver==0x10) {
			archflag = 0;
		} else {
			archflag = get8bit(&ptr);
		}
		if (mver<=0x11) {
			pairs = 0;
		} else {
			pairs = get8bit(&ptr);
		}
		if (archflag&0x80) {
			archflag &= 0x7F;
			pairs |= 0x100;
		}
		if (pairs==0) {
			dynsize = 0;
		} else if (pairs==1) {
			dynsize = 1+3;
		} else {
			dynsize = (1+3)*pairs+1;
		}
		if (chunkid==0 && version==0 && lockedto==0) {
			return 0;
		}
		if (mver<=0x11) {
			printf("CHUNK|i:0x%016"PRIX64"|v:0x%08"PRIX32"|t:%10"PRIu32"|a:%"PRIu8"\n",chunkid,version,lockedto,archflag);
		} else {
			printf("CHUNK|i:0x%016"PRIX64"|v:0x%08"PRIX32"|t:%10"PRIu32"|a:%"PRIu8"|(",chunkid,version,lockedto,archflag);
			if (dynsize>0) {
				r = fread(pairsbuff,1,dynsize,fd);
				(void)r;
			}
			ptr = pairsbuff;
			sclassid = 0;
			for (i=0 ; i<pairs ; i++) {
				sclassid = get8bit(&ptr);
				fcount = get24bit(&ptr);
				printf("s:%"PRIu8",#:%"PRIu32,sclassid,fcount);
				if (i+1<pairs) {
					printf(";");
				}
			}
			if (pairs>1) {
				sclassid = get8bit(&ptr);
			}
			printf(")|c:%"PRIu8"\n",sclassid);
		}
	}
}

int fs_loadedge(FILE *fd,uint8_t mver) {
	uint8_t uedgebuff[4+4+8+2];
	const uint8_t *ptr;
	uint32_t parent_id;
	uint32_t child_id;
	uint64_t edge_id;
	uint16_t nleng;

	ptr = uedgebuff;
	if (mver<=0x10) {
		if (fread(uedgebuff,1,4+4+2,fd)!=4+4+2) {
			fprintf(stderr,"loading edge: read error\n");
			return -1;
		}
		parent_id = get32bit(&ptr);
		child_id = get32bit(&ptr);
		edge_id = 0;
		nleng = get16bit(&ptr);
	} else {
		if (fread(uedgebuff,1,4+4+8+2,fd)!=4+4+8+2) {
			fprintf(stderr,"loading edge: read error\n");
			return -1;
		}
		parent_id = get32bit(&ptr);
		child_id = get32bit(&ptr);
		edge_id = get64bit(&ptr);
		nleng = get16bit(&ptr);
	}
	if (parent_id==0 && child_id==0) {	// last edge
		return 1;
	}

	if (parent_id==0) {
		printf("EDGE|p:      NULL|c:%10"PRIu32"|i:0x%016"PRIX64"|n:",child_id,edge_id);
	} else {
		printf("EDGE|p:%10"PRIu32"|c:%10"PRIu32"|i:0x%016"PRIX64"|n:",parent_id,child_id,edge_id);
	}
	print_name(fd,nleng);
	printf("\n");
	return 0;
}

static inline uint8_t fsnodes_type_convert(uint8_t type) {
	switch (type) {
		case DISP_TYPE_FILE:
			return TYPE_FILE;
		case DISP_TYPE_DIRECTORY:
			return TYPE_DIRECTORY;
		case DISP_TYPE_SYMLINK:
			return TYPE_SYMLINK;
		case DISP_TYPE_FIFO:
			return TYPE_FIFO;
		case DISP_TYPE_BLOCKDEV:
			return TYPE_BLOCKDEV;
		case DISP_TYPE_CHARDEV:
			return TYPE_CHARDEV;
		case DISP_TYPE_SOCKET:
			return TYPE_SOCKET;
		case DISP_TYPE_TRASH:
			return TYPE_TRASH;
		case DISP_TYPE_SUSTAINED:
			return TYPE_SUSTAINED;
	}
	return 0;
}

int fs_loadnode(FILE *fd,uint8_t mver,uint8_t *unodebuff) {
	const uint8_t *ptr,*chptr;
	uint8_t type,goal,flags,winattr;
	uint32_t nodeid,uid,gid,atimestamp,mtimestamp,ctimestamp,trashretention;
	uint16_t mode;
	uint32_t hdrsize;
	char c;

	type = fgetc(fd);
	if (type==0) {	// last node
		return 1;
	}
	if (mver<=0x11) {
		hdrsize = 4+1+2+6*4;
	} else if (mver<=0x13) {
		hdrsize = 4+1+1+2+6*4;
	} else {
		hdrsize = 4+1+1+1+2+5*4+2;
	}
	if (mver<=0x12) {
		type = fsnodes_type_convert(type);
	}
	switch (type) {
	case TYPE_DIRECTORY:
	case TYPE_FIFO:
	case TYPE_SOCKET:
		if (fread(unodebuff,1,hdrsize,fd)!=hdrsize) {
			fprintf(stderr,"loading node: read error\n");
			return -1;
		}
		break;
	case TYPE_BLOCKDEV:
	case TYPE_CHARDEV:
	case TYPE_SYMLINK:
		if (fread(unodebuff,1,hdrsize+4,fd)!=hdrsize+4) {
			fprintf(stderr,"loading node: read error\n");
			return -1;
		}
		break;
	case TYPE_FILE:
	case TYPE_TRASH:
	case TYPE_SUSTAINED:
		if (mver<=0x13) {
			if (fread(unodebuff,1,hdrsize+8+4+2,fd)!=hdrsize+8+4+2) {
				fprintf(stderr,"loading node: read error\n");
				return -1;
			}
		} else {
			if (fread(unodebuff,1,hdrsize+8+4,fd)!=hdrsize+8+4) {
				fprintf(stderr,"loading node: read error\n");
				return -1;
			}
		}
		break;
	default:
		fprintf(stderr,"loading node: unrecognized node type: %c\n",type);
		return -1;
	}
	c='?';
	switch (type) {
	case TYPE_DIRECTORY:
		c='D';
		break;
	case TYPE_SOCKET:
		c='S';
		break;
	case TYPE_FIFO:
		c='F';
		break;
	case TYPE_BLOCKDEV:
		c='B';
		break;
	case TYPE_CHARDEV:
		c='C';
		break;
	case TYPE_SYMLINK:
		c='L';
		break;
	case TYPE_FILE:
		c='-';
		break;
	case TYPE_TRASH:
		c='T';
		break;
	case TYPE_SUSTAINED:
		c='R';
		break;
	}
	ptr = unodebuff;
	nodeid = get32bit(&ptr);
	goal = get8bit(&ptr);
	if (mver<=0x11) {
		uint16_t flagsmode = get16bit(&ptr);
		flags = flagsmode >> 12;
		mode = flagsmode & 0xFFF;
		winattr = 0;
	} else {
		flags = get8bit(&ptr);
		if (mver>=0x14) {
			winattr = get8bit(&ptr);
		} else {
			winattr = 0;
		}
		mode = get16bit(&ptr);
	}
	uid = get32bit(&ptr);
	gid = get32bit(&ptr);
	atimestamp = get32bit(&ptr);
	mtimestamp = get32bit(&ptr);
	ctimestamp = get32bit(&ptr);
	if (mver>=0x14) {
		trashretention = get16bit(&ptr);
		printf("NODE|k:%c|i:%10"PRIu32"|#:%"PRIu8"|e:0x%02"PRIX8"|w:0x%1"PRIX8"|m:0%04"PRIo16"|u:%10"PRIu32"|g:%10"PRIu32"|a:%10"PRIu32",m:%10"PRIu32",c:%10"PRIu32"|t:%5"PRIu32"h",c,nodeid,goal,flags,winattr,mode,uid,gid,atimestamp,mtimestamp,ctimestamp,trashretention);
	} else {
		trashretention = get32bit(&ptr);
		printf("NODE|k:%c|i:%10"PRIu32"|#:%"PRIu8"|e:0x%02"PRIX8"|m:0%04"PRIo16"|u:%10"PRIu32"|g:%10"PRIu32"|a:%10"PRIu32",m:%10"PRIu32",c:%10"PRIu32"|t:%10"PRIu32"s",c,nodeid,goal,flags,mode,uid,gid,atimestamp,mtimestamp,ctimestamp,trashretention);
	}

	if (type==TYPE_BLOCKDEV || type==TYPE_CHARDEV) {
		uint32_t rdev;
		rdev = get32bit(&ptr);
		printf("|d:%5"PRIu32",%5"PRIu32"\n",rdev>>16,rdev&0xFFFF);
	} else if (type==TYPE_SYMLINK) {
		uint32_t pleng;
		pleng = get32bit(&ptr);
		printf("|p:");
		print_name(fd,pleng);
		printf("\n");
	} else if (type==TYPE_FILE || type==TYPE_TRASH || type==TYPE_SUSTAINED) {
		uint64_t length,chunkid;
		uint32_t ci,ch,sessionid;
		uint16_t sessionids;

		length = get64bit(&ptr);
		ch = get32bit(&ptr);
		if (mver<=0x13) {
			sessionids = get16bit(&ptr);
		} else {
			sessionids = 0;
		}

		printf("|l:%20"PRIu64"|c:(",length);
		while (ch>65536) {
			chptr = ptr;
			if (fread((uint8_t*)ptr,1,8*65536,fd)!=8*65536) {
				fprintf(stderr,"loading node: read error\n");
				return -1;
			}
			for (ci=0 ; ci<65536 ; ci++) {
				chunkid = get64bit(&chptr);
				if (chunkid>0) {
					printf("%016"PRIX64,chunkid);
				} else {
					printf("N");
				}
				printf(",");
			}
			ch-=65536;
		}

		if (fread((uint8_t*)ptr,1,8*ch+4*sessionids,fd)!=8*ch+4*sessionids) {
			fprintf(stderr,"loading node: read error\n");
			return -1;
		}

		while (ch>0) {
			chunkid = get64bit(&ptr);
			if (chunkid>0) {
				printf("%016"PRIX64,chunkid);
			} else {
				printf("N");
			}
			if (ch>1) {
				printf(",");
			}
			ch--;
		}
		if (mver<=0x13) {
			printf(")|r:(");
			while (sessionids>0) {
				sessionid = get32bit(&ptr);
				printf("%"PRIu32,sessionid);
				if (sessionids>1) {
					printf(",");
				}
				sessionids--;
			}
		}
		printf(")\n");
	} else {
		printf("\n");
	}

	return 0;
}

int fs_loadnodes(FILE *fd,uint8_t fver,uint8_t mver) {
	int s;
	uint8_t *auxbuff;
	uint8_t hdr[8];
	const uint8_t *ptr;
	uint32_t maxnodeid,hashelements;
	(void)fver;
	if (fver>=0x20) {
		if (mver>0x14) {
			fprintf(stderr,"loading node: unsupported format\n");
			return -1;
		}
		if (mver<=0x10) {
			if (fread(hdr,1,4,fd)!=4) {
				fprintf(stderr,"loading node: read error\n");
				return -1;
			}
			ptr = hdr;
			maxnodeid = get32bit(&ptr);
			printf("# maxinode: %"PRIu32"\n",maxnodeid);
		} else {
			if (fread(hdr,1,8,fd)!=8) {
				fprintf(stderr,"loading node: read error\n");
				return -1;
			}
			ptr = hdr;
			maxnodeid = get32bit(&ptr);
			hashelements = get32bit(&ptr);
			printf("# maxinode: %"PRIu32" ; hashelements: %"PRIu32"\n",maxnodeid,hashelements);
		}
	}
	auxbuff = malloc(4+1+1+1+2+4+4+4+4+4+4+8+4+2+8*65536+4*65536+4);
	do {
		s = fs_loadnode(fd,mver,auxbuff);
		if (s<0) {
			free(auxbuff);
			return -1;
		}
	} while (s==0);
	free(auxbuff);
	return 0;
}

int fs_loadedges(FILE *fd,uint8_t mver) {
	uint8_t hdr[8];
	const uint8_t *ptr;
	uint64_t nextedgeid;
	int s;
	if (mver>0x11) {
		fprintf(stderr,"loading edge: unsupported format\n");
		return -1;
	}
	if (mver>0x10) {
		if (fread(hdr,1,8,fd)!=8) {
			fprintf(stderr,"loading edge: read error\n");
			return -1;
		}
		ptr = hdr;
		nextedgeid = get64bit(&ptr);
		printf("# nextedgeid: %016"PRIX64"\n",nextedgeid);
	}
	do {
		s = fs_loadedge(fd,mver);
		if (s<0) {
			return -1;
		}
	} while (s==0);
	return 0;
}

int fs_loadfree(FILE *fd,uint8_t mver) {
	uint8_t rbuff[8];
	const uint8_t *ptr;
	uint32_t t,nodeid,ftime;
	if (mver>0x10) {
		fprintf(stderr,"loading free nodes: unsupported format\n");
		return -1;
	}
	if (fread(rbuff,1,4,fd)!=4) {
		return -1;
	}
	ptr=rbuff;
	t = get32bit(&ptr);
	printf("# free nodes: %"PRIu32"\n",t);
	while (t>0) {
		if (fread(rbuff,1,8,fd)!=8) {
			return -1;
		}
		ptr = rbuff;
		nodeid = get32bit(&ptr);
		ftime = get32bit(&ptr);
		printf("FREEID|i:%10"PRIu32"|f:%10"PRIu32"\n",nodeid,ftime);
		t--;
	}
	return 0;
}

int fs_loadquota(FILE *fd,uint8_t mver) {
	uint8_t rbuff[70];
	const uint8_t *ptr;
	uint8_t exceeded,flags;
	uint32_t t,graceperiod,nodeid,stimestamp,sinodes,hinodes;
	uint64_t slength,hlength,ssize,hsize,srealsize,hrealsize;
	size_t rsize;
	
	if (mver>0x11) {
		fprintf(stderr,"loading quota: unsupported format\n");
		return -1;
	}
	rsize = (mver==0x10)?66:70;
	if (fread(rbuff,1,4,fd)!=4) {
		return -1;
	}
	ptr=rbuff;
	t = get32bit(&ptr);
	printf("# quota nodes: %"PRIu32"\n",t);
	while (t>0) {
		if (fread(rbuff,1,rsize,fd)!=rsize) {
			return -1;
		}
		ptr = rbuff;
		nodeid = get32bit(&ptr);
		if (mver>=0x11) {
			graceperiod = get32bit(&ptr);
		} else {
			graceperiod = 7*86400;
		}
		exceeded = get8bit(&ptr);
		flags = get8bit(&ptr);
		stimestamp = get32bit(&ptr);
		sinodes = get32bit(&ptr);
		hinodes = get32bit(&ptr);
		slength = get64bit(&ptr);
		hlength = get64bit(&ptr);
		ssize = get64bit(&ptr);
		hsize = get64bit(&ptr);
		srealsize = get64bit(&ptr);
		hrealsize = get64bit(&ptr);
		printf("QUOTA|i:%10"PRIu32"|g:%"PRIu32"|e:%c|f:0x%02"PRIX8"|s:%10"PRIu32,nodeid,graceperiod,(exceeded)?'1':'0',flags,stimestamp);
		if (flags&QUOTA_FLAG_SINODES) {
			printf("|si:%10"PRIu32,sinodes);
		} else {
			printf("|si:         -");
		}
		if (flags&QUOTA_FLAG_HINODES) {
			printf("|hi:%10"PRIu32,hinodes);
		} else {
			printf("|hi:         -");
		}
		if (flags&QUOTA_FLAG_SLENGTH) {
			printf("|sl:%20"PRIu64,slength);
		} else {
			printf("|sl:                   -");
		}
		if (flags&QUOTA_FLAG_HLENGTH) {
			printf("|hl:%20"PRIu64,hlength);
		} else {
			printf("|hl:                   -");
		}
		if (flags&QUOTA_FLAG_SSIZE) {
			printf("|ss:%20"PRIu64,ssize);
		} else {
			printf("|ss:                   -");
		}
		if (flags&QUOTA_FLAG_HSIZE) {
			printf("|hs:%20"PRIu64,hsize);
		} else {
			printf("|hs:                   -");
		}
		if (flags&QUOTA_FLAG_SREALSIZE) {
			printf("|sr:%20"PRIu64,srealsize);
		} else {
			printf("|sr:                   -");
		}
		if (flags&QUOTA_FLAG_HREALSIZE) {
			printf("|hr:%20"PRIu64,hrealsize);
		} else {
			printf("|hr:                   -");
		}
		printf("\n");
		t--;
	}
	return 0;
}

int xattr_load(FILE *fd,uint8_t mver) {
	uint8_t hdrbuff[4+1+4];
	const uint8_t *ptr;
	uint32_t inode;
	uint8_t anleng;
	uint32_t avleng;

	if (mver>0x10) {
		fprintf(stderr,"loading xattr: unsupported format\n");
		return -1;
	}

	while (1) {
		if (fread(hdrbuff,1,4+1+4,fd)!=4+1+4) {
			fprintf(stderr,"loading xattr: read error");
			return -1;
		}
		ptr = hdrbuff;
		inode = get32bit(&ptr);
		anleng = get8bit(&ptr);
		avleng = get32bit(&ptr);
		if (inode==0) {
			return 0;
		}
		if (anleng==0) {
			fprintf(stderr,"loading xattr: empty name");
			fseek(fd,anleng+avleng,SEEK_CUR);
			continue;
		}
		printf("XATTR|i:%10"PRIu32"|n:",inode);
		print_name(fd,anleng);
		printf("|v:");
		print_hex(fd,avleng);
		printf("\n");
	}
}

int posix_acl_load(FILE *fd,uint8_t mver) {
	uint8_t hdrbuff[4+1+2*6];
	uint8_t aclbuff[6*100];
	const uint8_t *ptr;
	uint32_t inode;
	uint8_t acltype;
	uint16_t userperm;
	uint16_t groupperm;
	uint16_t otherperm;
	uint16_t mask;
	uint16_t namedusers;
	uint16_t namedgroups;
	uint32_t aclid;
	uint16_t aclperm;
	uint32_t acls,acbcnt;

	if (mver>0x11) {
		fprintf(stderr,"loading posix_acl: unsupported format\n");
		return -1;
	}

	while (1) {
		if (fread(hdrbuff,1,4+1+2*6,fd)!=4+1+2*6) {
			fprintf(stderr,"loading posix_acl: read error");
			return -1;
		}
		ptr = hdrbuff;
		inode = get32bit(&ptr);
		if (inode==0) {
			return 0;
		}
		acltype = get8bit(&ptr);
		userperm = get16bit(&ptr);
		groupperm = get16bit(&ptr);
		otherperm = get16bit(&ptr);
		mask = get16bit(&ptr);
		namedusers = get16bit(&ptr);
		namedgroups = get16bit(&ptr);
		printf("POSIXACL|i:%10"PRIu32"|t:%"PRIu8"|u:0%05"PRIo16"|g:0%05"PRIo16"|o:0%05"PRIo16"|m:0%05"PRIo16"|n:(",inode,acltype,userperm,groupperm,otherperm,mask);
		acls = namedusers+namedgroups;
		acbcnt = 0;
		while (acls>0) {
			if (acbcnt==0) {
				if (acls>100) {
					acbcnt=100;
				} else {
					acbcnt=acls;
				}
				if (fread(aclbuff,6,acbcnt,fd)!=acbcnt) {
					fprintf(stderr,"loading posix_acl: read error");
					return -1;
				}
				ptr = aclbuff;
			}
			aclid = get32bit(&ptr);
			aclperm = get16bit(&ptr);
			acls--;
			acbcnt--;
			if (acls>=namedgroups) {
				printf("u(%"PRIu32"):0%05"PRIo16,aclid,aclperm);
			} else {
				printf("g(%"PRIu32"):0%05"PRIo16,aclid,aclperm);
			}
			if (acls>0) {
				printf(",");
			}
		}
		printf(")\n");
	}
}

int sessions_load(FILE *fd,uint8_t mver) {
	uint8_t hdr[8];
	uint8_t *fsesrecord;
	const uint8_t *ptr;
	uint16_t dver,statsinfile;
	uint32_t recsize;
	uint32_t i,nextsessionid,sessionid;
	uint32_t ileng,peerip,rootinode,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disconnected;
	uint32_t disables;
	uint16_t umaskval,sclassgroups;
	uint64_t exportscsum;
	uint8_t sesflags,mingoal,maxgoal;
	char strip[16];

	if (mver>0x16) {
		fprintf(stderr,"loading sessions: unsupported format\n");
		return -1;
	}

	if (mver<=0x11) {
		if (fread(hdr,1,8,fd)!=8) {
			fprintf(stderr,"loading sessions: read error");
			return -1;
		}

		ptr = hdr;
		dver = get16bit(&ptr);
		if (dver==1) {
			mver = 0x10;
		} else if (dver==2) {
			mver = 0x11;
		}
	} else {
		dver = 0; // makes old gcc happy
		if (fread(hdr,1,6,fd)!=6) {
			fprintf(stderr,"loading sessions: read error");
			return -1;
		}
		ptr = hdr;
	}
	nextsessionid = get32bit(&ptr);
	statsinfile = get16bit(&ptr);

	if (mver<=0x11) {
		printf("# dver: %"PRIu16" ; nextsessionid: %"PRIu32" ; statscount: %"PRIu16"\n",dver,nextsessionid,statsinfile);
	} else {
		printf("# nextsessionid: %"PRIu32" ; statscount: %"PRIu16"\n",nextsessionid,statsinfile);
	}
	if (mver<0x11) {
		recsize = 43+statsinfile*8;
	} else if (mver<0x13) {
		recsize = 47+statsinfile*8;
	} else if (mver<0x14) {
		recsize = 55+statsinfile*8;
	} else if (mver<0x15) {
		recsize = 57+statsinfile*8;
	} else { // 0x15,0x16
		recsize = 61+statsinfile*8;
	}
	fsesrecord = malloc(recsize);
	if (fsesrecord==NULL) {
		fprintf(stderr,"loading sessions: out of memory\n");
		return -1;
	}

	while (1) {
		if (fread(fsesrecord,1,recsize,fd)!=(size_t)(recsize)) {
			free(fsesrecord);
			fprintf(stderr,"loading sessions: read error\n");
			return -1;
		}
		ptr = fsesrecord;
		sessionid = get32bit(&ptr);
		if (sessionid==0) {
			free(fsesrecord);
			return 0;
		}
		if (mver>=0x13) {
			exportscsum = get64bit(&ptr);
		} else {
			exportscsum = 0;
		}
		ileng = get32bit(&ptr);
		peerip = get32bit(&ptr);
		rootinode = get32bit(&ptr);
		sesflags = get8bit(&ptr);
		if (mver>=0x14) {
			umaskval = get16bit(&ptr);
		} else {
			umaskval = 0;
		}
		if (mver>=0x16) {
			sclassgroups = get16bit(&ptr);
			mingoal = maxgoal = 0;
		} else {
			sclassgroups = 0;
			mingoal = get8bit(&ptr);
			maxgoal = get8bit(&ptr);
		}
		mintrashretention = get32bit(&ptr);
		maxtrashretention = get32bit(&ptr);
		rootuid = get32bit(&ptr);
		rootgid = get32bit(&ptr);
		mapalluid = get32bit(&ptr);
		mapallgid = get32bit(&ptr);
		if (mver>=0x15) {
			disables = get32bit(&ptr);
		} else {
			disables = 0;
		}
		makestrip(strip,peerip);
		if (mver>=0x11) {
			disconnected = get32bit(&ptr);
		} else {
			disconnected = 0;
		}
		if (mver>=0x16) {
			printf("SESSION|s:%10"PRIu32"|e:#%"PRIu64"|p:%s|r:%10"PRIu32"|f:0x%02"PRIX8"|u:0%03"PRIo16"|a:0x%04"PRIX16"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|x:0x%08"PRIX32"|d:%10"PRIu32"|c:",sessionid,exportscsum,strip,rootinode,sesflags,umaskval,sclassgroups,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disables,disconnected);
		} else if (mver>=0x15) {
			printf("SESSION|s:%10"PRIu32"|e:#%"PRIu64"|p:%s|r:%10"PRIu32"|f:0x%02"PRIX8"|u:0%03"PRIo16"|g:%"PRIu8"-%"PRIu8"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|x:0x%08"PRIX32"|d:%10"PRIu32"|c:",sessionid,exportscsum,strip,rootinode,sesflags,umaskval,mingoal,maxgoal,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disables,disconnected);
		} else if (mver>=0x14) {
			printf("SESSION|s:%10"PRIu32"|e:#%"PRIu64"|p:%s|r:%10"PRIu32"|f:0x%02"PRIX8"|u:0%03"PRIo16"|g:%"PRIu8"-%"PRIu8"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|d:%10"PRIu32"|c:",sessionid,exportscsum,strip,rootinode,sesflags,umaskval,mingoal,maxgoal,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disconnected);
		} else if (mver>=0x13) {
			printf("SESSION|s:%10"PRIu32"|e:#%"PRIu64"|p:%s|r:%10"PRIu32"|f:%02"PRIX8"|g:%"PRIu8"-%"PRIu8"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|d:%10"PRIu32"|c:",sessionid,exportscsum,strip,rootinode,sesflags,mingoal,maxgoal,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disconnected);
		} else if (mver>=0x11) {
			printf("SESSION|s:%10"PRIu32"|p:%s|r:%10"PRIu32"|f:%02"PRIX8"|g:%"PRIu8"-%"PRIu8"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|d:%10"PRIu32"|c:",sessionid,strip,rootinode,sesflags,mingoal,maxgoal,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid,disconnected);
		} else {
			printf("SESSION|s:%10"PRIu32"|p:%s|r:%10"PRIu32"|f:%02"PRIX8"|g:%"PRIu8"-%"PRIu8"|t:%10"PRIu32"-%10"PRIu32"|m:%10"PRIu32",%10"PRIu32",%10"PRIu32",%10"PRIu32"|c:",sessionid,strip,rootinode,sesflags,mingoal,maxgoal,mintrashretention,maxtrashretention,rootuid,rootgid,mapalluid,mapallgid);
		}
		if (statsinfile==0) {
			printf("[]|l:[]|i:");
		} else {
			for (i=0 ; i<statsinfile ; i++) {
				printf("%c%"PRIu32,(i==0)?'[':',',get32bit(&ptr));
			}
			printf("]|l:");
			for (i=0 ; i<statsinfile ; i++) {
				printf("%c%"PRIu32,(i==0)?'[':',',get32bit(&ptr));
			}
			printf("]|i:");
		}
		print_name(fd,ileng);
		printf("\n");
	}
}

int csdb_load(FILE *fd,uint8_t mver) {
	uint8_t csdbbuff[13];
	const uint8_t *ptr;
	uint32_t t;
	uint32_t ip;
	uint16_t port;
	uint16_t csid;
	uint8_t maintenance;
	uint32_t maintenance_to;
	size_t bsize;
	char strip[16];

	if (mver>0x13) {
		fprintf(stderr,"loading chunk servers: unsupported format\n");
		return -1;
	}
	if (mver<=0x10) {
		bsize = 6;
	} else if (mver<=0x11) {
		bsize = 8;
	} else if (mver<=0x12) {
		bsize = 9;
	} else {
		bsize = 13;
	}

	if (fread(csdbbuff,1,4,fd)!=4) {
		return -1;
	}
	ptr = csdbbuff;
	t = get32bit(&ptr);
	printf("# chunk servers: %"PRIu32"\n",t);
	while (t>0) {
		if (fread(csdbbuff,1,bsize,fd)!=bsize) {
			fprintf(stderr,"loading chunk servers: read error\n");
			return -1;
		}
		ptr = csdbbuff;
		if (mver<=0x10) {
			ip = get32bit(&ptr);
			port = get16bit(&ptr);
			makestrip(strip,ip);
			printf("CHUNKSERVER|i:%s|p:%5"PRIu16"\n",strip,port);
		} else if (mver<=0x11) {
			ip = get32bit(&ptr);
			port = get16bit(&ptr);
			csid = get16bit(&ptr);
			makestrip(strip,ip);
			printf("CHUNKSERVER|i:%s|p:%5"PRIu16"|#:%5"PRIu16"\n",strip,port,csid);
		} else if (mver<=0x12) {
			ip = get32bit(&ptr);
			port = get16bit(&ptr);
			csid = get16bit(&ptr);
			maintenance = get8bit(&ptr);
			makestrip(strip,ip);
			printf("CHUNKSERVER|i:%s|p:%5"PRIu16"|#:%5"PRIu16"|m:%u\n",strip,port,csid,(maintenance)?1:0);
		} else {
			ip = get32bit(&ptr);
			port = get16bit(&ptr);
			csid = get16bit(&ptr);
			maintenance = get8bit(&ptr);
			maintenance_to = get32bit(&ptr);
			makestrip(strip,ip);
			printf("CHUNKSERVER|i:%s|p:%5"PRIu16"|#:%5"PRIu16"|m:%4s|t:%10"PRIu32"\n",strip,port,csid,(maintenance==0)?"off":(maintenance==1)?"on":(maintenance==2)?"temp":"???",maintenance_to);
		}
		t--;
	}
	return 0;
}

int of_load(FILE *fd,uint8_t mver) {
	uint8_t loadbuff[8];
	const uint8_t *ptr;
	uint32_t sessionid,inode;

	if (mver>0x10) {
		fprintf(stderr,"loading open files: unsupported format\n");
		return -1;
	}

	for (;;) {
		if (fread(loadbuff,1,8,fd)!=8) {
			fprintf(stderr,"loading open files: read error\n");
			return -1;
		}
		ptr = loadbuff;
		sessionid = get32bit(&ptr);
		inode = get32bit(&ptr);
		if (sessionid>0 && inode>0) {
			printf("OPENFILE|s:%10"PRIu32"|i:%10"PRIu32"\n",sessionid,inode);
		} else {
			return 0;
		}
	}
	return 0; // unreachable
}

int flock_load(FILE *fd,uint8_t mver) {
	uint8_t loadbuff[17];
	const uint8_t *ptr;
	uint32_t sessionid,inode;
	uint64_t owner;
	uint8_t ltype;

	if (mver>0x10) {
		fprintf(stderr,"loading flock locks: unsupported format\n");
		return -1;
	}

	for (;;) {
		if (fread(loadbuff,1,17,fd)!=17) {
			fprintf(stderr,"loading flock locks: read error\n");
			return -1;
		}
		ptr = loadbuff;
		inode = get32bit(&ptr);
		sessionid = get32bit(&ptr);
		owner = get64bit(&ptr);
		ltype = get8bit(&ptr);
		if (inode==0 && owner==0 && sessionid==0) {
			return 0;
		}
		printf("FLOCK|i:%10"PRIu32"|s:%10"PRIu32"|o:%016"PRIX64"|t:%c\n",inode,sessionid,owner,ltype?'W':'R');
	}
	return 0;	// unreachable
}

int posix_lock_load(FILE *fd,uint8_t mver) {
	uint8_t loadbuff[37];
	const uint8_t *ptr;
	uint32_t sessionid,inode,pid;
	uint64_t owner,start,end;
	uint8_t type;

	if (mver>0x10) {
		fprintf(stderr,"loading posix locks: unsupported format\n");
		return -1;
	}

	for (;;) {
		if (fread(loadbuff,1,37,fd)!=37) {
			fprintf(stderr,"loading posix locks: read error\n");
			return -1;
		}
		ptr = loadbuff;
		inode = get32bit(&ptr);
		owner = get64bit(&ptr);
		sessionid = get32bit(&ptr);
		pid = get32bit(&ptr);
		start = get64bit(&ptr);
		end = get64bit(&ptr);
		type = get8bit(&ptr);
		if (inode==0 && owner==0 && sessionid==0) {
			return 0;
		}
		printf("POSIXLOCK|i:%10"PRIu32"|s:%10"PRIu32"|o:0x%016"PRIX64"|p:%10"PRIu32"|r:<%20"PRIu64",%20"PRIu64")|t:%c\n",inode,sessionid,owner,pid,start,end,(type==POSIX_LOCK_RDLCK)?'R':(type==POSIX_LOCK_WRLCK)?'W':'?');
	}
	return 0;	// unreachable
}

static void print_labels(const uint8_t **rptr,uint8_t cnt,uint8_t orgroup) {
	uint8_t i,j,k;
	uint8_t p;
	uint32_t mask;
	for (i=0 ; i<cnt ; i++) {
		if (i>0) {
			printf(":");
		}
		p = 1;
		for (j=0 ; j<orgroup ; j++) {
			mask = get32bit(rptr);
			if (j==0 && mask==0) {
				printf("*");
			}
			if (mask==0) {
				p = 0;
			}
			if (p) {
				if (j>0) {
					printf("+");
				}
				for (k=0 ; k<26 ; k++) {
					if (mask & (1<<k)) {
						printf("%c",'A'+k);
					}
				}
			}
		}
	}
}

int sclass_load(FILE *fd,uint8_t mver) {
	uint8_t *databuff = NULL;
	uint8_t hdrbuff[3];
	char strbuff[LABELS_BUFF_SIZE];
	const uint8_t *ptr;
	uint32_t chunkcount;
	uint32_t priority;
	uint16_t sclassid;
	uint16_t arch_delay;
	uint16_t min_trashretention;
	uint64_t arch_min_size;
	uint8_t labels_mode;
	uint8_t arch_mode;
	uint8_t admin_only;
	uint8_t export_group;
	uint8_t dleng;
	uint8_t desc[MAXSCLASSDESCLENG];
	uint8_t nleng;
	uint8_t name[MAXSCLASSNAMELENG];
	parser_data create,keep,arch,trash;
	parser_data *smptr;
	uint8_t ec_is_turned_on;
	uint8_t descrleng;
	uint8_t i,j;
	uint8_t orgroup;
	uint16_t exprsize,lexprsize;
	uint8_t hdrleng;
	uint8_t dsize;

	if (mver>0x1C) {
		fprintf(stderr,"loading storage classes: unsupported format\n");
		return -1;
	}

	if (mver<0x15) {
		for (i=0 ; i<26 ; i++) {
			if (fread(&descrleng,1,1,fd)!=1) {
				fprintf(stderr,"loading storage classes: read error\n");
				return -1;
			}
			if (descrleng>128) {
				fprintf(stderr,"loading storage classes: description too long\n");
				return -1;
			}
			printf("LABELDESC|l:%c|n:",i+'A');
			print_name(fd,descrleng);
			printf("\n");
		}
	}

	if (mver>=0x17) { // expressions
		if (fread(hdrbuff,1,3,fd)!=3) {
			fprintf(stderr,"loading storage classes: read error\n");
			return -1;
		}
		ptr = hdrbuff;
		exprsize = get16bit(&ptr);
		if (exprsize==0) {
			fprintf(stderr,"loading storage classes: exprsize=0 !!!\n");
			return -1;
		}
		ec_is_turned_on = get8bit(&ptr);
		printf("SCLASSEC|%u\n",ec_is_turned_on);
		dsize = (mver>=0x1C)?48:(mver>=0x1B)?42:(mver>=0x1A)?38:(mver>=0x18)?30:29;
		databuff = malloc(dsize);
		while (1) {
			if (fread(databuff,1,2,fd)!=2) {
				fprintf(stderr,"loading storage classes: read error\n");
				return -1;
			}
			ptr = databuff;
			sclassid = get16bit(&ptr);
			if (sclassid==0) {
				break;
			}
			if (fread(databuff,1,dsize,fd)!=dsize) {
				fprintf(stderr,"loading storage classes: read error\n");
				return -1;
			}
			ptr = databuff;
			memset(&create,0,sizeof(parser_data));
			memset(&keep,0,sizeof(parser_data));
			memset(&arch,0,sizeof(parser_data));
			memset(&trash,0,sizeof(parser_data));
			nleng = get8bit(&ptr);
			if (mver>=0x1C) {
				dleng = get8bit(&ptr);
				priority = get32bit(&ptr);
				export_group = get8bit(&ptr);
			} else {
				dleng = 0;
				priority = 0;
				if (sclassid<10) {
					export_group = sclassid;
				} else {
					export_group = 0;
				}
			}
			admin_only = get8bit(&ptr);
			labels_mode = get8bit(&ptr);
			if (mver>=0x18) {
				arch_mode = get8bit(&ptr);
			} else {
				arch_mode = SCLASS_ARCH_MODE_CTIME;
			}
			arch_delay = get16bit(&ptr);
			if (mver>=0x1A) {
				arch_min_size = get64bit(&ptr);
			} else {
				arch_min_size = 0;
			}
			min_trashretention = get16bit(&ptr);
			create.labelscnt = get8bit(&ptr);
			create.uniqmask = get32bit(&ptr);
			if (mver>=0x1B) {
				create.labels_mode = get8bit(&ptr);
			} else {
				create.labels_mode = LABELS_MODE_GLOBAL;
			}
			keep.labelscnt = get8bit(&ptr);
			keep.uniqmask = get32bit(&ptr);
			if (mver>=0x1B) {
				keep.labels_mode = get8bit(&ptr);
			} else {
				keep.labels_mode = LABELS_MODE_GLOBAL;
			}
			arch.labelscnt = get8bit(&ptr);
			arch.ec_data_chksum_parts = get8bit(&ptr);
			arch.uniqmask = get32bit(&ptr);
			if (mver>=0x1B) {
				arch.labels_mode = get8bit(&ptr);
			} else {
				arch.labels_mode = LABELS_MODE_GLOBAL;
			}
			trash.labelscnt = get8bit(&ptr);
			trash.ec_data_chksum_parts = get8bit(&ptr);
			trash.uniqmask = get32bit(&ptr);
			if (mver>=0x1B) {
				trash.labels_mode = get8bit(&ptr);
			} else {
				trash.labels_mode = LABELS_MODE_GLOBAL;
			}
			if (fread(name,1,nleng,fd)!=nleng) {
				fprintf(stderr,"loading storage classes: read error\n");
				return -1;
			}
			name[nleng]=0;
			if (dleng>0) {
				if (fread(desc,1,dleng,fd)!=dleng) {
					fprintf(stderr,"loading storage classes: read error\n");
					return -1;
				}
				desc[dleng]=0;
			}
			if (mver<0x19) {
				if (arch.ec_data_chksum_parts>1) {
					arch.ec_data_chksum_parts--;
				}
				if (trash.ec_data_chksum_parts>1) {
					trash.ec_data_chksum_parts--;
				}
			}
			if (keep.labelscnt==0 || keep.labelscnt>MAXLABELSCNT || create.labelscnt>MAXLABELSCNT || arch.labelscnt>MAXLABELSCNT || trash.labelscnt>MAXLABELSCNT || create.ec_data_chksum_parts>0 || keep.ec_data_chksum_parts>0 || (arch.ec_data_chksum_parts&0xF)>MAX_EC_REDUNDANCY_LEVEL || (trash.ec_data_chksum_parts&0xF)>MAX_EC_REDUNDANCY_LEVEL) {
				fprintf(stderr,"loading storage classes: sclassid: %"PRIu16" - data format error (labelscnt,ec_data_chksum_parts) for create: (%"PRIu8",%"PRIu8") ; keep: (%"PRIu8",%"PRIu8") ; arch: (%"PRIu8",%"PRIu8") ; trash: (%"PRIu8",%"PRIu8")",sclassid,create.labelscnt,create.ec_data_chksum_parts,keep.labelscnt,keep.ec_data_chksum_parts,arch.labelscnt,arch.ec_data_chksum_parts,trash.labelscnt,trash.ec_data_chksum_parts);
				return -1;
			}
			if ((arch.ec_data_chksum_parts>0 || trash.ec_data_chksum_parts>0) && ec_is_turned_on==0) {
				fprintf(stderr,"loading storage class data: sclassid: %"PRIu16" - data format error (arch.ec_data_chksum_parts: %"PRIu8" ; trash.ec_data_chksum_parts: %"PRIu8" ; ec_is_turned_on: 0)",sclassid,arch.ec_data_chksum_parts,trash.ec_data_chksum_parts);
				return -1;
			}
			if (exprsize>SCLASS_EXPR_MAX_SIZE) {
				lexprsize = SCLASS_EXPR_MAX_SIZE;
			} else {
				lexprsize = exprsize;
			}
			printf("SCLASSMAIN|#:%5"PRIu16"|p:%"PRIu32"|g:%u|x:%u|m:%u|a:0x%"PRIX8"|d:%5"PRIu16"|s:%20"PRIu64"|t:%5"PRIu16"|n:%s|c:%s\n",sclassid,priority,export_group,admin_only,labels_mode,arch_mode,arch_delay,arch_min_size,min_trashretention,name,desc);
			smptr = NULL; // stupid old compilers !!!
			for (i=0 ; i<4 ; i++) {
				switch (i) {
					case 0:
						smptr = &create;
						break;
					case 1:
						smptr = &keep;
						break;
					case 2:
						smptr = &arch;
						break;
					case 3:
						smptr = &trash;
						break;
				}
				for (j=0 ; j<smptr->labelscnt ; j++) {
					if (fread(smptr->labelexpr[j],1,lexprsize,fd)!=lexprsize) {
						fprintf(stderr,"loading storage classes: read error\n");
						return -1;
					}
					if (exprsize>SCLASS_EXPR_MAX_SIZE) {
						fseek(fd,exprsize-SCLASS_EXPR_MAX_SIZE,SEEK_CUR);
					}
				}
				if (smptr->labelscnt>0 || smptr->ec_data_chksum_parts>0) {
					printf("SCLASSEXPR|#:%5"PRIu16"|t:%c|e:%s\n",sclassid,"CKAT"[i],make_label_expr(strbuff,smptr));
				}
			}
		}
	} else { // or groups
		if (mver==0x10) {
			orgroup = 1;
		} else {
			if (fread(&orgroup,1,1,fd)!=1) {
				fprintf(stderr,"loading labelset: read error\n");
				return -1;
			}
		}
		if (orgroup<1) {
			fprintf(stderr,"loading labelset: zero or-groups !!!\n");
			return -1;
		}
		databuff = malloc(3U*9U*4U*(uint32_t)orgroup);
		hdrleng = (mver==0x12)?11:(mver<=0x13)?3:(mver<=0x14)?5:(mver<=0x15)?8:10;

		while (1) {
			if (fread(databuff,1,hdrleng,fd)!=hdrleng) {
				fprintf(stderr,"loading labelset: read error\n");
				return -1;
			}
			ptr = databuff;
			sclassid = get16bit(&ptr);
			if (mver>0x15) {
				nleng = get8bit(&ptr);
				admin_only = get8bit(&ptr);
				labels_mode = get8bit(&ptr);
				arch_delay = get16bit(&ptr);
				create.labelscnt = get8bit(&ptr);
				keep.labelscnt = get8bit(&ptr);
				arch.labelscnt = get8bit(&ptr);
				chunkcount = 0;
			} else if (mver>0x14) {
				nleng = 0;
				admin_only = 0;
				labels_mode = get8bit(&ptr);
				arch_delay = get16bit(&ptr);
				create.labelscnt = get8bit(&ptr);
				keep.labelscnt = get8bit(&ptr);
				arch.labelscnt = get8bit(&ptr);
				chunkcount = 0;
			} else if (mver>0x13) {
				nleng = 0;
				admin_only = 0;
				labels_mode = get8bit(&ptr);
				create.labelscnt = get8bit(&ptr);
				keep.labelscnt = get8bit(&ptr);
				arch.labelscnt = keep.labelscnt;
				arch_delay = 0;
				chunkcount = 0;
			} else {
				nleng = 0;
				admin_only = 0;
				create.labelscnt = get8bit(&ptr);
				keep.labelscnt = create.labelscnt;
				arch.labelscnt = create.labelscnt;
				labels_mode = LABELS_MODE_STD;
				arch_delay = 0;
				if (mver==0x12) {
					chunkcount = get32bit(&ptr);
					ptr+=4;
				} else {
					chunkcount = 0;
				}
			}
			if (nleng>0) {
				if (fread(name,1,nleng,fd)!=nleng) {
					fprintf(stderr,"loading labelset: read error\n");
					return -1;
				}
			}
			name[nleng] = 0;
			if (sclassid==0 && create.labelscnt==0 && keep.labelscnt==0 && arch.labelscnt==0 && chunkcount==0 && arch_delay==0) {
				break;
			}
			if (create.labelscnt==0 || create.labelscnt>9 || keep.labelscnt==0 || keep.labelscnt>9 || arch.labelscnt==0 || arch.labelscnt>9) {
				fprintf(stderr,"loading labelset: data format error (sclassid: %"PRIu16" ; labels_mode: %"PRIu8" ; create.labelscnt: %"PRIu8" ; keep.labelscnt: %"PRIu8" ; arch.labelscnt: %"PRIu8" ; arch_delay: %"PRIu16")\n",sclassid,labels_mode,create.labelscnt,keep.labelscnt,arch.labelscnt,arch_delay);
				free(databuff);
				databuff=NULL;
				return -1;
			}
			if (mver>0x14) {
				if (fread(databuff,1,(create.labelscnt+keep.labelscnt+arch.labelscnt)*4*orgroup,fd)!=(size_t)((create.labelscnt+keep.labelscnt+arch.labelscnt)*4*orgroup)) {
					fprintf(stderr,"loading labelset: read error\n");
					free(databuff);
					databuff=NULL;
					return -1;
				}
			} else if (mver>0x13) {
				if (fread(databuff,1,(create.labelscnt+keep.labelscnt)*4*orgroup,fd)!=(size_t)((create.labelscnt+keep.labelscnt)*4*orgroup)) {
					fprintf(stderr,"loading labelset: read error\n");
					free(databuff);
					databuff=NULL;
					return -1;
				}
			} else {
				if (fread(databuff,1,create.labelscnt*4*orgroup,fd)!=(size_t)(create.labelscnt*4*orgroup)) {
					fprintf(stderr,"loading labelset: read error\n");
					free(databuff);
					databuff=NULL;
					return -1;
				}
			}
			if (chunkcount>0) {
				fseek(fd,chunkcount*8,SEEK_CUR);
			}
			ptr = databuff;
			printf("SCLASS|#:%5"PRIu16"|x:%u|m:%u|d:%5"PRIu16,sclassid,admin_only,labels_mode,arch_delay);
			if (mver<=0x13) {
				printf("|c+k+a: ");
			} else {
				printf("|c: ");
			}
			print_labels(&ptr,create.labelscnt,orgroup);
			if (mver>0x13) {
				if (mver<=0x14) {
					printf(" |k+a: ");
				} else {
					printf(" |k: ");
				}
				print_labels(&ptr,keep.labelscnt,orgroup);
				if (mver>0x14) {
					printf(" |a: ");
					print_labels(&ptr,arch.labelscnt,orgroup);
				}
			}
			if (nleng>0) {
				printf(" |n:%s",name);
			}
			printf("\n");
		}
	}
	free(databuff);
	databuff=NULL;
	return 0;
}

int patterns_load(FILE *fd,uint8_t mver) {
	uint8_t databuff[9]; // MAX(1+4+4,1+1+1+2+1)
	const uint8_t *ptr;
	uint8_t psize;
	uint8_t gnleng;
	uint32_t euid,egid;
	uint8_t priority,omask,seteattr,clreattr,scid;
	uint16_t trashretention;

	if (mver>0x11) {
		fprintf(stderr,"loading patterns data: unsupported format");
		return -1;
	}
	psize = (mver==0x10)?6:7;
	for (;;) {
		if (fread(databuff,1,(1+4+4),fd)!=(1+4+4)) {
			fprintf(stderr,"loading patterns data: read error");
			return -1;
		}
		ptr = databuff;
		gnleng = get8bit(&ptr);
		euid = get32bit(&ptr);
		egid = get32bit(&ptr);
		if (gnleng==0 && euid==PATTERN_EUGID_ANY && egid==PATTERN_EUGID_ANY) {
			break;
		}
		if (fread(databuff,1,psize,fd)!=psize) {
			fprintf(stderr,"loading patterns data: read error");
			return -1;
		}
		ptr = databuff;
		priority = get8bit(&ptr);
		omask = get8bit(&ptr);
		scid = get16bit(&ptr);
		trashretention = get16bit(&ptr);
		seteattr = get8bit(&ptr);
		if (mver==0x10) {
			clreattr = 0;
		} else {
			clreattr = get8bit(&ptr);
		}
		printf("PATTERN|v:%3"PRIu8,priority);
		if (omask&PATTERN_OMASK_SCLASS) {
			printf("|#:%3"PRIu8,scid);
		} else {
			printf("|#:---");
		}
		if (omask&PATTERN_OMASK_TRASHRETENTION) {
			printf("|t:%5"PRIu16"h",trashretention);
		} else {
			printf("|t:------");
		}
		if (omask&PATTERN_OMASK_EATTR) {
			printf("|e:+0x%1"PRIX8"/-0x%1"PRIX8,seteattr,clreattr);
		} else {
			printf("|e:-");
		}
		if (euid==PATTERN_EUGID_ANY) {
			printf("|u:       ANY");
		} else {
			printf("|u:10%"PRIu32,euid);
		}
		printf("|g:");
		if (egid==PATTERN_EUGID_ANY) {
			printf("|g:       ANY");
		} else {
			printf("|g:%10"PRIu32,egid);
		}
		printf("|p:");
		print_name(fd,gnleng);
		printf("\n");
	}
	return 1;
}

int hexdump(FILE *fd,uint64_t sleng) {
	uint8_t lbuff[32];
	uint32_t i;
	while (sleng>32) {
		if (fread(lbuff,1,32,fd)!=32) {
			return -1;
		}
		for (i=0 ; i<32 ; i++) {
			printf("%02"PRIX8" ",lbuff[i]);
		}
		printf(" |");
		for (i=0 ; i<32 ; i++) {
			printf("%c",dispchar(lbuff[i]));
		}
		printf("|\n");
		sleng-=32;
	}
	if (sleng>0) {
		if (fread(lbuff,1,sleng,fd)!=(size_t)sleng) {
			return -1;
		}
		for (i=0 ; i<32 ; i++) {
			if (i<sleng) {
				printf("%02"PRIX8" ",lbuff[i]);
			} else {
				printf("   ");
			}
		}
		printf(" |");
		for (i=0 ; i<32 ; i++) {
			if (i<sleng) {
				printf("%c",dispchar(lbuff[i]));
			} else {
				printf(" ");
			}
		}
		printf("|\n");
	}
	return 0;
}

int fs_load_pre17(FILE *fd) {
	uint32_t maxnodeid,nextsessionid;
	uint64_t version;
	uint8_t hdr[16];
	const uint8_t *ptr;
	if (fread(hdr,1,16,fd)!=16) {
		return -1;
	}
	ptr = hdr;
	maxnodeid = get32bit(&ptr);
	version = get64bit(&ptr);
	nextsessionid = get32bit(&ptr);

	printf("# maxnodeid: %"PRIu32" ; version: %"PRIu64" ; nextsessionid: %"PRIu32"\n",maxnodeid,version,nextsessionid);

	printf("# -------------------------------------------------------------------\n");
	if (fs_loadnodes(fd,0x15,0x10)<0) {
		printf("error reading metadata (node)\n");
		return -1;
	}
	printf("# -------------------------------------------------------------------\n");
	if (fs_loadedges(fd,0x10)<0) {
		printf("error reading metadata (edge)\n");
		return -1;
	}
	printf("# -------------------------------------------------------------------\n");
	if (fs_loadfree(fd,0x10)<0) {
		printf("error reading metadata (free)\n");
		return -1;
	}
	printf("# -------------------------------------------------------------------\n");
	return 0;
}

int fs_load(FILE *fd,uint8_t fver,const char section[4]) {
	uint32_t maxnodeid,nextsessionid;
	uint64_t sleng;
	off_t offbegin;
	uint64_t version,fileid;
	uint8_t hdr[16];
	const uint8_t *ptr;
	uint8_t mver;
	if (fread(hdr,1,16,fd)!=16) {
		return -1;
	}
	ptr = hdr;

	if (fver<0x20) {
		maxnodeid = get32bit(&ptr);
		version = get64bit(&ptr);
		nextsessionid = get32bit(&ptr);

		if (section[0]==0 || memcmp(section,"HEAD",4)==0) {
			printf("# maxnodeid: %"PRIu32" ; version: %"PRIu64" ; nextsessionid: %"PRIu32"\n",maxnodeid,version,nextsessionid);
		}
	} else {
		version = get64bit(&ptr);
		fileid = get64bit(&ptr);

		if (section[0]==0 || memcmp(section,"HEAD",4)==0) {
			printf("# version: %"PRIu64" ; fileid: 0x%"PRIX64"\n",version,fileid);
		}
	}

	while (1) {
		if (fread(hdr,1,16,fd)!=16) {
			printf("can't read section header\n");
			return -1;
		}
		if (memcmp(hdr,"[MFS EOF MARKER]",16)==0) {
			if (section[0]==0) {
				printf("# -------------------------------------------------------------------\n");
				printf("# MFS END OF FILE MARKER\n");
				printf("# -------------------------------------------------------------------\n");
			}
			return 0;
		}
		ptr = hdr+8;
		sleng = get64bit(&ptr);
		offbegin = ftello(fd);
		if (section[0]==0 || memcmp(hdr,section,4)==0) {
			printf("# -------------------------------------------------------------------\n");
			printf("# section header: %c%c%c%c%c%c%c%c (%02X%02X%02X%02X%02X%02X%02X%02X) ; length: %"PRIu64"\n",dispchar(hdr[0]),dispchar(hdr[1]),dispchar(hdr[2]),dispchar(hdr[3]),dispchar(hdr[4]),dispchar(hdr[5]),dispchar(hdr[6]),dispchar(hdr[7]),hdr[0],hdr[1],hdr[2],hdr[3],hdr[4],hdr[5],hdr[6],hdr[7],sleng);
			mver = (((hdr[5]-'0')&0xF)<<4)+(hdr[7]&0xF);
			printf("# section type: %c%c%c%c ; version: 0x%02"PRIX8"\n",dispchar(hdr[0]),dispchar(hdr[1]),dispchar(hdr[2]),dispchar(hdr[3]),mver);
			if (memcmp(hdr,"SESS",4)==0) {
				if (sessions_load(fd,mver)<0) {
					printf("error reading metadata (SESS)\n");
					return -1;
				}
			} else if (memcmp(hdr,"LABS",4)==0) {
				if (sclass_load(fd,mver)<0) {
					printf("error reading metadata (LABS)\n");
					return -1;
				}
			} else if (memcmp(hdr,"SCLA",4)==0) {
				if (sclass_load(fd,mver)<0) {
					printf("error reading metadata (SCLA)\n");
					return -1;
				}
			} else if (memcmp(hdr,"PATT",4)==0) {
				if (patterns_load(fd,mver)<0) {
					printf("error reading metadata (PATT)\n");
					return -1;
				}
			} else if (memcmp(hdr,"NODE",4)==0) {
				if (fs_loadnodes(fd,fver,mver)<0) {
					printf("error reading metadata (NODE)\n");
					return -1;
				}
			} else if (memcmp(hdr,"EDGE",4)==0) {
				if (fs_loadedges(fd,mver)<0) {
					printf("error reading metadata (EDGE)\n");
					return -1;
				}
			} else if (memcmp(hdr,"FREE",4)==0) {
				if (fs_loadfree(fd,mver)<0) {
					printf("error reading metadata (FREE)\n");
					return -1;
				}
			} else if (memcmp(hdr,"QUOT",4)==0) {
				if (fs_loadquota(fd,mver)<0) {
					printf("error reading metadata (QUOT)\n");
					return -1;
				}
			} else if (memcmp(hdr,"XATR",4)==0) {
				if (xattr_load(fd,mver)<0) {
					printf("error reading metadata (XATR)\n");
					return -1;
				}
			} else if (memcmp(hdr,"PACL",4)==0) {
				if (posix_acl_load(fd,mver)<0) {
					printf("error reading metadata (PACL)\n");
					return -1;
				}
			} else if (memcmp(hdr,"FLCK",4)==0) {
				if (flock_load(fd,mver)<0) {
					printf("error reading metadata (FLCK)\n");
					return -1;
				}
			} else if (memcmp(hdr,"PLCK",4)==0) {
				if (posix_lock_load(fd,mver)<0) {
					printf("error reading metadata (PLCK)\n");
					return -1;
				}
			} else if (memcmp(hdr,"OPEN",4)==0) {
				if (of_load(fd,mver)<0) {
					printf("error reading metadata (OPEN)\n");
					return -1;
				}
			} else if (memcmp(hdr,"CSDB",4)==0) {
				if (csdb_load(fd,mver)<0) {
					printf("error reading metadata (CSDB)\n");
					return -1;
				}
			} else if (memcmp(hdr,"CHNK",4)==0) {
				if (chunk_load(fd,mver)<0) {
					printf("error reading metadata (CHNK)\n");
					return -1;
				}
			} else {
				printf("unknown file part\n");
				if (hexdump(fd,sleng)<0) {
					return -1;
				}
			}
			if ((off_t)(offbegin+sleng)!=ftello(fd)) {
				fprintf(stderr,"some data in this section have not been read - file corrupted\n");
				return -1;
			}
		} else {
			fseeko(fd,sleng,SEEK_CUR);
		}
	}
	return 0;
}

int fs_loadall(const char *fname,const char section[4]) {
	FILE *fd;
	uint8_t hdr[8];
	uint8_t fver;

	fd = fopen(fname,"r");

	if (fd==NULL) {
		printf("can't open metadata file\n");
		return -1;
	}
	if (fread(hdr,1,8,fd)!=8) {
		printf("can't read metadata header\n");
		fclose(fd);
		return -1;
	}
	if (memcmp(section,"HEAD",4)==0 || section[0]==0) {
		printf("# header: %c%c%c%c%c%c%c%c (%02X%02X%02X%02X%02X%02X%02X%02X)\n",dispchar(hdr[0]),dispchar(hdr[1]),dispchar(hdr[2]),dispchar(hdr[3]),dispchar(hdr[4]),dispchar(hdr[5]),dispchar(hdr[6]),dispchar(hdr[7]),hdr[0],hdr[1],hdr[2],hdr[3],hdr[4],hdr[5],hdr[6],hdr[7]);
	}
	if (memcmp(hdr,"MFSM NEW",8)==0) {
		printf("empty file\n");
	} else if (memcmp(hdr,MFSSIGNATURE "M ",5)==0 && hdr[5]>='1' && hdr[5]<='9' && hdr[6]=='.' && hdr[7]>='0' && hdr[7]<='9') {
		fver = ((hdr[5]-'0')<<4)+(hdr[7]-'0');
		if (fver<0x17) {
			if (section[0]!=0) {
				printf("old format detected - can't dump sections separatelly");
				fclose(fd);
				return -1;
			}
			if (fs_load_pre17(fd)<0) {
				printf("error reading metadata (structure)\n");
				fclose(fd);
				return -1;
			}
			if (chunk_load(fd,0x10)<0) {
				printf("error reading metadata (chunks)\n");
				fclose(fd);
				return -1;
			}
		} else {
			if (fs_load(fd,fver,section)<0) {
				fclose(fd);
				return -1;
			}
		}
	} else {
		printf("wrong metadata header (old version ?)\n");
		fclose(fd);
		return -1;
	}
	if (ferror(fd)!=0) {
		printf("error reading metadata\n");
		fclose(fd);
		return -1;
	}
	fclose(fd);
	return 0;
}

void usage(const char *appname) {
//	printf("usage: %s [-f J|C[separator]] [-o outputfile] [-a sum_name] metadata.mfs PATH ...\n",appname);
	printf("Dumps metadata file or selected section of it into human readable format.\n");
	printf("\n");
	printf("usage: %s [-d] [-s section to dump] metadata.mfs\n",appname);
	printf("options:\n");
	printf("\td: print dot instead of non printable characters\n");
	printf("\ts: dump only given section\n");
	printf("section names:\n");
	printf("\tHEAD - header info\n");
	printf("\tSESS - client sessions\n");
	printf("\tLABS - labels (not used)\n");
	printf("\tSCLA - storage classes\n");
	printf("\tPATT - file patterns\n");
	printf("\tNODE - tree nodes (i-nodes)\n");
	printf("\tEDGE - tree edges (file names)\n");
	printf("\tFREE - free nodes (deleted i-nodes)\n");
	printf("\tQUOT - quota definitions\n");
	printf("\tXATR - xattr data\n");
	printf("\tPACL - posix acl data\n");
	printf("\tOPEN - open files\n");
	printf("\tFLCK - flock data\n");
	printf("\tPLCK - posix locks (lockf,ioctl) data\n");
	printf("\tCSDB - active chunkservers\n");
	printf("\tCHNK - chunks\n");
	exit(1);
}

int main(int argc,char *argv[]) {
	int ch;
	char *appname;
	char section[4];

	appname = argv[0];
	memset(section,0,4);

	while ((ch=getopt(argc,argv,"s:d"))>=0) {
		switch(ch) {
			case 's':
				if (strlen(optarg)!=4) {
					printf("wrong section name\n");
					usage(appname);
				}
				memcpy(section,optarg,4);
				break;
			case 'd':
				hide_nonprintable = 1;
				break;
			default:
				usage(appname);
				return 1;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc<1) {
		usage(appname);
		return 1;
	}

//	if (argc!=2) {
//		printf("usage: %s metadata_file\n",argv[0]);
//		return 1;
//	}
	return (fs_loadall(argv[0],section)<0)?1:0;
}
