blob: 47a66af3b0063e33abb50ec26ab18a809ef66b06 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include "Charmonizer/Core/Compiler.h"
#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Core/ConfWriter.h"
#include "Charmonizer/Core/OperatingSystem.h"
#define CHAZ_OS_TARGET_PATH "_charmonizer_target"
#define CHAZ_OS_NAME_MAX 31
static struct {
char name[CHAZ_OS_NAME_MAX+1];
char dev_null[20];
char dir_sep[2];
char local_command_start[3];
int shell_type;
int run_sh_via_cmd_exe;
} chaz_OS = { "", "", "", "", 0, 0 };
static int
chaz_OS_run_sh_via_cmd_exe(const char *command);
void
chaz_OS_init(void) {
char *output;
size_t output_len;
if (chaz_Util_verbosity) {
printf("Initializing Charmonizer/Core/OperatingSystem...\n");
}
/* Detect shell based on escape character. */
/* Needed to make redirection work. */
chaz_OS.shell_type = CHAZ_OS_POSIX;
output = chaz_OS_run_and_capture("echo foo\\^bar", &output_len);
if (output_len >= 7 && memcmp(output, "foo\\bar", 7) == 0) {
/* Escape character is caret. */
if (chaz_Util_verbosity) {
printf("Detected cmd.exe shell\n");
}
/* Try to see whether running commands via the `sh` command works.
* Run the `find` command to check whether we're in a somewhat POSIX
* compatible environment. */
free(output);
chaz_OS.run_sh_via_cmd_exe = 1;
output = chaz_OS_run_and_capture("find . -prune", &output_len);
if (output_len >= 2
&& output[0] == '.'
&& isspace((unsigned char)output[1])
) {
if (chaz_Util_verbosity) {
printf("Detected POSIX shell via cmd.exe\n");
}
chaz_OS.shell_type = CHAZ_OS_POSIX;
}
else {
chaz_OS.shell_type = CHAZ_OS_CMD_EXE;
chaz_OS.run_sh_via_cmd_exe = 0;
}
}
else if (output_len >= 7 && memcmp(output, "foo^bar", 7) == 0) {
/* Escape character is backslash. */
if (chaz_Util_verbosity) {
printf("Detected POSIX shell\n");
}
chaz_OS.shell_type = CHAZ_OS_POSIX;
}
if (chaz_OS.shell_type == CHAZ_OS_CMD_EXE) {
strcpy(chaz_OS.dir_sep, "\\");
strcpy(chaz_OS.dev_null, "nul");
/* Empty string should work, too. */
strcpy(chaz_OS.local_command_start, ".\\");
}
else if (chaz_OS.shell_type == CHAZ_OS_POSIX) {
strcpy(chaz_OS.dir_sep, "/");
strcpy(chaz_OS.dev_null, "/dev/null");
strcpy(chaz_OS.local_command_start, "./");
}
else {
chaz_Util_die("Couldn't identify shell");
}
free(output);
}
const char*
chaz_OS_dev_null(void) {
return chaz_OS.dev_null;
}
const char*
chaz_OS_dir_sep(void) {
return chaz_OS.dir_sep;
}
int
chaz_OS_shell_type(void) {
return chaz_OS.shell_type;
}
int
chaz_OS_remove(const char *name) {
/*
* On Windows it can happen that another process, typically a
* virus scanner, still has an open handle on the file. This can
* make the subsequent recreation of a file with the same name
* fail. As a workaround, files are renamed to a random name
* before deletion.
*/
int retval = 0;
static const size_t num_random_chars = 16;
size_t name_len = strlen(name);
size_t i;
char *temp_name = (char*)malloc(name_len + num_random_chars + 1);
const char *working_name = name;
clock_t start, now;
strcpy(temp_name, name);
for (i = 0; i < num_random_chars; i++) {
temp_name[name_len+i] = 'A' + rand() % 26;
}
temp_name[name_len+num_random_chars] = '\0';
/* Try over and over again for around 1 second to rename the file.
* Ideally we would sleep between attempts, but sleep functionality is not
* portable. */
start = now = clock();
while (now - start < CLOCKS_PER_SEC) {
now = clock();
if (!rename(name, temp_name)) {
/* The rename succeeded. */
working_name = temp_name;
break;
}
else if (errno == ENOENT) {
/* No such file or directory, so no point in trying to remove it.
* (Technically ENOENT is POSIX but hopefully this works.) */
free(temp_name);
return 0;
}
}
/* Try over and over again for around 1 second to delete the file. */
start = now = clock();
while (!retval && now - start < CLOCKS_PER_SEC) {
now = clock();
retval = !remove(working_name);
}
free(temp_name);
return retval;
}
int
chaz_OS_run_local_redirected(const char *command, const char *path) {
char *local_command
= chaz_Util_join("", chaz_OS.local_command_start, command, NULL);
int retval = chaz_OS_run_redirected(local_command, path);
free(local_command);
return retval;
}
int
chaz_OS_run_quietly(const char *command) {
return chaz_OS_run_redirected(command, chaz_OS.dev_null);
}
int
chaz_OS_run_redirected(const char *command, const char *path) {
int retval = 1;
char *quiet_command = NULL;
if (chaz_OS.shell_type == CHAZ_OS_POSIX
|| chaz_OS.shell_type == CHAZ_OS_CMD_EXE
) {
quiet_command = chaz_Util_join(" ", command, ">", path, "2>&1", NULL);
}
else {
chaz_Util_die("Don't know the shell type");
}
if (chaz_OS.run_sh_via_cmd_exe) {
retval = chaz_OS_run_sh_via_cmd_exe(quiet_command);
}
else {
retval = system(quiet_command);
}
free(quiet_command);
return retval;
}
static int
chaz_OS_run_sh_via_cmd_exe(const char *command) {
size_t i;
size_t size;
char *sh_command;
char *p;
int retval;
/* Compute size. */
size = sizeof("sh -c \"\"");
for (i = 0; command[i] != '\0'; i++) {
char c = command[i];
switch (c) {
case '"':
case '\\':
size += 2;
break;
case '%':
case '!':
size += 3;
break;
default:
size += 1;
break;
}
}
/* Build sh command. */
sh_command = (char*)malloc(size);
p = sh_command;
memcpy(p, "sh -c \"", 7);
p += 7;
/* Escape special characters. */
for (i = 0; command[i] != '\0'; i++) {
char c = command[i];
switch (c) {
case '"':
case '\\':
/* Escape double quote and backslash. */
*p++ = '\\';
*p++ = c;
break;
case '%':
case '!':
/* Break out of double quotes for percent sign and
* exclamation mark. This prevents variable expansion. */
*p++ = '"';
*p++ = c;
*p++ = '"';
break;
default:
*p++ = c;
break;
}
}
/* Finish and run sh command. */
*p++ = '"';
*p++ = '\0';
retval = system(sh_command);
free(sh_command);
return retval;
}
char*
chaz_OS_run_and_capture(const char *command, size_t *output_len) {
char *output;
chaz_OS_run_redirected(command, CHAZ_OS_TARGET_PATH);
output = chaz_Util_slurp_file(CHAZ_OS_TARGET_PATH, output_len);
chaz_Util_remove_and_verify(CHAZ_OS_TARGET_PATH);
return output;
}
void
chaz_OS_mkdir(const char *filepath) {
char *command = NULL;
if (chaz_OS.shell_type == CHAZ_OS_POSIX
|| chaz_OS.shell_type == CHAZ_OS_CMD_EXE
) {
command = chaz_Util_join(" ", "mkdir", filepath, NULL);
}
else {
chaz_Util_die("Don't know the shell type");
}
chaz_OS_run_quietly(command);
free(command);
}
void
chaz_OS_rmdir(const char *filepath) {
char *command = NULL;
if (chaz_OS.shell_type == CHAZ_OS_POSIX) {
command = chaz_Util_join(" ", "rmdir", filepath, NULL);
}
else if (chaz_OS.shell_type == CHAZ_OS_CMD_EXE) {
command = chaz_Util_join(" ", "rmdir", "/q", filepath, NULL);
}
else {
chaz_Util_die("Don't know the shell type");
}
chaz_OS_run_quietly(command);
free(command);
}