in managementnode/lib/VCL/Module/OS.pm [2833:2964]
sub create_text_file {
my $self = shift;
if (ref($self) !~ /VCL::Module/i) {
notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
return;
}
my ($file_path, $file_contents_string, $append) = @_;
if (!defined($file_path)) {
notify($ERRORS{'WARNING'}, 0, "file path argument was not supplied");
return;
}
elsif (!defined($file_contents_string)) {
notify($ERRORS{'WARNING'}, 0, "file contents argument was not supplied");
return;
}
my $computer_node_name = $self->data->get_computer_node_name();
my $image_os_type = $self->data->get_image_os_type();
my $newline;
if ($image_os_type =~ /win/i) {
$newline = "\r\n";
}
else {
$newline = "\n";
}
# Used only to format notify messages
my $mode_string;
if ($append) {
$mode_string = 'append';
# Retrieve the contents of the existing file if necessary
if ($self->file_exists($file_path)) {
my $existing_file_contents = $self->get_file_contents($file_path);
if (!defined($existing_file_contents)) {
# Do not proceed if any problem occurred retrieving the existing file contents
# Otherwise, it would be overwritten and data would be lost
notify($ERRORS{'WARNING'}, 0, "failed to $mode_string text file on $computer_node_name, append argument was specified but contents of the existing file could not be retrieved: $file_path");
return;
}
elsif ($existing_file_contents && $existing_file_contents !~ /\n$/) {
# Add a newline to the end of the existing contents if one isn't present
$existing_file_contents .= $newline;
$file_contents_string = $existing_file_contents . $file_contents_string;
}
}
}
else {
$mode_string = 'create';
}
# Make sure the file contents ends with a newline
# This is helpful to prevent problems with files such as sshd_config and sudoers where a line might be echo'd to the end of it
# Without the newline, the last line could wind up being a merged line if a simple echo is used to add a line
if (length($file_contents_string) && $file_contents_string !~ /\n$/) {
$file_contents_string .= $newline;
}
# Make line endings consistent
$file_contents_string =~ s/\r*\n/$newline/g;
# Attempt to create the parent directory if it does not exist
if ($file_path =~ /[\\\/]/ && $self->can('create_directory')) {
my $parent_directory_path = parent_directory_path($file_path);
$self->create_directory($parent_directory_path) if $parent_directory_path;
}
# The echo method will fail of the file contents are too large
# An "Argument list too long" error will be displayed
my $file_contents_length = length($file_contents_string);
if ($file_contents_length < 32000) {
# Create a command to echo the string and another to echo the hex string to the file
# The hex string command is preferred because it will handle special characters including single quotes
# Use -e to enable interpretation of backslash escapes
# Use -n to prevent trailing newline from being added
# However, the command may become very long and fail
# Convert the string to a string containing the hex value of each character
# This is done to avoid problems with special characters in the file contents
# Split the string up into an array if integers representing each character's ASCII decimal value
my @decimal_values = unpack("C*", $file_contents_string);
# Convert the ASCII decimal values into hex values and add '\x' before each hex value
my @hex_values = map { '\x' . sprintf("%x", $_) } @decimal_values;
# Join the hex values together into a string
my $hex_string = join('', @hex_values);
# Enclose the file path in quotes if it contains a space
if ($file_path =~ /[\s]/) {
$file_path = "\"$file_path\"";
}
# Attempt to create the file using the hex string
my $command = "echo -n -e \"$hex_string\" > $file_path";
my ($exit_status, $output) = $self->execute($command, 0, 15, 1);
if (!defined($output)) {
notify($ERRORS{'DEBUG'}, 0, "failed to execute command to $mode_string file on $computer_node_name, attempting to create file on management node and copy it to $computer_node_name");
}
elsif ($exit_status != 0 || grep(/^\w+:/i, @$output)) {
notify($ERRORS{'WARNING'}, 0, "failed to execute command to $mode_string a file on $computer_node_name, command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output) . "\nattempting to create file on management node and copy it to $computer_node_name");
}
elsif ($append) {
notify($ERRORS{'DEBUG'}, 0, $mode_string . ($append ? 'ed' : 'd') . " text file on $computer_node_name: $file_path");
return 1;
}
else {
notify($ERRORS{'DEBUG'}, 0, $mode_string . ($append ? 'ed' : 'd') . " text file on $computer_node_name: $file_path");
return 1;
}
}
else {
notify($ERRORS{'DEBUG'}, 0, "skipping attempt to $mode_string on $computer_node_name using echo, file content string length: $file_contents_length");
}
# File was not created using the quicker echo method
# Create a temporary file on the management node, copy it to the computer, then delete temporary file from management node
my $mn_temp_file_path = tmpnam();
if (!$self->mn_os->create_text_file($mn_temp_file_path, $file_contents_string, $append)) {
notify($ERRORS{'WARNING'}, 0, "failed to create text file on $computer_node_name, temporary file could not be created on the management node");
return;
}
if (!$self->copy_file_to($mn_temp_file_path, $file_path)) {
notify($ERRORS{'WARNING'}, 0, "failed to create text file, temporary file could not be dopied from management node to $computer_node_name: $mn_temp_file_path --> $file_path");
return;
}
$self->mn_os->delete_file($mn_temp_file_path);
notify($ERRORS{'DEBUG'}, 0, "created text file on $computer_node_name by copying a file created on management node");
return 1;
}