1 /*
2  * Copyright (C) 2021 Jakub Kruszona-Zawadzki, Core Technology Sp. z o.o.
3  *
4  * This file is part of MooseFS.
5  *
6  * MooseFS is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, version 2 (only).
9  *
10  * MooseFS is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with MooseFS; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA
18  * or visit http://www.gnu.org/licenses/gpl-2.0.html
19  */
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 
26 #include "datapack.h"
27 #include "MFSCommunication.h"
28 #include "liset64.h"
29 #include "idstr.h"
30 
31 // #define DEBUG 1
32 
33 #define PROGRESS_PRINT 100000
34 
35 //static uint8_t humode=0;
36 //static uint8_t numbermode=0;
37 
38 enum {
39 	STATUS_OK = 0,
40 	STATUS_ENOENT = 1,
41 	STATUS_ANY = 2
42 };
43 
44 #define PHN_USESI       0x01
45 #define PHN_USEIEC      0x00
mfs_humanize_number(uint64_t number,uint8_t flags)46 char* mfs_humanize_number(uint64_t number,uint8_t flags) {
47 	static char numbuf[6];	// [ "xxx" , "xx" , "x" , "x.x" ] + ["" , "X" , "Xi"]
48 	uint64_t divisor;
49 	uint16_t b;
50 	uint8_t i;
51 	uint8_t scale;
52 
53 	if (flags & PHN_USESI) {
54 		divisor = 1000;
55 	} else {
56 		divisor = 1024;
57 	}
58 	if (number>(UINT64_MAX/100)) {
59 		number /= divisor;
60 		number *= 100;
61 		scale = 1;
62 	} else {
63 		number *= 100;
64 		scale = 0;
65 	}
66 	while (number>=99950) {
67 		number /= divisor;
68 		scale+=1;
69 	}
70 	i=0;
71 	if (number<995 && scale>0) {
72 		b = ((uint32_t)number + 5) / 10;
73 		numbuf[i++]=(b/10)+'0';
74 		numbuf[i++]='.';
75 		numbuf[i++]=(b%10)+'0';
76 	} else {
77 		b = ((uint32_t)number + 50) / 100;
78 		if (b>=100) {
79 			numbuf[i++]=(b/100)+'0';
80 			b%=100;
81 		}
82 		if (b>=10 || i>0) {
83 			numbuf[i++]=(b/10)+'0';
84 			b%=10;
85 		}
86 		numbuf[i++]=b+'0';
87 	}
88 	if (scale>0) {
89 		if (flags&PHN_USESI) {
90 			numbuf[i++]="-kMGTPE"[scale];
91 		} else {
92 			numbuf[i++]="-KMGTPE"[scale];
93 			numbuf[i++]='i';
94 		}
95 	}
96 	numbuf[i++]='\0';
97 	return numbuf;
98 }
99 
100 typedef struct _metasection {
101 	off_t offset;
102 	uint64_t length;
103 	uint8_t mver;
104 } metasection;
105 
106 typedef struct _stats {
107 	uint32_t files;
108 	uint32_t dirs;
109 	uint64_t kchunks;
110 	uint64_t achunks;
111 	uint64_t length;
112 	uint64_t size;
113 	uint64_t keeprsize;
114 	uint64_t archrsize;
115 	uint64_t rsize;
116 } stats;
117 
118 typedef struct _dirinfostate {
119 	stats s;
120 	int inode_liset;
121 	int chunk_liset;
122 	char *path;
123 	const char *pptr;
124 	uint32_t inode;
125 	uint16_t nleng;
126 	uint8_t wantchunks;
127 	uint8_t pathscan;
128 	uint8_t parent_found;
129 	uint8_t nextsection;
130 	uint8_t status;
131 	struct _dirinfostate *next;
132 } dirinfostate;
133 
134 static dirinfostate *dishead;
135 
136 static int achunk_liset;
137 
138 static uint8_t sclass_keep_factor[256];
139 static uint8_t sclass_arch_factor[256];
140 
scan_sclass(FILE * fd,metasection * sdata)141 int scan_sclass(FILE *fd,metasection *sdata) {
142 	uint8_t buff[11];
143 	const uint8_t *rptr;
144 	uint8_t i,l;
145 	uint8_t ogroup;
146 	uint8_t nleng;
147 	uint16_t sclassid;
148 	uint8_t createcnt;
149 	uint8_t keepcnt;
150 	uint8_t archcnt;
151 	uint32_t chunkcount;
152 
153 	fseeko(fd,sdata->offset,SEEK_SET);
154 
155 	for (sclassid=0 ; sclassid<256 ; sclassid++) {
156 		sclass_keep_factor[sclassid]=(sclassid<10)?sclassid:0;
157 		sclass_arch_factor[sclassid]=(sclassid<10)?sclassid:0;
158 	}
159 
160 	if (sdata->mver>0x16) {
161 		fprintf(stderr,"unsupported sclass format\n");
162 		return -1;
163 	}
164 	if (sdata->mver<0x15) {
165 		for (i=0 ; i<26 ; i++) {
166 			l = fgetc(fd);
167 			if (l>128) {
168 				fprintf(stderr,"sclass section malformed\n");
169 				return -1;
170 			}
171 			fseeko(fd,l,SEEK_CUR);
172 		}
173 	}
174 	if (sdata->mver==0x10) {
175 		ogroup = 1;
176 	} else {
177 		ogroup = fgetc(fd);
178 	}
179 	if (ogroup<1) {
180 		fprintf(stderr,"sclass section malformed\n");
181 		return -1;
182 	}
183 	l = (sdata->mver==0x12)?11:(sdata->mver<=0x13)?3:(sdata->mver<=0x14)?5:(sdata->mver<=0x15)?8:10;
184 	while (1) {
185 		if (fread(buff,1,l,fd)!=l) {
186 			fprintf(stderr,"loading labelset: read error\n");
187 			return -1;
188 		}
189 		rptr = buff;
190 		sclassid = get16bit(&rptr);
191 		if (sdata->mver>0x15) {
192 			nleng = get8bit(&rptr);
193 			rptr+=4; // skip admin_only,create_mode,arch_delay
194 			createcnt = get8bit(&rptr);
195 			keepcnt = get8bit(&rptr);
196 			archcnt = get8bit(&rptr);
197 			chunkcount = 0;
198 		} else if (sdata->mver>0x14) {
199 			nleng = 0;
200 			rptr+=2; // skip create_mode,arch_delay
201 			createcnt = get8bit(&rptr);
202 			keepcnt = get8bit(&rptr);
203 			archcnt = get8bit(&rptr);
204 			chunkcount = 0;
205 		} else if (sdata->mver>0x13) {
206 			nleng = 0;
207 			rptr++; // skip create_mode
208 			createcnt = get8bit(&rptr);
209 			keepcnt = get8bit(&rptr);
210 			archcnt = keepcnt;
211 			chunkcount = 0;
212 		} else {
213 			nleng = 0;
214 			createcnt = get8bit(&rptr);
215 			keepcnt = createcnt;
216 			archcnt = createcnt;
217 			if (sdata->mver==0x12) {
218 				chunkcount = get32bit(&rptr);
219 			} else {
220 				chunkcount = 0;
221 			}
222 		}
223 		if (nleng>0) {
224 			fseeko(fd,nleng,SEEK_CUR);
225 		}
226 		if (sclassid==0 && createcnt==0 && keepcnt==0 && archcnt==0) {
227 			break;
228 		}
229 		if (keepcnt==0 || keepcnt>9 || archcnt==0 || archcnt>9) {
230 			fprintf(stderr,"sclass section malformed\n");
231 			return -1;
232 		}
233 		if (sdata->mver>0x14) {
234 			fseeko(fd,(createcnt+keepcnt+archcnt)*4*ogroup,SEEK_CUR);
235 		} else if (sdata->mver>0x13) {
236 			fseeko(fd,(createcnt+keepcnt)*4*ogroup,SEEK_CUR);
237 		} else {
238 			fseeko(fd,createcnt*4*ogroup,SEEK_CUR);
239 		}
240 		if (chunkcount>0) {
241 			fseeko(fd,chunkcount*8,SEEK_CUR);
242 		}
243 		sclass_keep_factor[sclassid] = keepcnt;
244 		sclass_arch_factor[sclassid] = archcnt;
245 	}
246 	return 0;
247 }
248 
249 // mver 0x13 and 0x14
scan_nodes(FILE * fd,metasection * sdata)250 int scan_nodes(FILE *fd,metasection *sdata) {
251 	uint8_t buff[14];
252 	const uint8_t *rptr;
253 	uint8_t type;
254 	uint32_t inode;
255 	uint32_t ch;
256 	uint16_t sessions;
257 	uint64_t length,l;
258 	uint8_t sclass;
259 	uint8_t wantchunks;
260 	uint32_t progresscnt;
261 	dirinfostate *dis;
262 
263 	fseeko(fd,sdata->offset,SEEK_SET);
264 	fseeko(fd,8,SEEK_CUR); // skip maxnodeid,hashelements
265 	progresscnt = 0;
266 	while (1) {
267 		progresscnt++;
268 		if (progresscnt>=PROGRESS_PRINT) {
269 			progresscnt = 0;
270 			fprintf(stderr,"node scan: %.2lf%%\r",100.0*(ftello(fd)-sdata->offset)/sdata->length);
271 			fflush(stderr);
272 		}
273 		type = fgetc(fd);
274 		if (type==0) { // end of section
275 			fprintf(stderr,"node scan: 100.00%%\n");
276 			return 0; // ok
277 		}
278 		if (fread(buff,1,5,fd)!=5) {
279 			fprintf(stderr,"error reading metadata file\n");
280 			return -1;
281 		}
282 		rptr = buff;
283 		inode = get32bit(&rptr);
284 		sclass = get8bit(&rptr);
285 		if (sdata->mver<=0x13) {
286 			fseeko(fd,27,SEEK_CUR); // 27 = 1+2+6*4 - flags,mode,uid,gid,atime,mtime,ctime,trashtime
287 		} else {
288 			fseeko(fd,26,SEEK_CUR); // 26 = 1+1+2+5*4+2 - flags,winattr,mode,uid,gid,atime,mtime,ctime,trashretention
289 		}
290 		if (type==TYPE_BLOCKDEV || type==TYPE_CHARDEV) {
291 			fseeko(fd,4,SEEK_CUR);
292 		} else if (type==TYPE_SYMLINK) {
293 			uint32_t pleng;
294 			if (fread(buff,1,4,fd)!=4) {
295 				fprintf(stderr,"error reading metadata file\n");
296 				return -1;
297 			}
298 			rptr = buff;
299 			pleng = get32bit(&rptr);
300 			fseeko(fd,pleng,SEEK_CUR);
301 		} else if (type==TYPE_FILE || type==TYPE_TRASH || type==TYPE_SUSTAINED) {
302 			if (sdata->mver<=0x13) {
303 				if (fread(buff,1,14,fd)!=14) {
304 					fprintf(stderr,"error reading metadata file\n");
305 					return -1;
306 				}
307 			} else {
308 				if (fread(buff,1,12,fd)!=12) {
309 					fprintf(stderr,"error reading metadata file\n");
310 					return -1;
311 				}
312 			}
313 			rptr = buff;
314 			length = get64bit(&rptr);
315 			ch = get32bit(&rptr);
316 			if (sdata->mver<0x13) {
317 				sessions = get16bit(&rptr);
318 			} else {
319 				sessions = 0;
320 			}
321 			wantchunks = 0;
322 			for (dis=dishead ; dis!=NULL ; dis=dis->next) {
323 				if ((dis->status==STATUS_OK || dis->status==STATUS_ANY) && liset_check(dis->inode_liset,inode)) {
324 					dis->s.length += length;
325 					dis->s.files++;
326 					wantchunks = 1;
327 					dis->wantchunks = 1;
328 				} else {
329 					dis->wantchunks = 0;
330 				}
331 			}
332 			if (wantchunks) {
333 				l = length;
334 				while (ch) {
335 					uint64_t chunkid;
336 					uint32_t chunksize;
337 					if (fread(buff,1,8,fd)!=8) {
338 						fprintf(stderr,"error reading metadata file\n");
339 						return -1;
340 					}
341 					rptr = buff;
342 					chunkid = get64bit(&rptr);
343 					if (chunkid>0) {
344 						if (l>MFSCHUNKSIZE) {
345 							chunksize = MFSCHUNKSIZE + MFSHDRSIZE;
346 							l -= MFSCHUNKSIZE;
347 						} else if (l>0) {
348 							chunksize = ((((l-1)&MFSCHUNKMASK)+MFSBLOCKSIZE)&MFSBLOCKNEGMASK)+MFSHDRSIZE;
349 							l = 0;
350 						} else {
351 							chunksize = MFSHDRSIZE;
352 						}
353 						for (dis=dishead ; dis!=NULL ; dis=dis->next) {
354 							if (dis->wantchunks && liset_check(dis->chunk_liset,chunkid)==0) {
355 								dis->s.size += chunksize;
356 								dis->s.keeprsize += chunksize * sclass_keep_factor[sclass];
357 								dis->s.archrsize += chunksize * sclass_arch_factor[sclass];
358 								if (liset_check(achunk_liset,chunkid)) {
359 									dis->s.achunks++;
360 									dis->s.rsize += chunksize * sclass_arch_factor[sclass];
361 								} else {
362 									dis->s.kchunks++;
363 									dis->s.rsize += chunksize * sclass_keep_factor[sclass];
364 								}
365 								liset_addval(dis->chunk_liset,chunkid);
366 							}
367 						}
368 					}
369 					ch--;
370 				}
371 			} else {
372 				fseeko(fd,ch*8,SEEK_CUR);
373 			}
374 			fseeko(fd,sessions*4U,SEEK_CUR);
375 		} else if (type==TYPE_DIRECTORY) {
376 			for (dis=dishead ; dis!=NULL ; dis=dis->next) {
377 				if ((dis->status==STATUS_OK || dis->status==STATUS_ANY) && liset_check(dis->inode_liset,inode)) {
378 					dis->s.dirs++;
379 				}
380 			}
381 		}
382 	}
383 	return -1;
384 }
385 
scan_edges(FILE * fd,metasection * sdata)386 int scan_edges(FILE *fd,metasection *sdata) {
387 	uint8_t edgebuff[18];
388 	uint8_t name[256];
389 	const uint8_t *rptr;
390 	uint16_t nleng;
391 	uint32_t parent,child;
392 	uint32_t progresscnt;
393 	dirinfostate *dis;
394 
395 	for (dis=dishead ; dis!=NULL ; dis=dis->next) {
396 		dis->pptr = dis->path;
397 		dis->inode = MFS_ROOT_ID;
398 		dis->parent_found = 0;
399 		dis->pathscan = 1;
400 		dis->nextsection = 1;
401 	}
402 
403 	fseeko(fd,sdata->offset,SEEK_SET);
404 	fseeko(fd,8,SEEK_CUR); // skip 'nextedgeid'
405 	progresscnt = 0;
406 	while (1) {
407 		progresscnt++;
408 		if (progresscnt>=PROGRESS_PRINT) {
409 			progresscnt = 0;
410 			fprintf(stderr,"edge scan: %.2lf%%\r",100.0*(ftello(fd)-sdata->offset)/sdata->length);
411 			fflush(stderr);
412 		}
413 		for (dis=dishead ; dis!=NULL ; dis=dis->next) {
414 			if (dis->status==STATUS_OK && dis->nextsection) {
415 				while (*dis->pptr=='/') {
416 					dis->pptr++;
417 				}
418 				dis->nleng = 0;
419 				while (dis->pptr[dis->nleng] && dis->pptr[dis->nleng]!='/') {
420 					dis->nleng++;
421 				}
422 				if (dis->nleng>255) {
423 					dis->status = STATUS_ENOENT;
424 				}
425 				if (dis->nleng==0) {
426 #ifdef DEBUG
427 					printf("path %s : inode found: %"PRIu32"\n",dis->path,dis->inode);
428 #endif
429 					liset_addval(dis->inode_liset,dis->inode);
430 					if (dishead->status==STATUS_ANY) { // all node
431 						liset_addval(dishead->inode_liset,dis->inode);
432 					}
433 					dis->pathscan = 0;
434 				}
435 				dis->nextsection=0;
436 			} // section: pptr,snleng
437 		}
438 		if (fread(edgebuff,1,18,fd)!=18) {
439 			fprintf(stderr,"error reading metadata file\n");
440 			return -1;
441 		}
442 		rptr = edgebuff;
443 		parent = get32bit(&rptr);
444 		child = get32bit(&rptr);
445 		rptr += 8; // skip 'edgeid'
446 		nleng = get16bit(&rptr);
447 		if (parent==0 && child==0) {
448 			fprintf(stderr,"edge scan: 100.00%%\n");
449 			return 0;
450 		}
451 		if (parent==0 && nleng>MFS_PATH_MAX) {
452 			fprintf(stderr,"path name too long (%"PRIu16")\n",nleng);
453 			return -1;
454 		}
455 		if (parent!=0 && nleng>MFS_NAME_MAX) {
456 			fprintf(stderr,"name name too long (%"PRIu16")\n",nleng);
457 			return -1;
458 		}
459 		if (parent==0) {
460 			fseeko(fd,nleng,SEEK_CUR);
461 		} else {
462 			if (fread(name,1,nleng,fd)!=nleng) {
463 				fprintf(stderr,"error reading metadata file\n");
464 				return -1;
465 			}
466 			for (dis=dishead ; dis!=NULL ; dis=dis->next) {
467 				if (dis->status==STATUS_OK) {
468 					if (dis->pathscan) {
469 						if (parent==dis->inode) {
470 							if (nleng==dis->nleng && memcmp(name,dis->pptr,nleng)==0) {
471 								dis->inode = child;
472 								dis->nextsection = 1;
473 								dis->parent_found = 0;
474 #ifdef DEBUG
475 								name[nleng]=0;
476 								printf("found edge %u -> %u (%s)\n",parent,child,name);
477 #endif
478 								dis->pptr += nleng;
479 							} else {
480 								dis->parent_found = 1;
481 							}
482 						} else {
483 							if (dis->parent_found) {
484 								dis->status = STATUS_ENOENT;
485 							}
486 						}
487 					} else {
488 						if (liset_check(dis->inode_liset,parent)) {
489 							liset_addval(dis->inode_liset,child);
490 							if (dishead->status==STATUS_ANY) { // all node
491 								liset_addval(dishead->inode_liset,child);
492 							}
493 						}
494 					}
495 				}
496 			}
497 		}
498 	}
499 }
500 
scan_chunks(FILE * fd,metasection * sdata)501 int scan_chunks(FILE *fd,metasection *sdata) {
502 	uint8_t buff[17];
503 	const uint8_t *rptr;
504 	uint64_t chunkid;
505 	uint8_t flags;
506 	uint32_t progresscnt;
507 
508 	if (sdata->mver>0x11) {
509 		fprintf(stderr,"loading chunks: unsupported format\n");
510 		return -1;
511 	}
512 	if (sdata->mver==0x10) { // no archive bit - just do nothhing
513 		return 0;
514 	}
515 	fseeko(fd,sdata->offset,SEEK_SET);
516 	fseeko(fd,8,SEEK_CUR); // skip nextchunkid
517 	progresscnt = 0;
518 	while (1) {
519 		progresscnt++;
520 		if (progresscnt>=PROGRESS_PRINT) {
521 			progresscnt = 0;
522 			fprintf(stderr,"chunk scan: %.2lf%%\r",100.0*(ftello(fd)-sdata->offset)/sdata->length);
523 			fflush(stderr);
524 		}
525 		if (fread(buff,1,17,fd)!=17) {
526 			fprintf(stderr,"error reading metadata file\n");
527 			return -1;
528 		}
529 		rptr = buff;
530 		chunkid = get64bit(&rptr);
531 		rptr+=8; // skip version,lockedto
532 		flags = get8bit(&rptr);
533 		if (chunkid==0) {
534 			fprintf(stderr,"chunk scan: 100.00%%\n");
535 			return 0;
536 		}
537 		if (flags!=0) { // arch
538 			liset_addval(achunk_liset,chunkid);
539 		}
540 	}
541 }
542 
calc_dirinfos(FILE * fd)543 int calc_dirinfos(FILE *fd) {
544 	uint8_t hdr[16];
545 	uint8_t mver;
546 	const uint8_t *rptr;
547 	uint64_t sleng;
548 	metasection edge,node,scla,chnk;
549 	dirinfostate *dis;
550 
551 	fseeko(fd,8+16,SEEK_SET);
552 	edge.offset = 0;
553 	node.offset = 0;
554 	scla.offset = 0;
555 	chnk.offset = 0;
556 
557 	// find metadata file sections
558 	while (1) {
559 		if (fread(hdr,1,16,fd)!=16) {
560 			printf("can't read section header\n");
561 			return -1;
562 		}
563 		if (memcmp(hdr,"[MFS EOF MARKER]",16)==0) {
564 			break;
565 		}
566 		mver = (((hdr[5]-'0')&0xF)<<4)+(hdr[7]&0xF);
567 		rptr = hdr+8;
568 		sleng = get64bit(&rptr);
569 #if DEBUG>1
570 		printf("section %c%c%c%c ; version: %c.%c ; %"PRIu64" bytes\n",hdr[0],hdr[1],hdr[2],hdr[3],hdr[5],hdr[7],sleng);
571 #endif
572 		if (memcmp(hdr,"EDGE",4)==0) {
573 			edge.offset = ftello(fd);
574 			edge.length = sleng;
575 			edge.mver = mver;
576 #ifdef DEBUG
577 			printf("found EDGE section (%.2lf GiB)\n",sleng/(1024.0*1024.0*1024.0));
578 #endif
579 		} else if (memcmp(hdr,"NODE",4)==0) {
580 			node.offset = ftello(fd);
581 			node.length = sleng;
582 			node.mver = mver;
583 #ifdef DEBUG
584 			printf("found NODE section (%.2lf GiB)\n",sleng/(1024.0*1024.0*1024.0));
585 #endif
586 		} else if (memcmp(hdr,"SCLA",4)==0 || memcmp(hdr,"LABS",4)==0) {
587 			scla.offset = ftello(fd);
588 			scla.length = sleng;
589 			scla.mver = mver;
590 #ifdef DEBUG
591 			printf("found SCLA section (%.2lf kB)\n",sleng/1024.0);
592 #endif
593 		} else if (memcmp(hdr,"CHNK",4)==0) {
594 			chnk.offset = ftello(fd);
595 			chnk.length = sleng;
596 			chnk.mver = mver;
597 #ifdef DEBUG
598 			printf("found CHNK section (%.2lf GiB)\n",sleng/(1024.0*1024.0*1024.0));
599 #endif
600 		}
601 		fseeko(fd,sleng,SEEK_CUR);
602 	}
603 
604 	if (edge.offset==0) {
605 		fprintf(stderr,"can't find EDGE section in metadata file\n");
606 		return -1;
607 	}
608 	if (node.offset==0) {
609 		fprintf(stderr,"can't find NODE section in metadata file\n");
610 		return -1;
611 	}
612 	if (scla.offset==0) {
613 		fprintf(stderr,"can't find SCLA(SS) section in metadata file\n");
614 		return -1;
615 	}
616 	if (chnk.offset==0) {
617 		fprintf(stderr,"can't find CH(U)NK section in metadata file\n");
618 		return -1;
619 	}
620 	if (node.mver<0x13 || node.mver>0x14 || edge.mver!=0x11 || chnk.mver!=0x11) { // MFS 3.x
621 		fprintf(stderr,"unsupported metadata format (MFS 3.x needed)\n");
622 		return -1;
623 	}
624 
625 	// find keep/arch factors
626 	fseeko(fd,scla.offset,SEEK_SET);
627 	if (scan_sclass(fd,&scla)<0) {
628 		return -1;
629 	}
630 
631 	// build inode set
632 	if (scan_edges(fd,&edge)<0) {
633 		return -1;
634 	}
635 
636 	for (dis = dishead ; dis!=NULL ; dis=dis->next) {
637 		if (dis->status==STATUS_ENOENT) {
638 			fprintf(stderr,"path '%s': no such file or directory\n",dis->path);
639 		}
640 	}
641 
642 	// add to achunk_liset chunks in arch mode
643 	if (scan_chunks(fd,&chnk)<0) {
644 		return -1;
645 	}
646 
647 	// build chunk set / calculate results
648 	if (scan_nodes(fd,&node)<0) {
649 		return -1;
650 	}
651 
652 	return 0;
653 }
654 
print_result_plain(FILE * ofd)655 void print_result_plain(FILE *ofd) {
656 	dirinfostate *dis;
657 	fprintf(ofd,"------------------------------\n");
658 	for (dis = dishead ; dis!=NULL ; dis=dis->next) {
659 		fprintf(ofd,"path: %s\n",dis->path);
660 		if (dis->status!=STATUS_ENOENT) {
661 			fprintf(ofd,"inodes: %"PRIu64"\n",liset_card(dis->inode_liset));
662 			fprintf(ofd," files: %"PRIu32"\n",dis->s.files);
663 			fprintf(ofd," dirs: %"PRIu32"\n",dis->s.dirs);
664 			fprintf(ofd,"chunks: %"PRIu64"\n",liset_card(dis->chunk_liset));
665 			fprintf(ofd," keep chunks: %"PRIu64"\n",dis->s.kchunks);
666 			fprintf(ofd," arch chunks: %"PRIu64"\n",dis->s.achunks);
667 			fprintf(ofd,"length: %"PRIu64" = %5sB\n",dis->s.length,mfs_humanize_number(dis->s.length,PHN_USEIEC));
668 			fprintf(ofd,"size: %"PRIu64" = %5sB\n",dis->s.size,mfs_humanize_number(dis->s.size,PHN_USEIEC));
669 			fprintf(ofd,"keep size: %"PRIu64" = %5sB\n",dis->s.keeprsize,mfs_humanize_number(dis->s.keeprsize,PHN_USEIEC));
670 			fprintf(ofd,"arch size: %"PRIu64" = %5sB\n",dis->s.archrsize,mfs_humanize_number(dis->s.archrsize,PHN_USEIEC));
671 			fprintf(ofd,"real size: %"PRIu64" = %5sB\n",dis->s.rsize,mfs_humanize_number(dis->s.rsize,PHN_USEIEC));
672 		} else {
673 			fprintf(ofd,"path not found !!!\n");
674 		}
675 		fprintf(ofd,"------------------------------\n");
676 	}
677 }
678 
print_result_json(FILE * ofd)679 void print_result_json(FILE *ofd) {
680 	dirinfostate *dis;
681 	char c;
682 	int j;
683 
684 	fprintf(ofd,"{\n");
685 	for (dis = dishead ; dis!=NULL ; dis=dis->next) {
686 		fprintf(ofd,"\t\"");
687 		for (j=0 ; dis->path[j]!=0 ; j++) {
688 			c = dis->path[j];
689 			switch (c) {
690 				case '\b':
691 					fprintf(ofd,"\\b");
692 					break;
693 				case '\n':
694 					fprintf(ofd,"\\n");
695 					break;
696 				case '\r':
697 					fprintf(ofd,"\\r");
698 					break;
699 				case '\f':
700 					fprintf(ofd,"\\f");
701 					break;
702 				case '\t':
703 					fprintf(ofd,"\\t");
704 					break;
705 				case '"':
706 					fprintf(ofd,"\\\"");
707 					break;
708 				case '\\':
709 					fprintf(ofd,"\\\\");
710 					break;
711 				default:
712 					fputc(c,ofd);
713 			}
714 		}
715 		fprintf(ofd,"\": {\n");
716 		fprintf(ofd,"\t\t\"inodes\": %"PRIu64",\n",liset_card(dis->inode_liset));
717 		fprintf(ofd,"\t\t\"files\": %"PRIu32",\n",dis->s.files);
718 		fprintf(ofd,"\t\t\"dirs\": %"PRIu32",\n",dis->s.dirs);
719 		fprintf(ofd,"\t\t\"chunks\": %"PRIu64",\n",liset_card(dis->chunk_liset));
720 		fprintf(ofd,"\t\t\"kchunks\": %"PRIu64",\n",dis->s.kchunks);
721 		fprintf(ofd,"\t\t\"achunks\": %"PRIu64",\n",dis->s.achunks);
722 		fprintf(ofd,"\t\t\"length\": %"PRIu64",\n",dis->s.length);
723 		fprintf(ofd,"\t\t\"size\": %"PRIu64",\n",dis->s.size);
724 		fprintf(ofd,"\t\t\"rsize\": %"PRIu64",\n",dis->s.rsize);
725 		fprintf(ofd,"\t\t\"ksize\": %"PRIu64",\n",dis->s.keeprsize);
726 		fprintf(ofd,"\t\t\"asize\": %"PRIu64",\n",dis->s.archrsize);
727 		fprintf(ofd,"\t\t\"error\": %u\n",(dis->status!=STATUS_ENOENT)?0:1);
728 		fprintf(ofd,"\t}%s\n",(dis->next!=NULL)?",":"");
729 	}
730 	fprintf(ofd,"}\n");
731 }
732 
print_result_csv(FILE * ofd,char s)733 void print_result_csv(FILE *ofd,char s) {
734 	dirinfostate *dis;
735 	uint8_t i;
736 	char c;
737 	int j;
738 
739 	fprintf(ofd,"path%cinodes%cfiles%cdirs%cchunks%ckeep_chunks%carch_chunks%clength%csize%creal_size%ckeep_size%carch_size\n",s,s,s,s,s,s,s,s,s,s,s);
740 	for (dis = dishead ; dis!=NULL ; dis=dis->next) {
741 		fputc('\"',ofd);
742 		for (j=0 ; dis->path[j]!=0 ; j++) {
743 			c = dis->path[j];
744 			switch (c) {
745 				case '\b':
746 					fprintf(ofd,"\\b");
747 					break;
748 				case '\n':
749 					fprintf(ofd,"\\n");
750 					break;
751 				case '\r':
752 					fprintf(ofd,"\\r");
753 					break;
754 				case '\f':
755 					fprintf(ofd,"\\f");
756 					break;
757 				case '\t':
758 					fprintf(ofd,"\\t");
759 					break;
760 				case '"':
761 					fprintf(ofd,"\\\"");
762 					break;
763 				case '\\':
764 					fprintf(ofd,"\\\\");
765 					break;
766 				default:
767 					fputc(c,ofd);
768 			}
769 		}
770 		fputc('\"',ofd);
771 		if (dis->status!=STATUS_ENOENT) {
772 			fprintf(ofd,"%c%"PRIu64,s,liset_card(dis->inode_liset));
773 			fprintf(ofd,"%c%"PRIu32,s,dis->s.files);
774 			fprintf(ofd,"%c%"PRIu32,s,dis->s.dirs);
775 			fprintf(ofd,"%c%"PRIu64,s,liset_card(dis->chunk_liset));
776 			fprintf(ofd,"%c%"PRIu64,s,dis->s.kchunks);
777 			fprintf(ofd,"%c%"PRIu64,s,dis->s.achunks);
778 			fprintf(ofd,"%c%"PRIu64,s,dis->s.length);
779 			fprintf(ofd,"%c%"PRIu64,s,dis->s.size);
780 			fprintf(ofd,"%c%"PRIu64,s,dis->s.rsize);
781 			fprintf(ofd,"%c%"PRIu64,s,dis->s.keeprsize);
782 			fprintf(ofd,"%c%"PRIu64,s,dis->s.archrsize);
783 		} else {
784 			for (i=0 ; i<11 ; i++) {
785 				fprintf(ofd,"%cerror",s);
786 			}
787 		}
788 		fprintf(ofd,"\n");
789 	}
790 }
791 
usage(const char * appname)792 void usage(const char *appname) {
793 	printf("usage: %s [-f J|C[separator]] [-o outputfile] [-a sum_name] metadata.mfs PATH ...\n",appname);
794 	exit(1);
795 }
796 
main(int argc,char * argv[])797 int main(int argc,char *argv[]) {
798 	uint8_t hdr[8];
799 	uint8_t fver;
800 	int indx,ret;
801 	dirinfostate *dis,**disp;
802 	FILE *fd;
803 	FILE *ofd;
804 	char *outfname;
805 	char *allname;
806 	const char *appname;
807 	char sep;
808 	int ch;
809 	uint8_t format;
810 
811 	appname = argv[0];
812 	outfname = NULL;
813 	allname = NULL;
814 	sep = ',';
815 	format = 0;
816 	while ((ch=getopt(argc,argv,"f:o:a:"))>=0) {
817 		switch(ch) {
818 			case 'f':
819 				if (optarg[0]=='j' || optarg[0]=='J') {
820 					format = 2;
821 				} else if (optarg[0]=='c' || optarg[0]=='C') {
822 					format = 1;
823 					if (optarg[1]!=0) {
824 						sep = optarg[1];
825 					}
826 				} else {
827 					fprintf(stderr,"unrecognized format - use 'j' for JSON and 'c' for CSV\n");
828 					usage(appname);
829 					return 1;
830 				}
831 				break;
832 			case 'o':
833 				if (outfname!=NULL) {
834 					usage(appname);
835 					return 1;
836 				}
837 				outfname = strdup(optarg);
838 				break;
839 			case 'a':
840 				if (allname!=NULL) {
841 					usage(appname);
842 					return 1;
843 				}
844 				allname = strdup(optarg);
845 				break;
846 			default:
847 				usage(appname);
848 				return 1;
849 		}
850 	}
851 	argc -= optind;
852 	argv += optind;
853 
854 	if (argc<1) {
855 		usage(appname);
856 		return 1;
857 	}
858 
859 	ret = 1;
860 	ofd = NULL;
861 	fd = NULL;
862 
863 	if (outfname!=NULL) {
864 		ofd = fopen(outfname,"w");
865 		if (ofd==NULL) {
866 			fprintf(stderr,"error opening output file '%s'\n",outfname);
867 			goto error;
868 		}
869 	} else {
870 		ofd = stdout;
871 	}
872 
873 	fd = fopen(argv[0],"rb");
874 	if (fd==NULL) {
875 		fprintf(stderr,"error opening metadata file '%s'\n",argv[0]);
876 		goto error;
877 	}
878 	if (fread(hdr,1,8,fd)!=8) {
879 		fprintf(stderr,"%s: can't read metadata header\n",argv[0]);
880 		goto error;
881 	}
882 	if (memcmp(hdr,"MFSM NEW",8)==0) {
883 		fprintf(stderr,"%s: empty file\n",argv[0]);
884 		goto error;
885 	}
886 	if (memcmp(hdr,MFSSIGNATURE "M ",5)==0 && hdr[5]>='1' && hdr[5]<='9' && hdr[6]=='.' && hdr[7]>='0' && hdr[7]<='9') {
887 		fver = ((hdr[5]-'0')<<4)+(hdr[7]-'0');
888 	} else {
889 		fprintf(stderr,"%s: unrecognized file format\n",argv[0]);
890 		goto error;
891 	}
892 	if (fver<0x20) {
893 		fprintf(stderr,"%s: metadata file format too old %u.%u\n",argv[0],(unsigned)(fver>>4),(unsigned)(fver&0xF));
894 		goto error;
895 	}
896 	dishead = NULL;
897 	disp = &dishead;
898 	if (allname!=NULL) {
899 		dis = malloc(sizeof(dirinfostate));
900 		memset(dis,0,sizeof(dirinfostate));
901 		dis->inode_liset = liset_new();
902 		dis->chunk_liset = liset_new();
903 		dis->path = strdup(allname);
904 		dis->next = NULL;
905 		dis->status = STATUS_ANY;
906 		*disp = dis;
907 		disp = &(dis->next);
908 	}
909 	for (indx=1 ; indx<argc ; indx++) {
910 		dis = malloc(sizeof(dirinfostate));
911 		memset(dis,0,sizeof(dirinfostate));
912 		dis->inode_liset = liset_new();
913 		dis->chunk_liset = liset_new();
914 		dis->path = strdup(argv[indx]);
915 		dis->next = NULL;
916 		*disp = dis;
917 		disp = &(dis->next);
918 	}
919 	achunk_liset = liset_new();
920 	ret = 0;
921 	if (calc_dirinfos(fd)<0) {
922 		ret = 1;
923 	}
924 	fclose(fd);
925 	fd = NULL;
926 	if (ret==0) {
927 		if (format==2) {
928 			print_result_json(ofd);
929 		} else if (format==1) {
930 			print_result_csv(ofd,sep);
931 		} else {
932 			print_result_plain(ofd);
933 		}
934 	}
935 	liset_remove(achunk_liset);
936 	while (dishead!=NULL) {
937 		dis = dishead;
938 		liset_remove(dis->inode_liset);
939 		liset_remove(dis->chunk_liset);
940 		free(dis->path);
941 		dishead = dis->next;
942 		free(dis);
943 	}
944 error:
945 	if (fd!=NULL) {
946 		fclose(fd);
947 	}
948 	if (outfname!=NULL) {
949 		if (ofd!=NULL) {
950 			fclose(ofd);
951 		}
952 		free(outfname);
953 	}
954 	if (allname!=NULL) {
955 		free(allname);
956 	}
957 	return ret;
958 }
959