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 '.' | 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 | 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.  | track it and will not be able to perform commands on it anymore. This will | ||||||
| If virsh undefine is run on a clone, qq2clone will not be able to see  | be addressed in the future using custom metadata in the libvirt domain | ||||||
| it once it is turned off. This limitation will be eliminated or reduced in  | XML. If the user undefines a domain, this will obviously cause it to | ||||||
| the future, when qq2clone moves away from relying on virsh and implements  | disappear from qq2clone's perspective when it is turned off, creating a | ||||||
| direct usage of the libvirt API. It could be addressed now by using  | discrepancy in its database. This can be fixed with **qq2clone** **check**. | ||||||
| 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. |  | ||||||
| 
 | 
 | ||||||
| qq2clone can only produce clones by making qcow2 image files. The backing  | 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  | file need not be qcow2, but the images produced by qq2clone always will  | ||||||
|  |  | ||||||
							
								
								
									
										388
									
								
								qq2clone
								
								
								
								
							
							
						
						
									
										388
									
								
								qq2clone
								
								
								
								
							|  | @ -1,11 +1,11 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| #shellcheck disable=1090 disable=2012 | #shellcheck disable=1090 disable=2012 | ||||||
| 
 | 
 | ||||||
|                             #-----------------# |                         #--------------------# | ||||||
|                             #@@@@@@@@@@@@@@@@@# |                         #@@@@@@@@@@@@@@@@@@@@# | ||||||
|                             #---ERROR CODES---# |                         #---LITERAL VALUES---# | ||||||
|                             #@@@@@@@@@@@@@@@@@# |                         #@@@@@@@@@@@@@@@@@@@@# | ||||||
|                             #-----------------# |                         #--------------------# | ||||||
| 
 | 
 | ||||||
| E_permission=10 # No permission for access or file does not exist | E_permission=10 # No permission for access or file does not exist | ||||||
| E_depends=11    # Lacking required software | 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_file=18       # Expected file does not exist or is of wrong type/format | ||||||
| E_unexpected=19 # Probably a bug in qq2clone | 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---# |            #---NAMED PIPE FOR PASSING DATA BETWEEN FUNCTIONS---# | ||||||
|  | @ -55,8 +65,7 @@ TEMPDIR=$(mktemp -d) || temp_error | ||||||
| #shellcheck disable=2064 | #shellcheck disable=2064 | ||||||
| trap "exec 3>&-; exec 3<&-;rm -rf $TEMPDIR" EXIT | trap "exec 3>&-; exec 3<&-;rm -rf $TEMPDIR" EXIT | ||||||
| fifo_path="${TEMPDIR}/qq2clone_fifo" | fifo_path="${TEMPDIR}/qq2clone_fifo" | ||||||
| mkfifo "$fifo_path" || | mkfifo "$fifo_path" || fifo_error | ||||||
|   { echo "Cannot make fifo" >&2; exit "$E_extcom" ;} |  | ||||||
| exec 3<>"$fifo_path" | exec 3<>"$fifo_path" | ||||||
| return 0 | return 0 | ||||||
| } | } | ||||||
|  | @ -75,7 +84,7 @@ read_pipe () | ||||||
| echo "EOF" >&3 | echo "EOF" >&3 | ||||||
| local line match | local line match | ||||||
| while IFS= read -r line <&3; do | 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 |   if [[ "$line" =~ ^\+(.*)$ ]]; then | ||||||
|     match="${BASH_REMATCH[1]}" |     match="${BASH_REMATCH[1]}" | ||||||
|     echo "$match" |     echo "$match" | ||||||
|  | @ -114,6 +123,78 @@ fi | ||||||
| return 0 | 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---# |               #---GET/ALTER CONFIGURATION, CHECK SYSTEM---# | ||||||
|  | @ -188,7 +269,7 @@ done | ||||||
|   { echo "This script won't run until you install the listed software" >&2; |   { echo "This script won't run until you install the listed software" >&2; | ||||||
|   exit "$E_depends"; }  |   exit "$E_depends"; }  | ||||||
| 
 | 
 | ||||||
|   return 0 | return 0 | ||||||
| } | } | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| disp_conf_names () | disp_conf_names () | ||||||
|  | @ -343,7 +424,7 @@ read -r check \ | ||||||
|   < <(sqlite3 "select exists ( select * from CONFIG)") |   < <(sqlite3 "select exists ( select * from CONFIG)") | ||||||
| ((check)) || | ((check)) || | ||||||
|   { echo "Is the database corrupt? No CONFIG table!" 2>/dev/null;  |   { echo "Is the database corrupt? No CONFIG table!" 2>/dev/null;  | ||||||
|     unexpected_error get_config; } |     exit "$E_config"; } | ||||||
| 
 | 
 | ||||||
| declare -gA OPT | declare -gA OPT | ||||||
| declare -a opts | declare -a opts | ||||||
|  | @ -377,6 +458,7 @@ return 0 | ||||||
|                         #---USAGE INFORMATION---# |                         #---USAGE INFORMATION---# | ||||||
|                         #@@@@@@@@@@@@@@@@@@@@@@@# |                         #@@@@@@@@@@@@@@@@@@@@@@@# | ||||||
|                         #-----------------------# |                         #-----------------------# | ||||||
|  | 
 | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| usage () | usage () | ||||||
| # DESCRIPTION: Output basic usage information | # DESCRIPTION: Output basic usage information | ||||||
|  | @ -497,7 +579,6 @@ xml="$(virt-xml "$@" <<<"$(read_pipe)" 2>/dev/null)" || | ||||||
| write_pipe 1 <<<"$xml" | write_pipe 1 <<<"$xml" | ||||||
| return 0 | return 0 | ||||||
| } | } | ||||||
|                           |  | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| find_tag () | find_tag () | ||||||
| # DESCRIPTION: Use xmllint to do an xpath search of xml and write_pipe | # 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 | 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 () | set_error () | ||||||
| # DESCRIPTION: Used when convert_to_seq fails | # DESCRIPTION: Used when convert_to_seq fails | ||||||
| # INPUT: None | # INPUT: None | ||||||
|  | @ -1399,183 +1502,6 @@ for id in "${!BAD_CL[@]}"; do | ||||||
|   echo |   echo | ||||||
| done | done | ||||||
| return 0 | 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 | if (($1)); then | ||||||
| #shellcheck disable=2119 | #shellcheck disable=2119 | ||||||
| # This is only a (functioning) mock implementation meant as a proof of | # 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  | # For this feature to be complete, it would: use a defined format, be  | ||||||
| # implemented with proper, modular code, and contain all information to | # implemented with proper, modular code, and contain all information to | ||||||
| # fully define qq2clone's state except for machine images and domain xml. | # 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 -n "all crashed idle in-shutdown off paused pmsuspended running" | ||||||
|   echo " saved" |   echo " saved" | ||||||
| fi | fi | ||||||
|  | return 0 | ||||||
| } | } | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| load_template () | load_template () | ||||||
|  | @ -2094,12 +2021,6 @@ load_template () | ||||||
| # PARAMETERS: None | # 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 | check_template | ||||||
| unset BAD_CL CL_MAP CL_STATE NAME_MAP | unset BAD_CL CL_MAP CL_STATE NAME_MAP | ||||||
| declare -ga 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" |   uuid_map["$uuid"]="$id" | ||||||
| done < <(sqlite3 "select id,uuid from CLONES where template='$t';") | done < <(sqlite3 "select id,uuid from CLONES where template='$t';") | ||||||
| 
 | 
 | ||||||
| # To use virsh in shell mode without having to repeatedly invoke it in | lv_api_do_open | ||||||
| # 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 |  | ||||||
| 
 |  | ||||||
| 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 |  | ||||||
| 
 | 
 | ||||||
|  | lv_api_do_comm list | ||||||
|  | while read -r uuid; do | ||||||
|   [[ -n "${uuid_map["$uuid"]}" ]] && |   [[ -n "${uuid_map["$uuid"]}" ]] && | ||||||
|     CL_MAP["${uuid_map["$uuid"]}"]="$uuid" |     CL_MAP["${uuid_map["$uuid"]}"]="$uuid" | ||||||
|   done | done < <(read_pipe); | ||||||
| 
 | 
 | ||||||
| local match _uuid | local match _uuid | ||||||
| for uuid in "${!uuid_map[@]}"; do | for uuid in "${!uuid_map[@]}"; do | ||||||
|  | @ -2153,43 +2058,15 @@ for uuid in "${!uuid_map[@]}"; do | ||||||
|   BAD_CL["${uuid_map["$uuid"]}"]="$uuid" |   BAD_CL["${uuid_map["$uuid"]}"]="$uuid" | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
| local line | local state="" name="" | ||||||
| for id in "${!CL_MAP[@]}"; do | for id in "${!CL_MAP[@]}"; do | ||||||
|   uuid="${CL_MAP["$id"]}" |     lv_api_do_comm get_state "$uuid" && state="$(read_pipe)" | ||||||
|   echo "domstate $uuid" >&3 |     CL_STATE["$id"]="$state" | ||||||
|   echo "echo EOF" >&3 |     lv_api_do_comm get_name "$uuid" && name="$(read_pipe)" | ||||||
|   while read -r line <&4; do |     NAME_MAP["$id"]="$name" | ||||||
|   { [[ "$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 |  | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
| echo "list --all --uuid --with-managed-save" >&3 | lv_api_do_close | ||||||
| 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 |  | ||||||
| 
 | 
 | ||||||
| return 0 | return 0 | ||||||
| } | } | ||||||
|  | @ -2349,7 +2226,7 @@ for ((i=0;i<${#parts[@]};i++)); do | ||||||
|   fi |   fi | ||||||
| 
 | 
 | ||||||
|   if ((not)); then |   if ((not)); then | ||||||
|     minus=" $minus $p " |     minus="$minus $p" | ||||||
|   else |   else | ||||||
|     plus="$plus $p" |     plus="$plus $p" | ||||||
|   fi |   fi | ||||||
|  | @ -2359,7 +2236,7 @@ done | ||||||
| local n before=0 | local n before=0 | ||||||
| while read -r n; do | while read -r n; do | ||||||
|   [[ -z "$n" ]] && continue |   [[ -z "$n" ]] && continue | ||||||
|   [[ "$minus" =~ [[:space:]]${n}[[:space:]] ]] && continue |   [[ " $minus " =~ [[:space:]]${n}[[:space:]] ]] && continue | ||||||
|   ((before)) && echo -n " "; before=1 |   ((before)) && echo -n " "; before=1 | ||||||
|   echo -n "$n" |   echo -n "$n" | ||||||
| done < <( tr " " "\n" <<<"$plus" | sort -n | uniq ) | done < <( tr " " "\n" <<<"$plus" | sort -n | uniq ) | ||||||
|  | @ -2388,10 +2265,7 @@ shift | ||||||
| 
 | 
 | ||||||
| local verbose_coms | local verbose_coms | ||||||
| verbose_coms="config|check|list|list-templates|exec|edit|modify-template" | verbose_coms="config|check|list|list-templates|exec|edit|modify-template" | ||||||
| if (( OPT[QUIET] == 2)) && | if (( OPT[QUIET] == 2)) && [[ ! "$com" =~ ^($verbose_coms)$ ]]; then | ||||||
|   [[ ! "$com" =~ ^($verbose_coms)$  |  | ||||||
| ]];  |  | ||||||
| then |  | ||||||
|   exec &>/dev/null |   exec &>/dev/null | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  | @ -2557,9 +2431,6 @@ for t in "${templates[@]}"; do | ||||||
|   echo |   echo | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
| echo "Do a complete check for potentially orphaned images files now?" |  | ||||||
| prompt_yes_no && { echo; prompt_delete_orphans; } |  | ||||||
| 
 |  | ||||||
| exit 0 | exit 0 | ||||||
| } | } | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
|  | @ -2997,6 +2868,7 @@ hr () | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| { | { | ||||||
| echo ---------------------------------------------------------------------- | echo ---------------------------------------------------------------------- | ||||||
|  | return 0 | ||||||
| } | } | ||||||
| #=========================================================================# | #=========================================================================# | ||||||
| parse_flags () | parse_flags () | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue