/* GNU fdisk - a clone of Linux fdisk. Copyright (C) 2006 Free Software Foundation, Inc. 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; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA*/#include <stdlib.h>#include <stdio.h>#include <stdarg.h>#include <check.h>#include <sys/stat.h>#include <parted/parted.h>/* #include <endian.h> */#include <string.h>#include "../config.h"#include "../src/strlist.h"#include "functions.h"#include "../src/common.h"/* NOTE: In the comments we say that we "query" or "ask" something when we emulate user query function and pass the parameter through there *//* We can't seek more... */#define SIZE 2147483647L/* We will give the partitions below different sizes. I'll use rand */#define PART_LENGTH (2097151LL - ((unsigned int) rand() % 1048575))PedDevice*dev=NULL;PedDisk*disk=NULL;UICallsuiquery;/* We need a seed for the random functions */intseed=1;voidset_seed(intsd){seed=sd;}/* No chance someone else decides to call this file this way, I hope */staticchartempfile[]="/tmp/check_cfdisk_temporary_disk_drive";staticchartempfile2[]="/tmp/check_cfdisk_temporary_disk_drive2";constchar*tempfile_name(){returntempfile;}constchar*tempfile2_name(){returntempfile2;}/* We should try not to ruin a file, or more importantly, some block device */staticintis_blockdev(constchar*file){structstatfile_stat;if(stat(file,&file_stat)<0)return-1;if(S_ISBLK(file_stat.st_mode))return1;elsereturn0;}voidcheck_safety(){inta=is_blockdev(tempfile);intb=is_blockdev(tempfile2);if(a>0||b>0){printf("ERROR: Found a block device with the name of our temp ""file. Aborting.");exit(1);}else{constcharmsg[]="%s exists and will be overwritten. ""Are you sure you want to continue (y/N)?";if(!a){printf(msg,tempfile);if(getchar()!='y'&&getchar()!='Y'){exit(0);}}if(!b){printf(msg,tempfile2);if(getchar()!='y'&&getchar()!='Y'){exit(0);}}}/* Well, there should be no problem, TODO: is it needed? */if(!getuid()||!geteuid()){printf("You should not run these tests as root.\n""Are you sure you want to continue (y/N)? ");if(getchar()!='y'&&getchar()!='Y'){exit(0);}}}/* We need an exception handler */PedExceptionOptionex_opts;PedExceptionTypeex_type;charexception_error[BUFSIZE];// = "%s";staticintexception_count=0;intset_exception_error(constchar*format,...){va_listargs;charbuf[BUFSIZE];va_start(args,format);if(vsnprintf(buf,BUFSIZE,format,args)<0){return-1;}va_end(args);returnsnprintf(exception_error,BUFSIZE,"%s: %s",buf,"%s");}intreset_exception_error(){strncpy(exception_error,"%s",BUFSIZE);}PedExceptionOptionexception_handler(PedException*ex){exception_count++;ex_opts=ex->options;ex_type=ex->type;switch(ex->type){casePED_EXCEPTION_ERROR:casePED_EXCEPTION_FATAL:casePED_EXCEPTION_BUG:casePED_EXCEPTION_NO_FEATURE:fail(exception_error,ex->message);}if(ex_opts&PED_EXCEPTION_FIX)returnPED_EXCEPTION_FIX;elseif(ex_opts&PED_EXCEPTION_RETRY)returnPED_EXCEPTION_RETRY;elseif(ex_opts&PED_EXCEPTION_YES)returnPED_EXCEPTION_YES;elseif(ex_opts&PED_EXCEPTION_OK)returnPED_EXCEPTION_OK;elseif(ex_opts&PED_EXCEPTION_IGNORE)returnPED_EXCEPTION_IGNORE;elsereturnPED_EXCEPTION_UNHANDLED;}/* Look for functions who handled exceptions incorrectly. If this does not go to our exception handler, we have a problem. Will check this at the end of each test. */voidtest_exception(){//printf("\nSo far we got %d exceptions\n", exception_count);exception_count=0;if(ped_exception_throw(PED_EXCEPTION_INFORMATION,PED_EXCEPTION_OK,"Test")!=PED_EXCEPTION_OK)fail("Exception not handled by our handler");if(exception_count!=1)fail("Caught %d exceptions instead of 1",exception_count);}/* This functions will return what's needed *//* NOTE: If the string is DEFAULT_STRING, we return the default string *//* Also note that we have two strings here, if the last character is 1, we should use the second string, if it is 2, we should change it to 1, and if 0, we should use the first. I know it's ugly, but it works. */charstring[2][BUFSIZE+1];intstring_gets;intgetstring(constchar*prompt,char**value,constStrList*str_list,constStrList*loc_str_list,intmulti_word){/* We count how much times this function is called */string_gets++;/* We check whether the first or the second string should be used if there is a "queue" of two strings. */char*s;if(string[0][BUFSIZE]==1){s=string[1];string[0][BUFSIZE]=0;}elses=string[0];if(string[0][BUFSIZE]==2)string[0][BUFSIZE]=1;if(!*value||strcmp(s,DEFAULT_STRING)){if(str_list||loc_str_list){intfound=0;char*temp=NULL;constStrList*c;/* Check if the test string is not found in the string list */for(c=str_list;!found&&c!=NULL;c=c->next){temp=str_list_convert_node(c);if(!strcmp(temp,s))found=1;if(temp)free(temp);}for(c=loc_str_list;!found&&c!=NULL;c=c->next){temp=str_list_convert_node(c);if(!strcmp(temp,s))found=1;if(temp)free(temp);}if(!found)return0;}if(*value)PED_FREE(*value);*value=strdup(s);}return1;}intinteger=0;intgetint(constchar*prompt,int*value){*value=integer;return1;}intbool=1;intgetbool(constchar*prompt,int*value){*value=bool;return1;}PedDevice*device=NULL;intgetdev(constchar*prompt,PedDevice**value){*value=device;return1;}PedPartition*partition;intgetpart(constchar*prompt,PedDisk**disk,PedPartition**value){*value=partition;return1;}PedDiskType*disk_type;intgetdisktype(constchar*prompt,PedDiskType**value){*value=disk_type;return1;}PedFileSystemType*fs_type;intgetfstype(constchar*prompt,PedFileSystemType**value){*value=fs_type;return1;}intpartpos;intpos_gets;charpos_poss[POS_POSS_SIZE];intgetpartpos(constchar*prompt,constvoid*context,constchar*possibilities){/* We count how much times this was called */pos_gets++;/* And save the possibilities */strncpy(pos_poss,possibilities,sizeof(pos_poss));returnpartpos;}intinit_tempfile(){inti;FILE*fp;fp=fopen(tempfile,"w");if(!fp)return0;fseek(fp,SIZE,SEEK_SET);/* NOTE: We want a ~21 GB file, this takes about 22 MB from the disk, this should be 41943039s */for(i=0;i<9;i++){fseek(fp,SIZE,SEEK_CUR);}fwrite("",1,1,fp);fclose(fp);return1;}voidunlink_tempfile(){unlink(tempfile);}intopen_device(){dev=ped_device_get(tempfile);if(!ped_device_open(dev))return0;returndev!=NULL;}#define FAR 128LL/* If these are too far apart, return false */intare_near(longlonga,longlongb){a-=b;if(a<0LL)a=-a;return(a<FAR);}/* I don't believe our tests require this now, but I leave this here */#if 0long long llrand() { /* We assume that long long is always 64 bits and that int is always 32 bits, as per /usr/include/bits/types.h */ long long result; result = (long long) rand(); result *= 4294967296; result += (long long) rand(); return result;}#endifvoidnext_part_geometry(PedGeometry**geom,PedConstraint*constraint,intfactor){PedSectorstart;PedGeometry*temp;if(!*geom)start=0LL;else{start=(*geom)->end+1LL;ped_geometry_destroy(*geom);}temp=ped_geometry_new(dev,start,PART_LENGTH*(longlong)factor);*geom=ped_constraint_solve_nearest(constraint,temp);ped_geometry_destroy(temp);}int_mkpart(inthow,PedSectorstart,PedSectorend,PedPartitionTypetype,constPedFileSystemType*fs_type,PedPartition**newpart,intch_mkpart(PedDisk*,PartPos*pos,PedPartitionType,constPedFileSystemType*,PedPartition**,UIOpts)){intstatus;string_gets=0;intstrings;PartPospos={{0LL,NULL},{0LL,NULL}};switch(how%3){/* We create the partition by specifying start and end */case0:strings=1;if(string[0][BUFSIZE]==1){strings++;string[0][BUFSIZE]=2;}elsestring[0][BUFSIZE]=0;uiquery.getstring=getstring;strncpy(string[0],fs_type->name,BUFSIZE-1);pos.start.sector=start;pos.end.sector=end;status=ch_mkpart(disk,&pos,type,NULL,newpart,UI_DEFAULT);fail_unless(string_gets==strings,"User queried for %d strings instead of %d",string_gets,strings);break;/* We test that giving the default string is OK */case1:strncpy(string[0],DEFAULT_STRING,BUFSIZE);uiquery.getstring=getstring;pos.start.sector=start;pos.end.sector=end;status=ch_mkpart(disk,&pos,type,fs_type,newpart,UI_CUSTOM_VALUES);fail_unless(string_gets==2,"User queried for %d strings instead of 2",string_gets);break;/* We query the start and end */case2:uiquery.getstring=getstring;string[0][BUFSIZE]=2;snprintf(string[0],BUFSIZE,"%llds",start);snprintf(string[1],BUFSIZE,"%llds",end);status=ch_mkpart(disk,&pos,type,fs_type,newpart,UI_CUSTOM_VALUES);fail_unless(string_gets==2,"User queried for %d strings instead of 2",string_gets);break;}returnstatus;}/* We use this for both resize and move, as they take the same params */int_resmov(inthow,PedPartition*part,PedSectorstart,PedSectorend,UIOptsopts,intaction(PedDisk*,PedPartition*,PartPos*pos,UIOpts)){PartPospos={{0LL,NULL},{0LL,NULL}};switch(how%3){/* Specify start, end and partition */case0:pos.start.sector=start;pos.end.sector=end;returnaction(disk,part,&pos,opts);/* We specify start and end, ask for partition */case1:uiquery.getint=NULL;uiquery.getpart=getpart;partition=part;pos.start.sector=start;pos.end.sector=end;returnaction(disk,NULL,&pos,opts);/* We ask for everything, the partition with number */case2:uiquery.getpart=NULL;uiquery.getstring=getstring;uiquery.getint=getint;integer=part->num;string[0][BUFSIZE]=2;snprintf(string[0],BUFSIZE,"%llds",start);snprintf(string[1],BUFSIZE,"%llds",end);opts|=UI_CUSTOM_VALUES;returnaction(disk,NULL,&pos,opts);}}voidget_max_geometry(PedGeometry**geom,PedConstraint*constraint,PedPartition*part){PedGeometry*temp=NULL;if(*geom)ped_geometry_destroy(*geom);PedSectorstart,end;inti=0;/* We check if we have free space before or after the partition */if(part->prev&&part->prev->type&PED_PARTITION_FREESPACE)start=part->prev->geom.start;elsestart=part->geom.start;if(part->next&&part->next->type&PED_PARTITION_FREESPACE)end=part->next->geom.end;elseend=part->geom.end;temp=ped_geometry_new(dev,start,end-start+1LL);*geom=ped_constraint_solve_nearest(constraint,temp);ped_geometry_destroy(temp);}#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))/* NOTE: If you create a rather big test disk, you can make this use llrand from functions.c instead. */voidrandomize_position(PedSector*start,PedSector*end,PedGeometry*where,inti){/* This chooses random partition position within where *//* If i is 0, we place it at the beginning, if i is 3, at the end */if(i==0){*start=where->start;do{*end=*start+((unsignedint)rand()%where->length)-1;*end=MIN(*end,where->end);}while(are_near(*start,*end));}elseif(i==3){*end=where->end;do{*start=*end-((unsignedint)rand()%where->length)+1;*start=MAX(*start,where->start);}while(are_near(*start,*end));}else{PedSectormid=where->start+((unsignedint)rand()%where->length)-1;do{*start=where->start+((unsignedint)rand()%(mid-where->start));*end=mid+((unsignedint)rand()%(where->end-mid));*start=MAX(*start,where->start);*end=MIN(*end,where->end);}while(are_near(*start,*end));}}/* This is for our size changing checks. We check on all label types */voidchange_size(constchar*ltype,UIOptsopts){PedConstraint*constraint=ped_device_get_constraint(dev);PedGeometry*geom=NULL;PedSectorstart,end,oldstart=-1,oldend=-1;PedPartition*part=NULL;PedFileSystemType*fs_type=ped_file_system_type_get("linux-swap");intis_mac=!strcmp(ltype,"mac");inti;fail_unless(perform_mklabel(dev,&disk,ped_disk_type_get(ltype)),"We could create the %s label for resizing partitions",ltype);/* NOTE: This might blow up if ltype is less than three chars *//* FIXME: This checks works differently on LE and BE platforms */srand(2+opts+((int)ltype));/* We will resize create 4 partitions */if(is_mac){part=ped_disk_get_partition(disk,1);geom=ped_geometry_new(dev,part->geom.start,part->geom.length);part=NULL;}for(i=0;i<4;i++){next_part_geometry(&geom,constraint,1);if(ped_disk_type_check_feature(disk->type,PED_DISK_TYPE_PARTITION_NAME)){/* 1 means get second string on queue */string[0][BUFSIZE]=1;strncpy(string[1],"Label",BUFSIZE);}elsestring[0][BUFSIZE]=0;uiquery.getstring=getstring;set_exception_error("Creating partition %d at %llds-%llds ""with previous one at %llds-%llds on %s",i,geom->start,geom->end,oldstart,oldend,ltype);fail_unless(_mkpart(0,geom->start,geom->end,PED_PARTITION_NORMAL,fs_type,&part,(opts&UI_NO_FS_RESIZE?perform_mkpart:perform_mkpartfs)),"Failed to create partition for resize on %s. ""Number %d, size %llds, start %llds, end %llds",ltype,i,geom->length,geom->start,geom->end);reset_exception_error();oldstart=geom->start;oldend=geom->end;}/* And we will resize them, 4 times each, moving them as near possible to end of the disk as a final result */while(part&&(!is_mac||part->num!=1)){get_max_geometry(&geom,constraint,part);for(i=0;i<4;i++){randomize_position(&start,&end,geom,i);set_exception_error("Resize %d of partition %d ""from %llds-%llds to %llds-%llds ""in region %llds-%llds on %s",i,part->num,part->geom.start,part->geom.end,start,end,geom->start,geom->end,ltype);/* Note that exact values fail on some partition types, and when we are querying the user, exact values should be used... Or? FIXME? *///fail_unless(_resmov (strncmp("ms",ltype,2) ? 0 : i, fail_unless(_resmov(i,part,start,end,opts,perform_resize),"Could not do %d resize of partition %d ""from %llds-%llds to %llds-%llds ""in region %llds-%llds on %s",i,part->num,part->geom.start,part->geom.end,start,end,geom->start,geom->end,ltype);fail_unless(are_near(part->geom.start,start)&&are_near(part->geom.end,end),"The %d resize of partition %d on %s wrong.""we wanted %llds-%llds, we got %llds-%llds",i,part->num,ltype,start,end,part->geom.start,part->geom.end);reset_exception_error();}do{part=part->prev;}while(part&&((part->type&PED_PARTITION_METADATA)||(part->type&PED_PARTITION_FREESPACE)));}}