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:
Jesse Gardner 2021-03-22 18:48:23 -07:00
commit ff65da8adb
4 changed files with 477 additions and 319 deletions

BIN
lv_api_do Executable file

Binary file not shown.

290
lv_api_do.c Normal file
View File

@ -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");
}

View File

@ -28,7 +28,7 @@ requires:
Bash 4.0+
qemu-img
libvirt tools:
libvirt tools:
virt-clone
virt-xml
virt-xml-validate
@ -413,16 +413,12 @@ are involved. This may be due to apparmor, or it may be an issue with
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.
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. 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

488
qq2clone
View File

@ -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,14 +65,13 @@ 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
}
#=========================================================================#
read_pipe ()
# DESCRIPTION: Flushes the contents of the named pipe to stdout,
# DESCRIPTION: Flushes the contents of the named pipe to stdout,
# nonblocking
# INPUT: None
# OUTPUT: Contents of named pipe on fd3
@ -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"
@ -94,9 +103,9 @@ write_pipe ()
# INPUT: Tell write_pipe whether information is coming on stdin or from
# a parameter, then pass information
# OUTPUT: None
# PARAMETERS: $1: '0' if passing another parameter(s), '1' if writing to
# PARAMETERS: $1: '0' if passing another parameter(s), '1' if writing to
# stdin instead.
# $2 and on: If $1 is 0, this is the information write_pipe will
# $2 and on: If $1 is 0, this is the information write_pipe will
# write as "$*"
#=========================================================================#
{
@ -114,12 +123,84 @@ 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---#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
#-------------------------------------------#
#=========================================================================#
check_config ()
#= DESCRIPTION: Given a name or name/value pair, check if it is a
@ -144,7 +225,7 @@ def_opt[STORAGE]="^/.*"
(( $# == 1 )) &&
{ [[ " ${!def_opt[*]} " =~ [[:space:]]${1}[[:space:]] ]];
return $?; }
local patt="${def_opt["${1}"]}"
[[ -n "$patt" ]] || return 1
[[ "$2" =~ $patt ]] || return 1
@ -187,8 +268,8 @@ done
((missing)) &&
{ echo "This script won't run until you install the listed software" >&2;
exit "$E_depends"; }
return 0
return 0
}
#=========================================================================#
disp_conf_names ()
@ -260,7 +341,7 @@ fi
return 0
}
first_run_setup ()
# DESCRIPTION: Generate a new database with default config values,
# DESCRIPTION: Generate a new database with default config values,
# create subdirectories of QQ2_DIR
# INPUT: None
# OUTPUT: None
@ -305,10 +386,10 @@ fi
sqlite3 <<EOF
create table CLONES(uuid TEXT, id INTEGER, template TEXT, disks TEXT);
create table TEMPLATES(name TEXT, md5sum TEXT, disks TEXT,\
valid INTEGER);
create table CONFIG(name TEXT, value TEXT);
insert into CONFIG values('TEMPLATE_DIR', '${TEMPLATE_DIR}');
insert into CONFIG values('USE_SPICE', '${use_spice}');
@ -337,13 +418,13 @@ get_config ()
[[ -e "$QQ2_DIR/sqlite3" ]] ||
{ echo "The sqlite3 binary is missing from $QQ2_DIR";
exit "$E_depends"; }
local check
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
@ -415,7 +497,7 @@ return 0
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
#-----------------------------------#
#=========================================================================#
prompt_num ()
# DESCRIPTION: Prompt user for a number between $1 and $2
@ -461,7 +543,7 @@ prompt_yes_no ()
# OUTPUT: Prompts for input, returns 1 for N/n or 0 for Y/y
# PARAMETERS: None
#=========================================================================
#
#
{
local char
until [[ "$char" =~ ^[YyNn]$ ]]; do
@ -496,8 +578,7 @@ 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
@ -522,7 +603,7 @@ xmllint --xpath "$1" --auto |& grep -qi 'xpath error' &&
xmllint --noblanks --dropdtd --nowarning --xpath "$1" \
2>/dev/null <(read_pipe) | write_pipe 1
return 0
}
#=========================================================================#
@ -911,7 +992,7 @@ if (( ${#CL_MAP[@]} + ${#BAD_CL[@]} )); then
read -rs
echo ; } >&2
fi
local id uuid cl_name
while read -r id; do
read -r uuid
@ -967,7 +1048,7 @@ fi
#---ERROR MESSAGES AND CHECKS---#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
#-------------------------------#
#=========================================================================#
arg_error ()
# DESCRIPTION: If args are too few, too many, or simply wrong, this
@ -1024,10 +1105,10 @@ if [[ "$1" == "-r" ]]; then
else
redir=0
fi
while (($#)); do
if { chmod +rw "$1" || { [[ -w "$1" ]] && [[ -r "$1" ]]; } ||
readlink "$1" ; } &>/dev/null;
if { chmod +rw "$1" || { [[ -w "$1" ]] && [[ -r "$1" ]]; } ||
readlink "$1" ; } &>/dev/null;
then
shift
elif [[ -e "$1" ]]; then
@ -1041,7 +1122,7 @@ then
local type line
type="$(file -b "$1")"
if [[ "$type" =~ directory ]] && ((redir)); then
while read -r line; do
while read -r line; do
check_rw -r "$line"
done < <(find "$1" 2>/dev/null)
fi
@ -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
}
#-----------------------------#
@ -1583,7 +1509,7 @@ return 0
#---INTERACT WITH VIRSH/VMs---#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
#-----------------------------#
#=========================================================================#
clone ()
# DESCRIPTION: Clone a virtual machine from OPT[TEMPLATE]
@ -1592,7 +1518,7 @@ clone ()
# PARAMETERS: $1: (Optional) If '0', create clone intended for staging
# changes to a base template image
#=========================================================================#
{
{
local base_mach_name line check i
local txml="${OPT[TEMPLATE_DIR]}/${OPT[TEMPLATE]}.xml"
@ -1772,7 +1698,7 @@ fi
local virsh_out
virsh_out="$(virsh undefine "$uuid" "${undef_args[@]}" 2>&1)" ||
{ echo "$virsh_out"; unexpected_error delete_machine; }
sqlite3 "delete from CLONES where id='$1' and \
template='${OPT[TEMPLATE]}';"
@ -1981,8 +1907,8 @@ read -ra states \
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.
# This is only a (functioning) mock implementation meant as a proof of
# 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.
@ -2057,33 +1983,34 @@ list_states ()
if (($#)); then
if [[ "$1" == connect ]]; then
echo "all"
elif [[ "$1" == destroy ]]; then
elif [[ "$1" == destroy ]]; then
echo "running idle paused in-shutdown pmsuspended"
elif [[ "$1" == exec ]]; then
echo "all"
elif [[ "$1" == restore ]]; then
echo "saved"
elif [[ "$1" == resume ]]; then
echo "paused"
elif [[ "$1" == restore ]]; then
echo "saved"
elif [[ "$1" == resume ]]; then
echo "paused"
elif [[ "$1" == rm ]]; then
echo "all"
elif [[ "$1" == rm-wipe ]]; then
echo "all"
elif [[ "$1" == rm-shred ]]; then
echo "all"
elif [[ "$1" == save ]]; then
elif [[ "$1" == save ]]; then
echo "running pmsuspended idle paused paused"
elif [[ "$1" == save-rm ]]; then
echo "saved"
elif [[ "$1" == start ]]; then
elif [[ "$1" == start ]]; then
echo "off crashed saved"
elif [[ "$1" == suspend ]]; then
echo "running pmsuspended idle"
elif [[ "$1" == suspend ]]; then
echo "running pmsuspended idle"
fi
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,30 +2040,14 @@ 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
lv_api_do_comm list
while read -r uuid; do
[[ -n "${uuid_map["$uuid"]}" ]] &&
CL_MAP["${uuid_map["$uuid"]}"]="$uuid"
done < <(read_pipe);
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
local match _uuid
for uuid in "${!uuid_map[@]}"; do
match=0
@ -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
}
@ -2201,7 +2078,7 @@ save_domain ()
# PARAMETERS: $1: Machine number
#=========================================================================#
{
local uuid
local uuid
uuid="${CL_MAP["$1"]}"
virsh managedsave "$uuid" &>/dev/null
return 0
@ -2347,9 +2224,9 @@ for ((i=0;i<${#parts[@]};i++)); do
else
return 1
fi
if ((not)); then
minus=" $minus $p "
if ((not)); then
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
@ -2536,9 +2410,9 @@ for t in "${templates[@]}"; do
fi
fi
echo
local n sum
sum="$(( ${#BAD_CL[@]} + ${#CL_MAP[@]} ))"
echo "TOTAL CLONES: $sum"
if (( ${#BAD_CL[@]} )); then
@ -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
}
#=========================================================================#
@ -2601,7 +2472,7 @@ if ((OPT[QUIET] == 2)); then
fi
(( $# )) || arg_error 0 config
if [[ "$1" == "list" ]]; then
[[ -n "$2" ]] && arg_error 1 "config list"
disp_conf_names
@ -2621,8 +2492,8 @@ fi
if [[ "$1" == "edit" ]]; then
[[ -n "$2" ]] || arg_error 0 "config edit"
[[ -n "$4" ]] && arg_error 1 "config edit"
check_config "$option" || { echo "Unknown option: $option";
exit "$E_args"; }
check_config "$option" || { echo "Unknown option: $option";
exit "$E_args"; }
local line
if (($#==3));then
line="$3"
@ -2833,7 +2704,7 @@ exec_com_list ()
# qq2clone's overall state
#=========================================================================#
{
(( $# > 1)) && arg_error 1 "list"
(( $# > 1)) && arg_error 1 "list"
if (($#)); then
local line
if [[ "$1" == "all" ]]; then
@ -2846,7 +2717,7 @@ if (($#)); then
done < <(get_template_list)
elif [[ "$1" == "xml" ]]; then
echo "<qq2clone directory=\"${QQ2_DIR}\">"
local name value
local name value
while read -r name; do
read -r value
echo " <config name=\"$name\" value=\"$value\" />"
@ -2855,7 +2726,7 @@ if (($#)); then
while read -r line; do
OPT[TEMPLATE]="$line"
load_template
list_display 1
list_display 1
done < <(get_template_list)
echo "</qq2clone>"
else
@ -2920,7 +2791,7 @@ if [[ "$2" == "prepare-image" ]]; then
((is_staging == 2)) && stage_error
((is_staging)) || { clone 0; load_template; }
connect 0
elif [[ "$2" == "commit-image" ]]; then
((is_staging == 2)) && stage_error
if (($#==3)); then
@ -2929,7 +2800,7 @@ elif [[ "$2" == "commit-image" ]]; then
((is_staging)) ||
{ echo "No changes are staged" >&2; exit "$E_args"; }
commit_image "$@"
elif [[ "$2" == "destroy-image" ]]; then
((is_staging == 2)) && stage_error
local state uuid
@ -2938,14 +2809,14 @@ elif [[ "$2" == "destroy-image" ]]; then
{ echo "Domain is not running" >&2; exit "$E_args"; }
uuid="${CL_MAP[0]}"
virsh destroy "$uuid" &>/dev/null
elif [[ "$2" == "discard-image" ]]; then
((is_staging == 2)) && stage_error
((is_staging)) ||
{ echo "No image to discard" >&2; exit "$E_args"; }
delete_machine 0 0
((OPT[QUIET])) || echo "Image discarded"
elif [[ "$2" == rename ]]; then
(( $#==3)) || arg_error 0 "modify-template $1 $2"
rename_template "$@"
@ -2997,6 +2868,7 @@ hr ()
#=========================================================================#
{
echo ----------------------------------------------------------------------
return 0
}
#=========================================================================#
parse_flags ()