diff options
Diffstat (limited to 'open-vm-tools/libDeployPkg/linuxDeployment.c')
-rw-r--r-- | open-vm-tools/libDeployPkg/linuxDeployment.c | 1339 |
1 files changed, 1339 insertions, 0 deletions
diff --git a/open-vm-tools/libDeployPkg/linuxDeployment.c b/open-vm-tools/libDeployPkg/linuxDeployment.c new file mode 100644 index 00000000..951258e3 --- /dev/null +++ b/open-vm-tools/libDeployPkg/linuxDeployment.c @@ -0,0 +1,1339 @@ +/********************************************************* + * Copyright (C) 2006-2014 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <time.h> +#include <stdbool.h> + +#include "mspackWrapper.h" +#include "deployPkgFormat.h" +#include "deployPkg/linuxDeployment.h" +#include "imgcust-common/process.h" +#include "imgcust-guest/guestcust-events.h" +#include "mspackWrapper.h" +#include "rpcout.h" +#include "toolsDeployPkg.h" + +/* + * These are covered by #ifndef to give the ability to change these + * variables from makefile (mostly planned for the forthcoming Solaris + * customization) + */ + +#define CLEANUPCMD "/bin/rm -r -f " + +#ifndef EXTRACTPATH +#define EXTRACTPATH "/tmp/.vmware/linux/deploy" +#endif + +#ifndef CLEANUPPATH +#define CLEANUPPATH "/tmp/.vmware" +#endif + +#ifndef BASEFILENAME +#define BASEFILENAME "/tmp/.vmware-deploy" +#endif + +#ifndef CABCOMMANDLOG +#define CABCOMMANDLOG "/var/log/vmware-imc/toolsDeployPkg.log" +#endif + +#define MAXSTRING 2048 + +/* + * Constant definitions + */ + +static const char ENDOFLINEMARKER = '\n'; +static const char SPACECHAR = ' '; +static const char TABCHAR = '\t'; +static const char QUOTECHAR = '"'; +static const char BACKSLASH = '\\'; +static const char* INPROGRESS = "INPROGRESS"; +static const char* DONE = "Done"; +static const char* ERRORED = "ERRORED"; + +// Possible return codes from perl script +static const int CUST_SUCCESS = 0; +static const int CUST_GENERIC_ERROR = 255; +static const int CUST_NETWORK_ERROR = 254; +static const int CUST_NIC_ERROR = 253; +static const int CUST_DNS_ERROR = 252; + +// Common return values +static const int DEPLOY_SUCCESS = 0; +static const int DEPLOY_ERROR = -1; + +/* + * Linked list definition + * + * +---------+---------+ + * | data | Next ---+--> + * +---------+---------+ + * + */ + +struct List { + char* data; + struct List* next; +}; + +/* + * Template function definition + */ + +// Private functions +static Bool GetPackageInfo(const char* pkgName, char** cmd, uint8* type); +static Bool ExtractZipPackage(const char* pkg, const char* dest); +static Bool CreateDir(const char* path); +static void Init(void); +static struct List* AddToList(struct List* head, const char* token); +static int ListSize(struct List* head); +static int Touch(const char* state); +static int UnTouch(const char* state); +static int TransitionState(const char* stateFrom, const char* stateTo); +static int Deploy(const char* pkgName); +static char** GetFormattedCommandLine(const char* command); +static int ForkExecAndWaitCommand(const char* command); +static void SetDeployError(const char* format, ...); +static const char* GetDeployError(void); +static void NoLogging(int level, const char* fmtstr, ...); + +/* + * Globals + */ + +static char* gDeployError = NULL; +static LogFunction sLog = NoLogging; + +// ..................................................................................... + +/** + * + * Panic + * + * Used by VMware libraries pass PANIC signals to the parent application. + * + * @param fmstr [in] Format to print the arguments in. + * @param args [in] Argument list. + * + **/ +void +Panic(const char *fmtstr, + va_list args) +{ + /* Ignored */ + sLog(log_warning, "Panic call back invoked. \n"); +} + +// ..................................................................................... + +/** + * + * Debug + * + * Mechanism used by VMware libraries to pass debug messages to the parent. + * + * @param fmstr [in] Format to print the arguments in. + * @param args [in] Argument list. + * + **/ +void +Debug(const char *fmtstr, + va_list args) +{ + /* Ignored */ +#ifdef VMX86_DEBUG + sLog(log_debug, "Debug callback invoked. \n"); +#endif +} + +// ..................................................................................... + +/** + *----------------------------------------------------------------------------- + * + * SetCustomizationStatusInVmxEx + * + * Set the guest customization status in the VMX server, returning responses. + * + * @param customizzationState [in] Customization state of the + * deployment/customization process + * @param errCode [in] Error code (can be success too) + * @param errMsg [in] Error message + * @param vmxResponse [out] VMX response. Note that this is _not_ a + success indicator but contains additional + info target functionality may provide + * @param vmxResponseLength [out] VMX response length + * @param responseBufferSize [in] VMX response buffer size + * + *----------------------------------------------------------------------------- + **/ + +static Bool +SetCustomizationStatusInVmxEx(int customizationState, + int errCode, + const char* errMsg, + char *vmxResponse, + size_t *vmxResponseLength, + size_t responseBufferSize) +{ + char *msg = NULL; + char *response = NULL; + size_t responseLength = 0; + Bool success; + + if (errMsg) { + msg = malloc(strlen(CABCOMMANDLOG) + 1 + strlen(errMsg) + 1); + strcpy (msg, CABCOMMANDLOG); + strcat (msg, "@"); + strcat (msg, errMsg); + } else { + msg = malloc(strlen(CABCOMMANDLOG) + 1); + strcpy (msg, CABCOMMANDLOG); + } + + success = RpcOut_sendOne(vmxResponse != NULL ? &response : NULL, + vmxResponse != NULL ? &responseLength : NULL, + "deployPkg.update.state %d %d %s", + customizationState, + errCode, + msg); + free (msg); + + if (vmxResponse != NULL) { + if (response != NULL) { + sLog(log_debug, "Got VMX response '%s'", response); + if (responseLength > responseBufferSize - 1) { + sLog(log_warning, + "The VMX response is too long (only %d chars are allowed)", + responseBufferSize - 1); + responseLength = responseBufferSize - 1; + } + memcpy(vmxResponse, response, responseLength); + free(response); + } + else { + sLog(log_debug, "Got no VMX response"); + responseLength = 0; + } + vmxResponse[responseLength] = 0; + if (vmxResponseLength != NULL) { + *vmxResponseLength = responseLength; + } + } + + if (!success) { + sLog(log_error, "Unable to set customization status in vmx.\n"); + } + + return success; +} + + +/** + *----------------------------------------------------------------------------- + * + * SetCustomizationStatusInVmx + * + * Set the VMX customization status in the VMX server. + * + * @param customizzationState [in] Customization state of the + * deployment/customization process + * @param errCode [in] Error code (can be success too) + * @param errMsg [in] Error message + * + *----------------------------------------------------------------------------- + **/ +static void +SetCustomizationStatusInVmx(int customizationState, + int errCode, + const char* errMsg) +{ + SetCustomizationStatusInVmxEx(customizationState, errCode, errMsg, NULL, NULL, 0); +} + +// ..................................................................................... + +/** + * + * DeployPkg_SetLogger + * + * Set the logging function. + * + * @param [in] log The logging function to be used + * @returns None + * + **/ +void +DeployPkg_SetLogger(LogFunction log) +{ + sLog = log; +} + +// ..................................................................................... + +/** + * + * NoLogging + * + * NOP log function. + * + * @param [in] level Log level + * @param [in] fmtstr Format string to format the variables. + * + **/ +static void +NoLogging(int level, const char* fmtstr, ...) +{ + // No logging +} + +// ...................................................................................... + +/** + * + * SetDeployError + * + * Sets the deployment error in a verbose style. Can be queried using + * GetDeployError. + * + * @param format [in] Format string to follow. + * @param ... [in] List of params to be formatted. + * + **/ +static void +SetDeployError(const char* format, ...) +{ + /* + * No Error check is employed since this is only an advisory service. + */ + va_list args; + + char* tmp = malloc(MAXSTRING); + + if (tmp) { + va_start(args, format); + vsprintf(tmp, format, args); + } + + if (gDeployError) { + free(gDeployError); + gDeployError = NULL; + } + + sLog(log_debug, "Setting deploy error: %s \n", tmp); + gDeployError = tmp; +} + +// ...................................................................................... + +/** + * + * GetDeployError + * + * Get the deploy error set using the SetDeployError method. + * + * @param None + * @returns Pointer to the deploy error string. + * + **/ +static const char* +GetDeployError(void) +{ + return gDeployError; +} + +// ...................................................................................... + +/** + * + * AddToList + * + * Add an element to the specified linked list. + * + * List organization: + * ------------------ + * <<head>> + * +----+---+ +----+---+ + * | D1 | x-+-> | D2 | x-+-> NULL + * +----+---+ +----+---+ + * <<tail>> + * + * @param head [in] Head of the linked list. + * @param token [in] Token to be added. + * @returns The head to the list. + * + **/ +struct List* +AddToList(struct List* head, const char* token) +{ + struct List* l; + struct List* tail; + char* data; + +#ifdef VMX86_DEBUG + sLog(log_debug, "Adding to list %s. \n", token); +#endif + data = malloc(strlen(token) + 1); + if (!data) { + SetDeployError("Error allocating memory. (%s)", strerror(errno)); + return NULL; + } + + strcpy(data, token); + + l = malloc(sizeof(struct List)); + if (!l) { + SetDeployError("Error allocating memory. (%s)", strerror(errno)); + return NULL; + } + + l->data = data; + l->next = NULL; + + tail = head; + while (tail && tail->next) { + tail = tail->next; + } + + if (tail) { + tail->next = l; + } + + return head ? head : l; +} + +// ...................................................................................... + +/** + * + * Listsize + * + * Return the size of the specified linked list. + * + * @param head [in] Head of the linked list. + * @preturns DEPLOY_SUCCESS on success and DEPLOY_ERROR on failure. + * + **/ +static int +ListSize(struct List* head) +{ + int sz = 0; + struct List* l; + + for(l = head; l; ++sz, l = l->next); +#ifdef VMX86_DEBUG + sLog(log_debug, "Query: List size is %i. \n", sz); +#endif + return sz; +} + +// ...................................................................................... + +/** + * + * DeleteList + * + * Delete the complete list. + * + * @param head [in] Head of the list to be deleted. + * @returns None + * + **/ +static void +DeleteList(struct List* head) +{ + struct List* t = head; +#ifdef VMX86_DEBUG + sLog(log_debug, "Cleaning the linked list. \n"); +#endif + + while(t) { + struct List* tmp = t; + t = t->next; + + // delete resource + free(tmp->data); + free(tmp); + } +} + +//...................................................................................... + +/** + * + * Initialize the deployment module. + * + * @param None + * @return None + * + **/ +static void +Init(void) +{ + // Clean up if there is any deployment locks/status before + sLog(log_info, "Cleaning old state file from tmp directory. \n"); + UnTouch(INPROGRESS); + UnTouch(DONE); + UnTouch(ERRORED); + + /* + * Set the error message as success. This will be replaced with an error + * message when an error occours. Standard Linux practice. + */ + SetDeployError("Success."); +} + +//...................................................................................... + +/** + * + * Get the command to execute from the cab file. + * + * @param [IN] packageName package file name + * @param [OUT] command command line from header + * @param [OUT] archiveType package archive format + * @return TRUE is success + * + **/ +Bool +GetPackageInfo(const char* packageName, + char** command, + uint8* archiveType) +{ + unsigned int sz; + VMwareDeployPkgHdr hdr; + int fd = open(packageName, O_RDONLY); + + // open errored ? + if (fd < 0) { + SetDeployError("Error opening file. (%s)", strerror(errno)); + return FALSE; + } + + // read the custom header + sz = read(fd, &hdr, sizeof(VMwareDeployPkgHdr)); + + if (sz != sizeof(VMwareDeployPkgHdr)) { + close(fd); + + SetDeployError("Error reading header. (%s)", strerror(errno)); + return FALSE; + } + + // close the file + close(fd); + + // Create space and copy the command + *command = malloc(VMWAREDEPLOYPKG_CMD_LENGTH); + if (!*command) { + SetDeployError("Error allocating memory."); + return FALSE; + } + + memcpy(*command, hdr.command, VMWAREDEPLOYPKG_CMD_LENGTH); + *archiveType = hdr.payloadType; + + return TRUE; +} + +//...................................................................................... + +/** + * + * Create a lock file. + * + * @param [IN] state The state of the system + * @returns DEPLOY_SUCCESS on success and DEPLOY_ERROR on error + * + **/ +static int +Touch(const char* state) +{ + char* fileName = malloc(strlen(BASEFILENAME) + 1 + strlen(state) + 1); + int fd; + + sLog(log_info, "ENTER STATE %s \n", state); + if (!fileName) { + SetDeployError("Error allocatin memory."); + return DEPLOY_ERROR; + } + + strcpy(fileName, BASEFILENAME); + strcat(fileName, "."); + strcat(fileName, state); + + fd = open(fileName, O_WRONLY|O_CREAT|O_EXCL, 0644); + + if (fd < 0) { + SetDeployError("Error creating lock file %s.(%s)", fileName, strerror(errno)); + free (fileName); + return DEPLOY_ERROR; + } + + close(fd); + free (fileName); + + return DEPLOY_SUCCESS; +} + +//...................................................................................... + +/** + * + * Delete a lock file. + * + * @param [IN] state The state of the system + * @returns DEPLOY_SUCCESS on success and DEPLOY_ERROR on error + * + **/ +static int +UnTouch(const char* state) +{ + char* fileName = malloc(strlen(BASEFILENAME) + 1 + strlen(state) + 1); + int result; + + sLog(log_info, "EXIT STATE %s \n", state); + if (!fileName) { + SetDeployError("Error allocating memory."); + return DEPLOY_ERROR; + } + + strcpy(fileName, BASEFILENAME); + strcat(fileName, "."); + strcat(fileName, state); + + result = remove(fileName); + + if (result < 0) { + SetDeployError("Error removing lock %s (%s)", fileName, strerror(errno)); + free (fileName); + return DEPLOY_ERROR; + } + + free (fileName); + return DEPLOY_SUCCESS; +} + +//...................................................................................... + +/** + * + * Depict a transitions from one state to another. the file corresponding to the + * the old state is deleted and a new file corresponding to the new state is + * created. This way it is ensured that the tmp directory is left is no status + * entry at all. The other way to do this is to rename a given. I have opted for + * deletion and creation to represent the physical transition. + * + * @param [IN] stateFrom The state from which the transition happens + * @param [IN] stateTo The state to which the transition happens + * @returns DEPLOY_SUCCESS on success and DEPLOY_ERROR on error + * + **/ +static int +TransitionState(const char* stateFrom, const char* stateTo) +{ + sLog(log_info, "Transitioning from state %s to state %s. \n", stateFrom, stateTo); + + // Create a file to indicate state to + if (stateTo) { + if (Touch(stateTo) == DEPLOY_ERROR) { + SetDeployError("Error creating new state %s. (%s)", stateTo, GetDeployError()); + return DEPLOY_ERROR; + } + } + + // Remove the old state file + if (stateFrom) { + if (UnTouch(stateFrom) == DEPLOY_ERROR) { + SetDeployError("Error deleting old state %s.(%s)", stateFrom, GetDeployError()); + return DEPLOY_ERROR; + } + } + + return DEPLOY_SUCCESS; +} + +/** + *----------------------------------------------------------------------------- + * + * GetNicsToEnable -- + * + * Returns ordinal numbers of nics to enable once customization is done. + * Ordinal numbers are read from a file in the deployment package and are + * separated by ",". Nics are disabled by VC before customization to avoid + * ip conflict on network while this vm is being customized. + * + * This method allocates memory and then reutrns the nics to caller. The + * caller should call free to get rid of memory allocated. + * + * @param dir [in] Directory where package files were expanded. + * @return ordinal number of nics to enable separated by ",". If no nic file is + * found or no nics need re-enabling NULL is returned. + * + *----------------------------------------------------------------------------- + */ + +static char* +GetNicsToEnable(const char* dir) +{ + /* + * The file nics.txt will list ordinal number of all nics to enable separated by + * a ",". In current architecture we can have max 4 nics. So we just have to read + * maximum of 7 characters. This code uses 1024 chars to make sure any future + * needs are accomodated. + */ + static const unsigned int NICS_SIZE = 1024; + static const char* nicFile = "/nics.txt"; + + FILE *file; + + char *ret = NULL; + char *fileName = malloc(strlen(dir) + strlen(nicFile) + 1); + strcpy(fileName, dir); + strcat(fileName, nicFile); + + file = fopen(fileName, "r"); + if (file) { + ret = malloc(NICS_SIZE); + if (fgets(ret, NICS_SIZE, file) == NULL) { + sLog(log_warning, "fgets() failed or reached EOF"); + } + + // Check various error condition + if (ferror(file)) { + SetDeployError("Error reading nic file %s (%s)", fileName, strerror(errno)); + free(ret); + ret = NULL; + } + + if (!feof(file)) { + SetDeployError("More than expected nics to enable. Nics: %s \n", ret); + free(ret); + ret = NULL; + } + + fclose(file); + } + + free(fileName); + return ret; +} + +/** + *----------------------------------------------------------------------------- + * + * TryToEnableNics -- + * + * Sends a command to connect network interfaces and waits synchronously + * for its completion. If NICs are not connected in predefined time the + * command is send again several times. + * + * Note that since guest has no direct visibility to NIC connection status + * we rely on VMX to get such info. + * + * Use the enableNicsX constants to fine tune behavior, if needed. + * + * @param nics List of nics that need to be activated. + * + *----------------------------------------------------------------------------- + */ + +static void +TryToEnableNics(const char *nics) +{ + static const int enableNicsRetries = 5; + static const int enableNicsWaitCount = 5; + static const int enableNicsWaitSeconds = 1; + + char vmxResponse[64]; // buffer for responses from VMX calls + + int attempt, count; + + for (attempt = 0; attempt < enableNicsRetries; ++attempt) { + sLog(log_debug, + "Trying to connect network interfaces, attempt %d", + attempt + 1); + + if (!SetCustomizationStatusInVmxEx(TOOLSDEPLOYPKG_RUNNING, + GUESTCUST_EVENT_ENABLE_NICS, + nics, + vmxResponse, + NULL, + sizeof(vmxResponse))) + { + sleep(enableNicsWaitCount * enableNicsWaitSeconds); + continue; + } + + // Note that we are checking for 'query nics' functionality in the loop to + // protect against potential vMotion during customization process in which + // case the new VMX could be older, i.e. not that supportive :) + if (strcmp(vmxResponse, QUERY_NICS_SUPPORTED) != 0) { + sLog(log_warning, "VMX doesn't support NICs connection status query"); + return; + } + + for (count = 0; count < enableNicsWaitCount; ++count) { + // vMotion is unlikely between check for support above and actual call here + if (SetCustomizationStatusInVmxEx(TOOLSDEPLOYPKG_RUNNING, + GUESTCUST_EVENT_QUERY_NICS, + nics, + vmxResponse, + NULL, + sizeof(vmxResponse)) && + strcmp(vmxResponse, NICS_STATUS_CONNECTED) == 0) + { + sLog(log_info, + "The network interfaces are connected on %d second", + (attempt * enableNicsWaitCount + count) * + enableNicsWaitSeconds); + return; + } + + sleep(enableNicsWaitSeconds); + } + } + + sLog(log_error, + "Can't connect network interfaces after %d attempts, giving up", + enableNicsRetries); +} + +/** + *----------------------------------------------------------------------------- + * + * _DeployPkg_SkipReboot -- + * + * Controls skipping the last reboot when customization package is deployed. + * XXX This is a UNDOCUMENTED function and is WORKAROUND solution to PR 536688 + * + * @param path IN: skip - whether to skip the final reboot after customization + * + *----------------------------------------------------------------------------- + */ + +static bool sSkipReboot = false; + +IMGCUST_API void +_DeployPkg_SkipReboot(bool skip) +{ + sSkipReboot = skip; +} + +//...................................................................................... + +/** + * + * Core function which takes care of deployment in Linux. + * Essentially it does + * - uncabing of the cabinet + * - execution of the command embedded in the cabinet header + * + * @param [IN[ packageName Package file to be used for deployment + * @returns DEPLOY_SUCCESS on success and DEPLOY_ERROR on error + * + **/ +static int +Deploy(const char* packageName) +{ + int deployStatus = DEPLOY_SUCCESS; + char* command = NULL; + int deploymentResult; + char *nics; + char* cleanupCommand; + uint8 archiveType; + + // Move to IN PROGRESS state + TransitionState(NULL, INPROGRESS); + + // Notify the vpx of customization in-progress state + SetCustomizationStatusInVmx(TOOLSDEPLOYPKG_RUNNING, + TOOLSDEPLOYPKG_ERROR_SUCCESS, + NULL); + + sLog(log_info, "Reading cabinet file %s. \n", packageName); + + // Get the command to execute + if (!GetPackageInfo(packageName, &command, &archiveType)) { + SetDeployError("Error extracting package header information. (%s)", + GetDeployError()); + return DEPLOY_ERROR; + } + + // Print the header command +#ifdef VMX86_DEBUG + sLog(log_debug, "Header command: %s \n ", command); +#endif + + // create the destination directory + if (!CreateDir(EXTRACTPATH "/")) { + free(command); + return DEPLOY_ERROR; + } + + if (archiveType == VMWAREDEPLOYPKG_PAYLOAD_TYPE_CAB) { + if (!ExtractCabPackage(packageName, EXTRACTPATH)) { + free(command); + return DEPLOY_ERROR; + } + } else if (archiveType == VMWAREDEPLOYPKG_PAYLOAD_TYPE_ZIP) { + if (!ExtractZipPackage(packageName, EXTRACTPATH)) { + free(command); + return DEPLOY_ERROR; + } + } + + // Run the deployment command + sLog(log_info, "Launching deployment %s. \n", command); + deploymentResult = ForkExecAndWaitCommand(command); + free (command); + + if (deploymentResult != 0) { + sLog(log_error, "Customization process returned with error. \n"); + sLog(log_debug, "Deployment result = %d \n", deploymentResult); + + if (deploymentResult == CUST_NETWORK_ERROR || deploymentResult == CUST_NIC_ERROR) { + // Network specific error in the guest + sLog(log_info, "Setting network error status in vmx. \n"); + SetCustomizationStatusInVmx(TOOLSDEPLOYPKG_RUNNING, + GUESTCUST_EVENT_NETWORK_SETUP_FAILED, + NULL); + } else { + // Generic error in the guest + sLog(log_info, "Setting generic error status in vmx. \n"); + SetCustomizationStatusInVmx(TOOLSDEPLOYPKG_RUNNING, + GUESTCUST_EVENT_CUSTOMIZE_FAILED, + NULL); + } + + // Move to ERROR state + TransitionState(INPROGRESS, ERRORED); + + // Set deploy status to be returned + deployStatus = DEPLOY_ERROR; + SetDeployError("Deployment failed. The forked off process returned error code."); + sLog(log_error, "Deployment failed. " + "The forked off process returned error code. \n"); + } else { + // Set vmx status - customization success + SetCustomizationStatusInVmx(TOOLSDEPLOYPKG_DONE, + TOOLSDEPLOYPKG_ERROR_SUCCESS, + NULL); + + // Move to DONE state + TransitionState(INPROGRESS, DONE); + + deployStatus = DEPLOY_SUCCESS; + sLog(log_info, "Deployment succeded. \n"); + } + + /* + * Read in nics to enable from the nics.txt file. We do it irrespective of the + * sucess/failure of the customization so that at the end of it we always + * have nics enabled. + */ + nics = GetNicsToEnable(EXTRACTPATH); + if (nics) { + // XXX: Sleep before the last SetCustomizationStatusInVmx + // This is a temporary-hack for PR 422790 + sleep(5); + sLog(log_info, "Wait before set enable-nics stats in vmx.\n"); + + TryToEnableNics(nics); + + free(nics); + } else { + sLog(log_info, "No nics to enable.\n"); + } + + // Clean up command + cleanupCommand = malloc(strlen(CLEANUPCMD) + strlen(CLEANUPPATH) + 1); + if (!cleanupCommand) { + SetDeployError("Error allocating memory."); + return DEPLOY_ERROR; + } + + strcpy(cleanupCommand, CLEANUPCMD); + strcat(cleanupCommand, CLEANUPPATH); + + sLog(log_info, "Launching cleanup. \n"); + if (ForkExecAndWaitCommand(cleanupCommand) != 0) { + sLog(log_warning, "Error while clean up. Error removing directory %s. (%s)", + EXTRACTPATH, strerror (errno)); + //TODO: What should be done if cleanup fails ?? + } + free (cleanupCommand); + + //Reset the guest OS + if (!sSkipReboot && !deploymentResult) { + // Reboot the Vm + pid_t pid = fork(); + if (pid == -1) { + sLog(log_error, "Failed to fork: %s", strerror(errno)); + } else if (pid == 0) { + // We're in the child + + // Repeatedly try to reboot to workaround PR 530641 where + // telinit 6 is overwritten by a telinit 2 + int rebootComandResult = 0; + do { + sLog(log_info, "Rebooting\n"); + rebootComandResult = ForkExecAndWaitCommand("/sbin/telinit 6"); + sleep(1); + } while (rebootComandResult == 0); + sLog(log_error, "telinit returned error %d\n", rebootComandResult); + + exit (127); + } + } + + return deployStatus; +} + +/* + * Extract all files into the destination folder + */ +Bool +ExtractCabPackage(const char* cabFileName, + const char* destDir) +{ + unsigned int error; + + sLog(log_info, "Extracting package files. \n"); + + // Set log function + MspackWrapper_SetLogger(sLog); + + // Self check library compatibility + if ((error = SelfTestMspack()) != LINUXCAB_SUCCESS) { + SetDeployError("mspack self test failed. (%s)", GetLinuxCabErrorMsg(error)); + return FALSE; + } + + // check if cab file is set + if (!cabFileName) { + SetDeployError("Cab file not set."); + return FALSE; + } + + // uncab the cabinet file + if ((error = ExpandAllFilesInCab(cabFileName, destDir)) != LINUXCAB_SUCCESS) { + SetDeployError("Error expanding cabinet. (%s)", GetLinuxCabErrorMsg(error)); + return FALSE; + } + return TRUE; +} + +/* + * Extract all files into the destination folder + */ +static Bool +ExtractZipPackage(const char* pkgName, + const char* destDir) +{ + ProcessHandle h; + char* args[32]; + const char* stderr; + + int pkgFd, zipFd; + char zipName[1024]; + char copyBuf[4096]; + ssize_t rdCount; + char* destCopy; + + Bool ret = TRUE; + + // strip the header from the file + snprintf(zipName, sizeof zipName, "%s/%x", destDir, (unsigned int)time(0)); + zipName[(sizeof zipName) - 1] = '\0'; + if ((pkgFd = open(pkgName, O_RDONLY)) < 0) { + sLog(log_error, "Failed to open package file %s for read: %s", pkgName, + strerror(errno)); + return FALSE; + } + if ((zipFd = open(zipName, O_CREAT | O_WRONLY | O_TRUNC, 0700)) < 0) { + sLog(log_error, "Failed to create temporary zip file %s: %s", zipName, + strerror(errno)); + close(pkgFd); + return FALSE;; + } + lseek(pkgFd, sizeof(VMwareDeployPkgHdr), 0); + while((rdCount = read(pkgFd, copyBuf, sizeof copyBuf)) > 0) { + if (write(zipFd, copyBuf, rdCount) < 0) { + sLog(log_warning, "write() failed"); + } + } + + close(pkgFd); + close(zipFd); + + destCopy = strdup(destDir); // destDir is const + args[0] = "/usr/bin/unzip"; + args[1] = "-o"; + args[2] = zipName; + args[3] = "-d"; + args[4] = destCopy; + args[5] = NULL; + Process_Create(&h, args, sLog); + free(destCopy); + Process_RunToComplete(h, 100); + + sLog(log_info, "unzip output: %s\n", Process_GetStdout(h)); + + // Assume zip failed if it wrote to stderr + stderr = Process_GetStderr(h); + if (strlen(stderr) > 0) { + sLog(log_error, "Package unzip failed: %s\n", stderr); + ret = FALSE; + } + + Process_Destroy(h); + + return ret; +} + +//...................................................................................... + +/** + * + * Coverts the string into array of "C" string with NULL as the last + * element in the array. This is the way an "exec" in Linux expects its + * parameters. + * + * @param [IN] command Command to execute + * @return 2-Dimensional array of "C" string or NULL on error + * + **/ +static char** +GetFormattedCommandLine(const char* command) +{ + // tokenize it into program that is needed to be executed and arguments for it + struct List* commandTokens = NULL; + + char token[strlen(command) + 1]; + unsigned int wToken = 0; // write head for token + + char** args; + struct List* l; + + unsigned int i; + for (i = 0; i < strlen(command); ++i) { + if (command[i] == BACKSLASH) {// if backslash skip next char + token[wToken++] = command[i++]; + if (i < (strlen(command) - 1)) { + token[wToken++] = command[i++]; + } + continue; + } else if (command[i] == QUOTECHAR) {// if quote skip till next quote + token[wToken++] = QUOTECHAR; + for (++i; command[i] && (command[i] != QUOTECHAR); ++i) { + token[wToken++] = command[i]; + } + token[wToken++] = QUOTECHAR; + continue; + } else if (command[i] == SPACECHAR || command[i] == TABCHAR) {// tab or space + token[wToken++] = 0; + commandTokens = AddToList(commandTokens, token); + + memset(token, 0, strlen(command)); + wToken = 0; + + // seek to the next char position that is not a space or a tab + for (;command[i] != SPACECHAR && command[i] != TABCHAR; ++i); + } else {// add it to token + token[wToken++] = command[i]; + } + } + + // last token -- check and insert + /* if (token) { */ + commandTokens = AddToList(commandTokens, token); + /* } */ + + // prefixing the start path for the commands + args = malloc((ListSize(commandTokens) + 1) * sizeof(char*)); + if (!args) { + SetDeployError("Error allocating memory."); + return NULL; + } + + for(l = commandTokens, i = 0; l; l = l->next, i++) { + char* arg = malloc(strlen(l->data) + 1); + if (!arg) { + SetDeployError("Error allocating memory.(%s)", strerror(errno)); + return NULL; + } + + strcpy (arg, l->data); + args[i] = arg; + +#ifdef VMX86_DEBUG + sLog(log_debug, "Arg (address & value) : %p %s \n", args[i], args[i]); +#endif + } + + // marks the end of params + args[ListSize(commandTokens)] = NULL; + + // clear resources + DeleteList(commandTokens); + + return args; +} + +//...................................................................................... + +/** + * + * Fork off the command and wait for it to finish. Classical Linux/Unix + * fork-and-exec. + * + * @param [IN] command Command to execute + * @return Return code from the process (or DEPLOY_ERROR) + * + **/ +static int +ForkExecAndWaitCommand(const char* command) +{ + ProcessHandle hp; + int retval; + int i; + char** args = GetFormattedCommandLine(command); + + sLog(log_debug, "Command to exec : %s \n", args[0]); + Process_Create(&hp, args, sLog); + + // Free args array as Process_Create has its own private copy now. + for (i = 0; args[i] != NULL; i++) { + free(args[i]); + } + free(args); + + Process_RunToComplete(hp, 100); + sLog(log_info, "Customization command output: %s\n", Process_GetStdout(hp)); + + if(Process_GetExitCode(hp) == 0 && strlen(Process_GetStderr(hp)) > 0) { + // Assume command failed if it wrote to stderr, even if exitCode is 0 + sLog(log_error, "Customization command failed: %s\n", Process_GetStderr(hp)); + retval = -1; + } else { + retval = Process_GetExitCode(hp); + } + Process_Destroy(hp); + return retval; +} + +/** + * Sets up the path for exracting file. For e.g. if the file is /a/b/c/d.abc + * then it creates /a/b/c (skips if any of the directories along the path + * exists). If the the path ends in '/', then the the entire input is assumed + * to be a directory and is created as such. + * + * @param path IN: Complete path of the file + * @return TRUE on success + */ + +Bool +CreateDir(const char* path) +{ + struct stat stats; + char* token; + char* copy; + + // make a copy we can modify + copy = strdup(path); + + // walk through the path (it employs in string replacement) + for (token = copy + 1; *token; ++token) { + + // find + if (*token != '/') { + continue; + } + + /* + * cut it off here for e.g. on first iteration /a/b/c/d.abc will have + * token /a, on second /a/b etc + */ + *token = 0; + + sLog(log_debug, "Creating directory %s", copy); + + // ignore if the directory exists + if (!((stat(copy, &stats) == 0) && S_ISDIR(stats.st_mode))) { + // make directory and check error (accessible only by owner) + if (mkdir(copy, 0700) == -1) { + sLog(log_error, "Unable to create directory %s (%s)", copy, + strerror(errno)); + free(copy); + return FALSE; + } + } + + // restore the token + *token = '/'; + } + + free(copy); + return TRUE; +} + +//...................................................................................... + +/** + * + * The only public function in this shared library, and the only + * part of the DeployPkg_ interface implemented in Linux. + * Decodes a package from a file, extracts its payload, + * expands the payload into a temporary directory, and then executes + * the command specified in the package. + * + * @param [IN] file The package file + * @retutns DEPLOY_SUCCESS on success and DEPLOY_ERROR on error + * + **/ +int +DeployPkg_DeployPackageFromFile(const char* file) +{ + int retStatus; + + sLog(log_info, "Initializing deployment module. \n"); + Init(); + + sLog(log_info, "Deploying cabinet file %s. \n", file); + retStatus = Deploy(file); + + if (retStatus != DEPLOY_SUCCESS) { + sLog(log_error, "Deploy error: %s \n", GetDeployError()); + } + + free(gDeployError); + gDeployError = NULL; + + return retStatus; +} |