Merge branch 'devel'
Small C application added to begine reducing dependency on virsh, starting by listing machines, their names and their states by directly accessing the libvirt API
This commit is contained in:
		
						commit
						ff65da8adb
					
				|  | @ -0,0 +1,290 @@ | |||
| #include <libvirt.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| void pr_bad(); | ||||
| void pr_conn_bad(); | ||||
| void pr_conn_good(); | ||||
| void pr_fatal_and_exit(int e); | ||||
| void pr_good(); | ||||
| void pr_no_match(); | ||||
| 
 | ||||
| int get_domain(char *uuid, virDomainPtr *domain); | ||||
| char * get_state_string(virDomainPtr dom, int state); | ||||
| 
 | ||||
| void lv_get_name(char *uuid); | ||||
| void lv_get_state(char *uuid); | ||||
| void lv_get_xml(char *uuid); | ||||
| void lv_list_all_name(); | ||||
| void lv_list_all_uuid(); | ||||
| 
 | ||||
| const int E_LIBVIRT=16; | ||||
| virConnectPtr conn; | ||||
| 
 | ||||
| /*One of these is displayed on startup. If CONN_BAD, program exits*/ | ||||
| const char CONN_BAD[]= "# No Connection"; | ||||
| const char CONN_GOOD[]="# Connected"; | ||||
| 
 | ||||
| /*After a command is issued, one of these displays. Only GOOD is followed
 | ||||
|   by the requested information, then EOF */ | ||||
| const char BAD_REQ[]="# Bad Request"; | ||||
| const char FATAL[]=  "# Fatal error"; | ||||
| const char GOOD[]=   "# Making API Request"; | ||||
| const char NOMATCH[]="# No matching domain"; | ||||
| 
 | ||||
| /************************************************************************/ | ||||
| 
 | ||||
| /*  Possible commands:
 | ||||
|  *    list: list UUIDs of all domains that are defined and/or running | ||||
|  *    list_name: list names of all domains that are defined and/or running | ||||
|  *    get_name <uuid>: get name of domain | ||||
|  *    get_state <uuid>: get state of domain as defined in man qq2clone. It | ||||
|  *      is possible, though unlikely for this to return an empty string | ||||
|  *      even when referring to a valid uuid | ||||
|  *    get_xml <uuid>: print xml file describing domain (prints current | ||||
|  *      config of a running machine, rather than inactive config) | ||||
|  */ | ||||
| 
 | ||||
| int main () { | ||||
|   setlinebuf(stdout); /* Make sure output is always line buffered */ | ||||
|   /* Connect to libvirt API */ | ||||
|   conn=virConnectOpen(NULL); | ||||
|   int loop=1; | ||||
|   if ( conn == NULL) { | ||||
|     pr_conn_bad(); | ||||
|     exit(E_LIBVIRT); | ||||
|   } else { | ||||
|     pr_conn_good(); | ||||
|   } | ||||
| 
 | ||||
|   char *lineptr, *token, **str_arr; | ||||
|   const char delim[]="\t\n "; | ||||
|   unsigned int count; | ||||
|   int i; | ||||
|   size_t n; | ||||
| 
 | ||||
|   /* Get input from stdin and execute commands in a loop */ | ||||
|   while(1) { | ||||
|     str_arr=NULL; | ||||
|     lineptr=NULL; n=0; count=0; | ||||
| 
 | ||||
|     getline(&lineptr,&n,stdin); | ||||
|     token=strtok(lineptr,delim); | ||||
|     if (token == NULL) { free(lineptr); pr_bad(); continue; } | ||||
| 
 | ||||
|     while (token != NULL) { | ||||
|       str_arr=realloc(str_arr, sizeof(char*) * (count + 1) ); | ||||
|       if (str_arr == NULL){ pr_fatal_and_exit(1); } | ||||
|       str_arr[count]=token; | ||||
|       count++; | ||||
|       token=strtok(NULL,delim); | ||||
|     } | ||||
| 
 | ||||
|     if ( count > 2 || count == 0) { free(lineptr); pr_bad(); continue; } | ||||
|     if ( strcmp( str_arr[0], "exit" ) == 0 ) { | ||||
|       virConnectClose(conn); exit(0); | ||||
| 
 | ||||
|     } else if (strcmp( str_arr[0], "list" ) == 0 ) { | ||||
|       if (count==1){ lv_list_all_uuid(); } else { pr_bad(); } | ||||
| 
 | ||||
|     } else if (strcmp( str_arr[0], "list_name" ) == 0 ) { | ||||
|       if (count==1){ lv_list_all_name(); } else { pr_bad(); } | ||||
| 
 | ||||
|     } else if (strcmp( str_arr[0], "get_name" ) == 0 ) { | ||||
|       if (count==2){ lv_get_name(str_arr[1]); } else { pr_bad(); } | ||||
| 
 | ||||
|     } else if (strcmp( str_arr[0], "get_state" ) == 0 ) { | ||||
|       if (count==2){ lv_get_state(str_arr[1]); } else { pr_bad(); } | ||||
| 
 | ||||
|     } else if (strcmp( str_arr[0], "get_xml" ) == 0 ) { | ||||
|       if (count==2){ lv_get_xml(str_arr[1]); } else { pr_bad(); } | ||||
| 
 | ||||
|     } else { | ||||
|       pr_bad(); | ||||
|     } | ||||
| 
 | ||||
|     free(lineptr); | ||||
|     if (str_arr != NULL){ free(str_arr); } | ||||
|   } | ||||
| 
 | ||||
|   exit(0); | ||||
| } | ||||
| 
 | ||||
| /************************************************************************/ | ||||
| 
 | ||||
|                   /***********************************
 | ||||
|                   * Basic, repeated output sequences * | ||||
|                   ***********************************/ | ||||
| 
 | ||||
| void pr_bad() { | ||||
|   printf("%s\n",BAD_REQ); | ||||
| } | ||||
| 
 | ||||
| void pr_conn_bad() { | ||||
|   printf("%s\n", CONN_BAD); | ||||
| } | ||||
| 
 | ||||
| void pr_conn_good() { | ||||
|   printf("%s\n",CONN_GOOD); | ||||
| } | ||||
| 
 | ||||
| /* Okay, this one provides a basic output sequence *and* quits */ | ||||
| void pr_fatal_and_exit(int e) { | ||||
|   printf("%s\n",FATAL); | ||||
|   virConnectClose; | ||||
|   exit(e); | ||||
| } | ||||
| 
 | ||||
| void pr_good() { | ||||
|   printf("%s\n",GOOD); | ||||
| } | ||||
| 
 | ||||
| void pr_no_match() { | ||||
|   printf("%s\n",NOMATCH); | ||||
| } | ||||
| 
 | ||||
|                 /*****************************************
 | ||||
|                 * Helper functions for working with API * | ||||
|                 *****************************************/ | ||||
| 
 | ||||
| int get_domain(char *uuid, virDomainPtr *domain){ | ||||
|   *domain=virDomainLookupByUUIDString(conn, (const char *) uuid); | ||||
|   if (domain == NULL){ | ||||
|     return 1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| char * get_state_string(virDomainPtr dom, int state) | ||||
| { | ||||
|   if (state == VIR_DOMAIN_RUNNING) { | ||||
|     return ("running"); | ||||
|   } else if (state == VIR_DOMAIN_BLOCKED) { | ||||
|     return ("idle"); | ||||
|   } else if ( state == VIR_DOMAIN_PAUSED ) { | ||||
|     return ("paused"); | ||||
|   } else if ( state == VIR_DOMAIN_SHUTDOWN ) { | ||||
|     return("shutting-down"); | ||||
|   } else if ( state == VIR_DOMAIN_SHUTOFF ) { | ||||
|       int saved; | ||||
|       saved=virDomainHasManagedSaveImage(dom,0); | ||||
|       if( saved == 1 ) { | ||||
|         return("saved"); | ||||
|       } else if ( saved == 0 ) { | ||||
|         return("off"); | ||||
|       } else { | ||||
|         return(NULL); | ||||
|       } | ||||
|   } else if ( state == VIR_DOMAIN_CRASHED ) { | ||||
|     return("crashed"); | ||||
|   } else if ( state == VIR_DOMAIN_PMSUSPENDED ) { | ||||
|     return ("pmsuspended"); | ||||
|   } else { | ||||
|       return(""); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|         /*******************************************************
 | ||||
|         * Get information from libvirt API and print to stdout * | ||||
|         *******************************************************/ | ||||
| 
 | ||||
| void lv_get_name (char *uuid){ | ||||
|   virDomainPtr domain; | ||||
|   if (get_domain(uuid, &domain) == 0){ | ||||
|     pr_good(); | ||||
|     const char *name; | ||||
|     name=virDomainGetName(domain); | ||||
|     if (name!=NULL){ printf("+%s\n",name); } | ||||
|       else { pr_fatal_and_exit(E_LIBVIRT); } | ||||
|     virDomainFree(domain); | ||||
|     printf("EOF\n"); | ||||
|   }else{ | ||||
|     pr_no_match(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lv_get_state(char *uuid){ | ||||
|   virDomainPtr domain; | ||||
|   if (get_domain(uuid, &domain) == 0){ | ||||
|     pr_good(); | ||||
|     int *state=malloc(sizeof(state)), *reason=malloc(sizeof(reason)), ret; | ||||
|     ret=virDomainGetState(domain,state,reason,0); | ||||
|     if ( ret==0 ){  | ||||
|       const char *state_str; | ||||
|       state_str=get_state_string(domain, *state); | ||||
|       printf("+%s\n",state_str); | ||||
|     } else { printf("+\n"); } | ||||
|     virDomainFree(domain); | ||||
|     free(state); | ||||
|     free(reason); | ||||
|     printf("EOF\n"); | ||||
|   }else{ | ||||
|     pr_no_match(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lv_get_xml(char *uuid){ | ||||
|   virDomainPtr domain; | ||||
|   if (get_domain(uuid, &domain) == 0){ | ||||
|     pr_good(); | ||||
|     char *xml; | ||||
|     xml=virDomainGetXMLDesc(domain, 0); | ||||
|     if ( xml!=NULL ){ | ||||
|       char *token; | ||||
|       token=strtok(xml, "\n"); | ||||
|       if(token==NULL) { free(xml); virDomainFree(domain); return; } | ||||
| 
 | ||||
|       while (token != NULL) { | ||||
|         printf ("+%s\n",token); | ||||
|         token=strtok(NULL, "\n"); | ||||
|       } | ||||
|       free(xml); | ||||
|     } else { pr_fatal_and_exit(E_LIBVIRT); } | ||||
|     virDomainFree(domain); | ||||
|     printf("EOF\n"); | ||||
|   } else { | ||||
|     pr_no_match(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lv_list_all_uuid() { | ||||
|   virDomainPtr *domains; | ||||
|   int ret_list, ret_uuid, i; | ||||
|   ret_list=virConnectListAllDomains(conn,&domains,0); | ||||
|   if(ret_list<0){ virConnectClose(conn); | ||||
|       pr_fatal_and_exit(E_LIBVIRT); }; | ||||
|   pr_good(); | ||||
| 
 | ||||
|   char *uuid=malloc(VIR_UUID_STRING_BUFLEN); | ||||
|   for(i=0;i<ret_list;i++){ | ||||
|     ret_uuid=virDomainGetUUIDString(domains[i],uuid); | ||||
|     if (ret_uuid==0) { | ||||
|       printf("+%s\n",uuid); | ||||
|     } | ||||
|     virDomainFree(domains[i]); | ||||
|   } | ||||
| 
 | ||||
|   free(uuid); | ||||
|   free(domains); | ||||
|   printf("EOF\n"); | ||||
| } | ||||
| 
 | ||||
| void lv_list_all_name() { | ||||
|   virDomainPtr *domains; | ||||
|   int ret_list, i; | ||||
|   ret_list=virConnectListAllDomains(conn,&domains,0); | ||||
|   if(ret_list<0){ virConnectClose(conn); pr_fatal_and_exit(E_LIBVIRT); }; | ||||
|   pr_good(); | ||||
| 
 | ||||
|   const char *name; | ||||
|   for(i=0;i<ret_list;i++){ | ||||
|     name=virDomainGetName(domains[i]); | ||||
|     if (name!=NULL){ printf("+%s\n",name); } | ||||
|       else { pr_fatal_and_exit(E_LIBVIRT); } | ||||
|     virDomainFree(domains[i]); | ||||
|   } | ||||
| 
 | ||||
|   free(domains); | ||||
|   printf("EOF\n"); | ||||
| } | ||||
							
								
								
									
										14
									
								
								man.pandoc
								
								
								
								
							
							
						
						
									
										14
									
								
								man.pandoc
								
								
								
								
							|  | @ -414,15 +414,11 @@ libvirt. It is unknown how widespread this issue is, but it is the reason | |||
| that the default directory storage-qq2clone does not start with '.' | ||||
| 
 | ||||
| If the UUID of a clone is changed, qq2clone will no longer be able to | ||||
| track it and will not be able to perform commands on it anymore.  | ||||
| If virsh undefine is run on a clone, qq2clone will not be able to see  | ||||
| it once it is turned off. This limitation will be eliminated or reduced in  | ||||
| the future, when qq2clone moves away from relying on virsh and implements  | ||||
| direct usage of the libvirt API. It could be addressed now by using  | ||||
| transient domains, but that would require qq2clone to do more things  | ||||
| manually instead of just invoking virsh. Since the plan is to  | ||||
| transition to a different approach later, that would be wasted effort. For  | ||||
| now, if you find yourself in this position just use **qq2clone** check. | ||||
| track it and will not be able to perform commands on it anymore. This will | ||||
| be addressed in the future using custom metadata in the libvirt domain | ||||
| XML. If the user undefines a domain, this will obviously cause it to | ||||
| disappear from qq2clone's perspective when it is turned off, creating a | ||||
| discrepancy in its database. This can be fixed with **qq2clone** **check**. | ||||
| 
 | ||||
| qq2clone can only produce clones by making qcow2 image files. The backing  | ||||
| file need not be qcow2, but the images produced by qq2clone always will  | ||||
|  |  | |||
							
								
								
									
										392
									
								
								qq2clone
								
								
								
								
							
							
						
						
									
										392
									
								
								qq2clone
								
								
								
								
							|  | @ -1,11 +1,11 @@ | |||
| #!/bin/bash | ||||
| #shellcheck disable=1090 disable=2012 | ||||
| 
 | ||||
|                             #-----------------# | ||||
|                             #@@@@@@@@@@@@@@@@@# | ||||
|                             #---ERROR CODES---# | ||||
|                             #@@@@@@@@@@@@@@@@@# | ||||
|                             #-----------------# | ||||
|                         #--------------------# | ||||
|                         #@@@@@@@@@@@@@@@@@@@@# | ||||
|                         #---LITERAL VALUES---# | ||||
|                         #@@@@@@@@@@@@@@@@@@@@# | ||||
|                         #--------------------# | ||||
| 
 | ||||
| E_permission=10 # No permission for access or file does not exist | ||||
| E_depends=11    # Lacking required software | ||||
|  | @ -20,6 +20,16 @@ E_timeout=17    # Timeout was exceeded before spice connection to clone | |||
| E_file=18       # Expected file does not exist or is of wrong type/format | ||||
| E_unexpected=19 # Probably a bug in qq2clone | ||||
| 
 | ||||
| # lv_api_do prints one of these when started | ||||
| CONN_BAD="# No Connection" | ||||
| CONN_GOOD="# Connected" | ||||
| 
 | ||||
| # lv_api_do prints one of these immediately following a line of input | ||||
| BAD_REQ="# Bad Request" | ||||
| FATAL="# Fatal error" | ||||
| GOOD="# Making API Request" | ||||
| NOMATCH="# No matching domain" | ||||
| 
 | ||||
|            #---------------------------------------------------# | ||||
|            #@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# | ||||
|            #---NAMED PIPE FOR PASSING DATA BETWEEN FUNCTIONS---# | ||||
|  | @ -55,8 +65,7 @@ TEMPDIR=$(mktemp -d) || temp_error | |||
| #shellcheck disable=2064 | ||||
| trap "exec 3>&-; exec 3<&-;rm -rf $TEMPDIR" EXIT | ||||
| fifo_path="${TEMPDIR}/qq2clone_fifo" | ||||
| mkfifo "$fifo_path" || | ||||
|   { echo "Cannot make fifo" >&2; exit "$E_extcom" ;} | ||||
| mkfifo "$fifo_path" || fifo_error | ||||
| exec 3<>"$fifo_path" | ||||
| return 0 | ||||
| } | ||||
|  | @ -75,7 +84,7 @@ read_pipe () | |||
| echo "EOF" >&3 | ||||
| local line match | ||||
| while IFS= read -r line <&3; do | ||||
|   # write_pipe puts a + at the start of every line for read_pipe | ||||
|   # write_pipe puts a + at the start of every line | ||||
|   if [[ "$line" =~ ^\+(.*)$ ]]; then | ||||
|     match="${BASH_REMATCH[1]}" | ||||
|     echo "$match" | ||||
|  | @ -114,6 +123,78 @@ fi | |||
| return 0 | ||||
| } | ||||
| 
 | ||||
|                           #-------------------# | ||||
|                           #@@@@@@@@@@@@@@@@@@@# | ||||
|                           #---USE LV_API_DO---# | ||||
|                           #@@@@@@@@@@@@@@@@@@@# | ||||
|                           #-------------------# | ||||
| 
 | ||||
| #=========================================================================# | ||||
| lv_api_do_open () | ||||
| # DESCRIPTION: Open lv_api_do in background | ||||
| # INPUT: None | ||||
| # OUTPUT: Return 0 on success, exit on failure | ||||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
| declare -g lv_api_temp; | ||||
| lv_api_temp="$(mktemp -d )" || temp_error | ||||
| mkfifo "${lv_api_temp}/lv_api_do_fifo" || fifo_error | ||||
| exec 4<>"${lv_api_temp}/lv_api_do_fifo" | ||||
| ${QQ2_DIR}/lv_api_do <&4 >&3 2>/dev/null & | ||||
| 
 | ||||
| local check | ||||
| read -r check <&3 | ||||
| [[ "$check" == "$CONN_BAD" ]] && lv_api_do_bad_conn | ||||
| [[ "$check" == "$CONN_GOOD" ]] || unexpected_error lv_api_do_open | ||||
| 
 | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| lv_api_do_comm () | ||||
| # DESCRIPTION: Issue a command to lv_api_do | ||||
| # INPUT: The command | ||||
| # OUTPUT: Return 0 on success, lv_api_do output can be accessed with  | ||||
| #   read_pipe. Return 1 on failure. Exit and error message if lv_api_do | ||||
| #   encounters a fatal error | ||||
| # PARAMETERS: $@: command string to lv_api_do | ||||
| #=========================================================================# | ||||
| { | ||||
| echo "$*" >&4 | ||||
| local check | ||||
| read -r check <&3 | ||||
| [[ "$check" == "$BAD_REQ" ]] && unexpected_error lv_api_do_comm | ||||
| [[ "$check" == "$NOMATCH" ]] && return 1 | ||||
| [[ "$check" == "$FATAL" ]] && | ||||
|   { echo "Error using libvirt API" >&2; exit "$E_libvirt"; } | ||||
| [[ "$check" == "$GOOD" ]] || unexpected_error lv_api_do_comm | ||||
| 
 | ||||
| # This loop avoids a race condition when trying to read_pipe later by | ||||
| # ensuring that lv_api_do has finished its output before this function | ||||
| # returns | ||||
| local line | ||||
| while read -r line <&3; do | ||||
|   [[ "$line" == "EOF" ]] && break | ||||
|   write_pipe 0 "${line:1}" | ||||
| done | ||||
| 
 | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| lv_api_do_close () | ||||
| # DESCRIPTION: Tell lv_api_do to exit and close the extra pipe | ||||
| # INPUT: None | ||||
| # OUTPUT: None | ||||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
|   echo "exit" >&4 | ||||
|   exec 4>&- | ||||
|   exec 4<&- | ||||
|   rm -rf "${lv_api_temp:?}" | ||||
|   return 0 | ||||
| } | ||||
| 
 | ||||
|               #-------------------------------------------# | ||||
|               #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# | ||||
|               #---GET/ALTER CONFIGURATION, CHECK SYSTEM---# | ||||
|  | @ -188,7 +269,7 @@ done | |||
|   { echo "This script won't run until you install the listed software" >&2; | ||||
|   exit "$E_depends"; }  | ||||
| 
 | ||||
|   return 0 | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| disp_conf_names () | ||||
|  | @ -343,7 +424,7 @@ read -r check \ | |||
|   < <(sqlite3 "select exists ( select * from CONFIG)") | ||||
| ((check)) || | ||||
|   { echo "Is the database corrupt? No CONFIG table!" 2>/dev/null;  | ||||
|     unexpected_error get_config; } | ||||
|     exit "$E_config"; } | ||||
| 
 | ||||
| declare -gA OPT | ||||
| declare -a opts | ||||
|  | @ -377,6 +458,7 @@ return 0 | |||
|                         #---USAGE INFORMATION---# | ||||
|                         #@@@@@@@@@@@@@@@@@@@@@@@# | ||||
|                         #-----------------------# | ||||
| 
 | ||||
| #=========================================================================# | ||||
| usage () | ||||
| # DESCRIPTION: Output basic usage information | ||||
|  | @ -497,7 +579,6 @@ xml="$(virt-xml "$@" <<<"$(read_pipe)" 2>/dev/null)" || | |||
| write_pipe 1 <<<"$xml" | ||||
| return 0 | ||||
| } | ||||
|                           | ||||
| #=========================================================================# | ||||
| find_tag () | ||||
| # DESCRIPTION: Use xmllint to do an xpath search of xml and write_pipe | ||||
|  | @ -1147,6 +1228,28 @@ check="$(sqlite3 "select exists ( select * from TEMPLATES where\ | |||
| return 1 | ||||
| } | ||||
| #=========================================================================# | ||||
| fifo_error () | ||||
| # DESCRIPTION: Error to display if fifo creation files | ||||
| # INPUT: None | ||||
| # OUTPUT: Error message and exit code | ||||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
|   echo "Cannot make fifo" | ||||
|   exit "$E_extcom" | ||||
| } >&2 | ||||
| #=========================================================================# | ||||
| lv_api_do_bad_conn () | ||||
| # DESCRIPTION: Error displayed when lv_api_do cannot connect to API | ||||
| # INPUT: None | ||||
| # OUTPUT: Error message and exit code | ||||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
|   echo "Cannot connect to libvirt API" | ||||
|   exit "$E_libvirt" | ||||
| } 2>/dev/null | ||||
| #=========================================================================# | ||||
| set_error () | ||||
| # DESCRIPTION: Used when convert_to_seq fails | ||||
| # INPUT: None | ||||
|  | @ -1399,183 +1502,6 @@ for id in "${!BAD_CL[@]}"; do | |||
|   echo | ||||
| done | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| prompt_delete_orphans () | ||||
| # DESCRIPTION: Find any image files in qq2clone's default storage directory | ||||
| #   that don't belong to qqclone2 or any machine on the current libvirt | ||||
| #   connetion | ||||
| # INPUT: None | ||||
| # OUTPUT: Prompts user and gives status updates | ||||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
| # This function is very long, partially due to lots of echo statements | ||||
| # and requests for user input. However, all of the text describing things | ||||
| # to the user also makes it easy to understand the code, so for now  | ||||
| # I am leaving it as-is. | ||||
| hr | ||||
| echo | ||||
| echo "qq2clone will look in its default storage pool:"  | ||||
| echo | ||||
| echo "  ${OPT[STORAGE]}" | ||||
| echo | ||||
| echo "Any files found there that are not storage devices in use by" | ||||
| echo "a virtual machine on the current libvirt connection or qq2clone" | ||||
| echo "templates will be considered orphans. This shouldn't take too long," | ||||
| echo "but it could if you have a lot of files in that location, a large" | ||||
| echo "number of domains defined, or a slow machine" | ||||
| echo | ||||
| echo "qq2clone will take no action on these files unless directed to" | ||||
| echo | ||||
| echo "Ctrl+C to abort search" | ||||
| echo | ||||
| hr | ||||
| echo | ||||
| local patt | ||||
| patt="^[[:space:]]*[^[:space:]]+[[:space:]]+([^[:space:]].*[^[:space:]])" | ||||
| patt="${patt}[[:space:]]*$" | ||||
| 
 | ||||
| local check | ||||
| check="$(virsh list --all)" | ||||
| check="$(strip_ws "$check")" | ||||
| if [[ -n "$check" ]]; then check=1; else check=0; fi | ||||
| 
 | ||||
| local uuid device path c=8 n match | ||||
| 
 | ||||
| echo "Generating list of domain storage devices..." | ||||
| while (( check )) && read -r device; do | ||||
|   while (( c )); do (( c-- )); continue 2; done; | ||||
| 
 | ||||
|   if [[ -z "$device" ]]; then | ||||
|     for ((c=3;c>0;)); do | ||||
|       read -r n; [[ -z "$n" ]] && continue | ||||
|       (( c-- )) | ||||
|     done | ||||
|     continue | ||||
|   fi | ||||
|   [[ "$device" =~ $patt ]] && write_pipe 0 "${BASH_REMATCH[1]}" | ||||
| 
 | ||||
| done < <( while read -r uuid; do \ | ||||
|   echo "domblklist $uuid; echo --err null;\ | ||||
|   domblklist $uuid --inactive;"; done \ | ||||
|     < <( virsh list --all --uuid ) | virsh 2>&1 ) | ||||
| 
 | ||||
| # We have all the disk files associated with domains, but we still need | ||||
| # the ones referenced by template XML | ||||
| declare -a templates | ||||
| while read -r line; do | ||||
|   write_pipe 0 "$line" | ||||
| done < <(sqlite3 "select disks from TEMPLATES;") | ||||
| 
 | ||||
| echo "Generating list of filepaths in default storage pool..." | ||||
| declare -a f_paths | ||||
| local path | ||||
| while read -r path; do | ||||
|   f_paths[${#f_paths[@]}]="$path" | ||||
| done < <(find "${OPT[STORAGE]}" -depth -mindepth 1 2>/dev/null) | ||||
| 
 | ||||
| echo "Comparing..." | ||||
| declare -a orphans | ||||
| match=0 | ||||
| for path in "${f_paths[@]}"; do | ||||
|   while read -r device; do | ||||
|     (( match )) && continue | ||||
|     if [[ "$device" == "$path" ]]; then | ||||
|       match=1 | ||||
|     fi | ||||
|   done < <( read_pipe 1 ) | ||||
|   [[ "$match" == "1" ]] && { match=0; continue; } | ||||
|   orphans[${#orphans[@]}]="$path" | ||||
| done  | ||||
| read_pipe >/dev/null | ||||
| 
 | ||||
| local j=${#orphans[@]} | ||||
| if ((j)); then | ||||
|   echo | ||||
|   echo "Total potentially orphaned files: $j" | ||||
|   echo | ||||
|   echo "Remember that false positives are very possible. qq2clone" | ||||
|   echo "considers any file in its default storage pool that is not" | ||||
|   echo "a storage device for a virtual machine listed by virsh or" | ||||
|   echo "a template known to qq2clone to be an orphan. It is unwise to" | ||||
|   echo "delete any detected files without looking at them first" | ||||
|   echo | ||||
|   prompt_yes_no && less \ | ||||
|     < <( for path in "${orphans[@]}"; do echo "$path"; done  ) | ||||
|   echo | ||||
|   echo "Would you like to store a copy of this list to disk?" | ||||
|   local temp | ||||
|   if prompt_yes_no; then | ||||
|     temp="$(mktemp)" | ||||
|     for path in "${orphans[@]}"; do echo "$path"; done > "$temp" | ||||
|     echo "File printed to $temp" | ||||
|   fi | ||||
|   echo | ||||
|   echo "1) Delete all files found" | ||||
|   echo "2) Answer a prompt to delete or leave alone each file" | ||||
|   echo "3) Abort and handle the situation manually" | ||||
|   local select prompt=1 file fail=0 | ||||
|   select="$(prompt_num 1 3)" | ||||
|   ((select==1)) && prompt=0 | ||||
|   ((select==3)) && { echo "Abort"; return 0; } | ||||
| 
 | ||||
|  local i | ||||
|   for ((i=0;i<j;i++));do | ||||
|     hr | ||||
|     file="${orphans["$i"]}" | ||||
|     echo "$file" | ||||
|     echo "  $((i+1))/$j" | ||||
|     echo "  Type: $(file "$file")" | ||||
|     if ((prompt)); then | ||||
|       echo "  1) Delete it" | ||||
|       echo "  2) Leave it alone" | ||||
|       echo "  3) Delete all" | ||||
|       echo "  4) Abort" | ||||
|       select="$(prompt_num 1 4)" | ||||
|       ((select==2)) && continue | ||||
|       ((select==3)) && prompt=0 | ||||
|       ((select==4)) && { echo "Abort"; return 0; } | ||||
|     fi | ||||
|     chmod +rw "$file" &>/dev/null | ||||
|     rmdir "$file" &>/dev/null | ||||
|     rm -f "$file" &>/dev/null | ||||
|     if [[ -e "$file" ]]; then | ||||
|       echo "Unable to delete" | ||||
|       write_pipe "$file" | ||||
|       fail=1 | ||||
|     else | ||||
|       echo "Deleted" | ||||
|     fi | ||||
|     echo | ||||
|   done | ||||
|   if ((fail)); then | ||||
|     echo "Check complete, but failed to delete some files." | ||||
|     echo | ||||
|     echo "View a list of fails qq2clone failed to delete?" | ||||
|     if prompt_yes_no; then | ||||
|       echo | ||||
|       read_pipe 1 | less | ||||
|     else | ||||
|       echo | ||||
|     fi | ||||
|     echo "Save to disk?" | ||||
|     if prompt_yes_no; then | ||||
|       echo | ||||
|       temp="$(mktemp)" | ||||
|       read_pipe > "$temp" | ||||
|       echo "File printed to $temp" | ||||
|     else | ||||
|       echo | ||||
|     fi | ||||
|   else | ||||
|     echo "Orphaned file check completed" | ||||
|   fi | ||||
| else | ||||
|   echo | ||||
|   echo "No orphaned files were found" | ||||
| fi | ||||
| return 0 | ||||
| } | ||||
| 
 | ||||
|                       #-----------------------------# | ||||
|  | @ -1982,7 +1908,7 @@ local state m | |||
| if (($1)); then | ||||
| #shellcheck disable=2119 | ||||
| # This is only a (functioning) mock implementation meant as a proof of | ||||
| # concept for what XML describing qq2clone's current state may be like. | ||||
| # concept for what XML describing qq2clone's current state may be like | ||||
| # For this feature to be complete, it would: use a defined format, be  | ||||
| # implemented with proper, modular code, and contain all information to | ||||
| # fully define qq2clone's state except for machine images and domain xml. | ||||
|  | @ -2084,6 +2010,7 @@ else | |||
|   echo -n "all crashed idle in-shutdown off paused pmsuspended running" | ||||
|   echo " saved" | ||||
| fi | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| load_template () | ||||
|  | @ -2094,12 +2021,6 @@ load_template () | |||
| # PARAMETERS: None | ||||
| #=========================================================================# | ||||
| { | ||||
| # This is a hacky way of getting the information we need in a reasonably  | ||||
| # performant manner. It is a bit fragile, but not overly so assuming that | ||||
| # virsh's output is fairly consistent across versions. This method is | ||||
| # temporary, as later on qq2clone will include portions in (probably) C  | ||||
| # that use the libvirt API instead of virsh | ||||
| 
 | ||||
| check_template | ||||
| unset BAD_CL CL_MAP CL_STATE NAME_MAP | ||||
| declare -ga BAD_CL CL_MAP CL_STATE NAME_MAP | ||||
|  | @ -2119,29 +2040,13 @@ while read -r id; do | |||
|   uuid_map["$uuid"]="$id" | ||||
| done < <(sqlite3 "select id,uuid from CLONES where template='$t';") | ||||
| 
 | ||||
| # To use virsh in shell mode without having to repeatedly invoke it in | ||||
| # different subshells for a large performance penalty, we will run it in | ||||
| # the background and write to it with one fifo while reading it from  | ||||
| # another | ||||
| local temp | ||||
| temp="$(mktemp -d)" || temp_error | ||||
| mkfifo "$temp/fifo" &>/dev/null || unexpected_error load_template | ||||
| exec 4<>"$temp/fifo" | ||||
| virsh <&3 >&4 2>&4 & | ||||
| # virsh prepends 5 lines of useless output | ||||
| local c; for ((c=5;c>0;c--)); do read -r <&4; done | ||||
| lv_api_do_open | ||||
| 
 | ||||
| local prompt="virsh #" # In the virsh shell, input lines start with this | ||||
| 
 | ||||
|   echo "list --all --uuid" >&3 | ||||
|   echo "echo EOF" >&3 | ||||
|   while read -r uuid <&4; do | ||||
|     { [[ "$uuid" =~ ^$prompt ]] || [[ -z "$uuid" ]] ; } && continue | ||||
|     [[ "$uuid" == "EOF" ]] && break | ||||
|      | ||||
|     [[ -n "${uuid_map["$uuid"]}" ]] &&  | ||||
|       CL_MAP["${uuid_map["$uuid"]}"]="$uuid" | ||||
|   done | ||||
| lv_api_do_comm list | ||||
| while read -r uuid; do | ||||
|   [[ -n "${uuid_map["$uuid"]}" ]] && | ||||
|     CL_MAP["${uuid_map["$uuid"]}"]="$uuid" | ||||
| done < <(read_pipe); | ||||
| 
 | ||||
| local match _uuid | ||||
| for uuid in "${!uuid_map[@]}"; do | ||||
|  | @ -2153,43 +2058,15 @@ for uuid in "${!uuid_map[@]}"; do | |||
|   BAD_CL["${uuid_map["$uuid"]}"]="$uuid" | ||||
| done | ||||
| 
 | ||||
| local line | ||||
| local state="" name="" | ||||
| for id in "${!CL_MAP[@]}"; do | ||||
|   uuid="${CL_MAP["$id"]}" | ||||
|   echo "domstate $uuid" >&3 | ||||
|   echo "echo EOF" >&3 | ||||
|   while read -r line <&4; do | ||||
|   { [[ "$line" =~ ^$prompt ]] || [[ -z "$line" ]] ; } && continue | ||||
|   [[ "$line" == "EOF" ]] && break | ||||
|     [[ "$line" == "in shutdown" ]] && line="in-shutdown" | ||||
|     [[ "$line" == "shut off" ]] && line="off" | ||||
|     CL_STATE["$id"]="$line" | ||||
|   done | ||||
|    | ||||
|   echo "domname $uuid" >&3 | ||||
|   echo "echo EOF" >&3 | ||||
|   while read -r line <&4; do | ||||
|   { [[ "$line" =~ ^$prompt ]] || [[ -z "$line" ]] ; } && continue | ||||
|   [[ "$line" == "EOF" ]] && break | ||||
|     NAME_MAP["$id"]="$line" | ||||
|   done | ||||
|     lv_api_do_comm get_state "$uuid" && state="$(read_pipe)" | ||||
|     CL_STATE["$id"]="$state" | ||||
|     lv_api_do_comm get_name "$uuid" && name="$(read_pipe)" | ||||
|     NAME_MAP["$id"]="$name" | ||||
| done | ||||
| 
 | ||||
| echo "list --all --uuid --with-managed-save" >&3 | ||||
| echo "echo EOF" >&3 | ||||
| 
 | ||||
| while read -r uuid <&4; do | ||||
|   { [[ "$uuid" =~ ^$prompt ]] || [[ -z "$uuid" ]] ; } && continue | ||||
|   [[ "$uuid" == "EOF" ]] && break | ||||
|   id="${uuid_map["$uuid"]}" | ||||
|   [[ -z "$id" ]] && continue;  [[ -z "${CL_MAP["$id"]}" ]] && continue; | ||||
|   CL_STATE["$id"]="saved" | ||||
| done | ||||
| 
 | ||||
| echo "quit" >&3 | ||||
| exec 4>&- | ||||
| exec 4<&- | ||||
| rm -rf "$temp" &>/dev/null | ||||
| lv_api_do_close | ||||
| 
 | ||||
| return 0 | ||||
| } | ||||
|  | @ -2349,7 +2226,7 @@ for ((i=0;i<${#parts[@]};i++)); do | |||
|   fi | ||||
| 
 | ||||
|   if ((not)); then | ||||
|     minus=" $minus $p " | ||||
|     minus="$minus $p" | ||||
|   else | ||||
|     plus="$plus $p" | ||||
|   fi | ||||
|  | @ -2359,7 +2236,7 @@ done | |||
| local n before=0 | ||||
| while read -r n; do | ||||
|   [[ -z "$n" ]] && continue | ||||
|   [[ "$minus" =~ [[:space:]]${n}[[:space:]] ]] && continue | ||||
|   [[ " $minus " =~ [[:space:]]${n}[[:space:]] ]] && continue | ||||
|   ((before)) && echo -n " "; before=1 | ||||
|   echo -n "$n" | ||||
| done < <( tr " " "\n" <<<"$plus" | sort -n | uniq ) | ||||
|  | @ -2388,10 +2265,7 @@ shift | |||
| 
 | ||||
| local verbose_coms | ||||
| verbose_coms="config|check|list|list-templates|exec|edit|modify-template" | ||||
| if (( OPT[QUIET] == 2)) && | ||||
|   [[ ! "$com" =~ ^($verbose_coms)$  | ||||
| ]];  | ||||
| then | ||||
| if (( OPT[QUIET] == 2)) && [[ ! "$com" =~ ^($verbose_coms)$ ]]; then | ||||
|   exec &>/dev/null | ||||
| fi | ||||
| 
 | ||||
|  | @ -2557,9 +2431,6 @@ for t in "${templates[@]}"; do | |||
|   echo | ||||
| done | ||||
| 
 | ||||
| echo "Do a complete check for potentially orphaned images files now?" | ||||
| prompt_yes_no && { echo; prompt_delete_orphans; } | ||||
| 
 | ||||
| exit 0 | ||||
| } | ||||
| #=========================================================================# | ||||
|  | @ -2997,6 +2868,7 @@ hr () | |||
| #=========================================================================# | ||||
| { | ||||
| echo ---------------------------------------------------------------------- | ||||
| return 0 | ||||
| } | ||||
| #=========================================================================# | ||||
| parse_flags () | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue