//
// aegis - project change supervisor
// Copyright (C) 1999-2008 Peter Miller
// Copyright (C) 2007, 2008 Walter Franzini
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any 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
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// .
//
#include // for assert
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static void
pconf_improve(change::pointer cp, bool required)
{
pconf_ty *d;
sub_context_ty *scp;
size_t j;
d = cp->pconf_data;
if (required && !d->build_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "build_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (!d->development_build_command && d->build_command)
d->development_build_command = str_copy(d->build_command);
// This field is obsolete.
if (d->mask & pconf_create_symlinks_before_build_mask)
{
if (cp->is_being_developed())
{
change_warning_obsolete_field
(
cp,
d->errpos,
"create_symlinks_before_build",
"development_directory_style.source_file_symlink"
);
}
}
// This field is obsolete.
if (d->mask & pconf_remove_symlinks_after_build_mask)
{
if (cp->is_being_developed())
{
change_warning_obsolete_field
(
cp,
d->errpos,
"remove_symlinks_after_build",
"development_directory_style.during_build_only"
);
}
}
// This field is obsolete.
if (d->mask & pconf_create_symlinks_before_integration_build_mask)
{
if (cp->is_being_developed())
{
change_warning_obsolete_field
(
cp,
d->errpos,
"create_symlinks_before_integration_build",
"integration_directory_style.source_file_symlink"
);
}
}
else
{
d->create_symlinks_before_integration_build =
d->create_symlinks_before_build;
if (d->mask & pconf_create_symlinks_before_build_mask)
d->mask |= pconf_create_symlinks_before_integration_build_mask;
}
// This field is obsolete.
if (d->mask & pconf_remove_symlinks_after_integration_build_mask)
{
if (cp->is_being_developed())
{
change_warning_obsolete_field
(
cp,
d->errpos,
"remove_symlinks_after_integration_build",
"integration_directory_style.during_build_only"
);
}
}
else
{
//
// Integration builds always remove the symlinks
// again, even if they are kept around in the
// development directories. This stops them
// becoming stale if there are deeper baseline
// integrations.
//
d->remove_symlinks_after_integration_build = true;
}
if (required && !d->history_get_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "history_get_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (!d->history_put_command && d->history_create_command)
d->history_put_command = str_copy(d->history_create_command);
if (required && !d->history_put_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "history_put_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (required && !d->history_query_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "history_query_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (required && !d->diff_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "diff_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (required && !d->diff3_command && !d->merge_command)
{
assert(d->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", d->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "merge_command");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (!(d->mask & pconf_shell_safe_filenames_mask))
{
d->shell_safe_filenames = true;
d->mask |= pconf_shell_safe_filenames_mask;
}
if (d->file_template)
{
for (j = 0; j < d->file_template->length; ++j)
{
pconf_file_template_ty *tp;
tp = d->file_template->list[j];
if (!tp->pattern || !tp->pattern->length)
{
assert(tp->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", tp->errpos);
sub_var_set_charstar
(
scp,
"FieLD_Name",
"file_template.pattern"
);
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (!tp->body && !tp->body_command)
{
assert(tp->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", tp->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "file_template.body");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (tp->body && tp->body_command)
{
assert(tp->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", tp->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "file_template.body");
change_fatal
(
cp,
scp,
i18n("$filename: corrupted \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
}
}
attributes_list_verify(d->project_specific);
if (d->change_file_command)
{
if (!d->change_file_undo_command)
d->change_file_undo_command = str_copy(d->change_file_command);
if (!d->new_file_command)
d->new_file_command = str_copy(d->change_file_command);
if (!d->new_test_command)
d->new_test_command = str_copy(d->change_file_command);
if (!d->copy_file_command)
d->copy_file_command = str_copy(d->change_file_command);
if (!d->remove_file_command)
d->remove_file_command = str_copy(d->change_file_command);
}
if (d->change_file_undo_command)
{
if (!d->new_file_undo_command)
d->new_file_undo_command = str_copy(d->change_file_undo_command);
if (!d->new_test_undo_command)
d->new_test_undo_command = str_copy(d->change_file_undo_command);
if (!d->copy_file_undo_command)
d->copy_file_undo_command = str_copy(d->change_file_undo_command);
if (!d->remove_file_undo_command)
d->remove_file_undo_command = str_copy(d->change_file_undo_command);
}
// See also pconf_improve_more(), below.
}
static void
set_pconf_symlink_exceptions_defaults(pconf_ty *pconf_data)
{
meta_type *type_p = 0;
string_ty **str_p;
//
// make sure the list is there
//
if (!pconf_data->symlink_exceptions)
{
pconf_data->symlink_exceptions =
(pconf_symlink_exceptions_list_ty *)
pconf_symlink_exceptions_list_type.alloc();
}
//
// append the logfile to the list
//
str_p =
(string_ty **)
pconf_symlink_exceptions_list_type.list_parse
(
pconf_data->symlink_exceptions,
&type_p
);
assert(type_p == &string_type);
*str_p = str_copy(change_logfile_basename());
}
static input
input_catenate_tricky(string_list_ty *filename)
{
assert(filename->nstrings >= 1);
input *fpl = new input[filename->nstrings];
for (size_t j = 0; j < filename->nstrings; ++j)
{
input temp = input_file_open(filename->string[j]);
fpl[j] = new input_crlf(temp);
}
input fp = new input_catenate(fpl, filename->nstrings, 1);
delete [] fpl;
return fp;
}
static int
candidate(fstate_src_ty *src)
{
if (src->about_to_be_created_by)
return 0;
if (src->deleted_by)
return 0;
if (src->about_to_be_copied_by)
return 0;
switch (src->usage)
{
case file_usage_source:
case file_usage_config:
switch (src->action)
{
case file_action_create:
case file_action_modify:
case file_action_insulate:
case file_action_transparent:
return 1;
case file_action_remove:
break;
}
break;
case file_usage_build:
case file_usage_test:
case file_usage_manual_test:
break;
}
return 0;
}
static pconf_ty *
pconf_read_by_usage_completed(change::pointer cp)
{
trace(("pconf_read_by_usage_completed(cp = %08lX)\n{\n", (long)cp));
//
// Build the list of files.
//
// We use a symbol table so that we get O(n) behaviour.
// (The string_list_append_list_unique is O(n**2), oops.)
//
string_list_ty filename;
//
// Use project file list.
//
// Two reasons:
// 1. avoid a catch-22 of needing the pconf to know the history
// commands needed to load the pconf to know the history
// commands needed to ... etc.
// 2. it can be very slow.
//
// We may need to revisit this in future when we have change
// (branch) delta pinning working.
//
project_ty *pp = cp->pp;
while (!pp->is_a_trunk() && pp->change_get()->is_completed())
pp = pp->parent_get();
for (size_t j = 0;; ++j)
{
fstate_src_ty *src = pp->file_nth(j, view_path_extreme);
if (!src)
break;
if (src->usage != file_usage_config)
continue;
if (!candidate(src))
continue;
fstate_src_trace(src);
string_ty *s = project_file_path(pp, src);
assert(s);
//
// Add the filename to the list.
//
filename.push_back(s);
str_free(s);
}
//
// If there are no candidate files,
// look for files with the right name
// (this is the way Aegis used to do it).
//
if (filename.nstrings < 1)
{
string_ty *s = str_from_c(THE_CONFIG_FILE_NEW);
fstate_src_ty *src = project_file_find(pp, s, view_path_extreme);
str_free(s);
if (!src)
{
s = str_from_c(THE_CONFIG_FILE_OLD);
src = project_file_find(pp, s, view_path_extreme);
str_free(s);
}
if (src)
{
s = project_file_path(pp, src);
assert(s);
filename.push_back(s);
str_free(s);
//
// This is a hack, but it makes transition from the old
// method to the new method easier. We change the file have
// "config" usage. At some point the file state will be
// written out again, and the file will thus be quietly
// converted to have "config" usage.
//
src->usage = file_usage_config;
}
}
//
// If there are no candidate files,
// return a NULL pointer.
//
if (filename.empty())
{
trace(("return NULL;\n}\n"));
return 0;
}
//
// Read the configuration information.
//
project_become(pp);
input ifp = input_catenate_tricky(&filename);
pconf_ty *result = (pconf_ty *)parse_input(ifp, &pconf_type);
ifp.close();
project_become_undo(pp);
//
// If there is a configuration directory specified, then all the
// source files in that directory are also config files. We need
// to extend the list and read the catenate config again.
//
// But... aenf usually marks these files as config files, so there
// is a very good chance that this loop will be very fast and find
// nothing else to add to the file list, in which case we *dont't*
// re-read the project config.
//
string_ty *dir = result->configuration_directory;
if (dir)
{
bool more = false;
for (size_t j = 0;; ++j)
{
fstate_src_ty *src = pp->file_nth(j, view_path_extreme);
if (!src)
break;
if (!os_isa_path_prefix(dir, src->file_name))
continue;
if (src->usage == file_usage_config)
continue;
if (!candidate(src))
continue;
string_ty *s = project_file_path(pp, src);
assert(s);
filename.push_back(s);
str_free(s);
more = true;
//
// This is a hack, but it makes transition from the old
// method to the new method easier. We change the file have
// "config" usage. At some point the file state will be
// written out again, and the file will thus be quietly
// converted to have "config" usage.
//
src->usage = file_usage_config;
}
//
// Only re-read of the list of files has been extended.
//
if (more)
{
pconf_type.free((void *)result);
project_become(pp);
ifp = input_catenate_tricky(&filename);
result = (pconf_ty *)parse_input(ifp, &pconf_type);
// as a side-effect, parse_input will delete ifp
project_become_undo(pp);
}
}
//
// Report success.
//
trace(("return %08lX;\n}\n", (long)result));
return result;
}
static pconf_ty *
pconf_read_by_usage(change::pointer cp)
{
if (cp->bogus || cp->is_completed())
return pconf_read_by_usage_completed(cp);
trace(("pconf_read_by_usage(cp = %08lX)\n{\n", (long)cp));
//
// Build the list of files.
//
// We use a symbol table so that we get O(n) behaviour.
// (The string_list_append_list_unique is O(n**2), oops.)
//
string_list_ty filename;
trace(("NOT completed\n"));
for (size_t j = 0;; ++j)
{
fstate_src_ty *src = change_file_nth(cp, j, view_path_extreme);
if (!src)
break;
if (src->usage != file_usage_config)
continue;
if (!candidate(src))
continue;
fstate_src_trace(src);
//
// It may be necessary to get the file out of history when we
// are dealing with an historical change set, and the particular
// file's version is not in a baseline any longer, and must be
// extracted from history.
//
int need_to_unlink = 0;
string_ty *s = change_file_version_path(cp, src, &need_to_unlink);
assert(s);
if (need_to_unlink)
{
//
// Remember to remove the temporary file on exit.
//
nstring path(s);
os_become_orig();
quit_action *qap = new quit_action_unlink(path);
os_become_undo();
quit_register(*qap);
}
//
// Add the filename to the list.
//
filename.push_back(s);
str_free(s);
}
//
// If there are no candidate files,
// look for files with the right name
// (this is the way Aegis used to do it).
//
if (filename.nstrings < 1)
{
string_ty *s = str_from_c(THE_CONFIG_FILE_NEW);
fstate_src_ty *src = change_file_find(cp, s, view_path_extreme);
str_free(s);
if (!src)
{
s = str_from_c(THE_CONFIG_FILE_OLD);
src = change_file_find(cp, s, view_path_extreme);
str_free(s);
}
if (src)
{
s = change_file_path(cp, src);
if (!s)
s = project_file_path(cp->pp, src);
assert(s);
filename.push_back(s);
str_free(s);
//
// This is a hack, but it makes transition from the old
// method to the new method easier. We change the file have
// "config" usage. At some point the file state will be
// written out again, and the file will thus be quietly
// converted to have "config" usage.
//
src->usage = file_usage_config;
}
}
//
// If there are no candidate files,
// return a NULL pointer.
//
if (filename.empty())
{
trace(("return NULL;\n}\n"));
return 0;
}
//
// Read the configuration information.
//
change_become(cp);
input ifp = input_catenate_tricky(&filename);
pconf_ty *result = (pconf_ty *)parse_input(ifp, &pconf_type);
ifp.close();
change_become_undo(cp);
//
// If there is a configuration directory specified, then all the
// source files in that directory are also config files. We need
// to extend the list and read the catenate config again.
//
// But... aenf usually marks these files as config files, so there
// is a very good chance that this loop will be very fast and find
// nothing else to add to the file list, in which case we *dont't*
// re-read the project config.
//
string_ty *dir = result->configuration_directory;
if (dir)
{
bool more = false;
//
// We don't use the change_file_nth function because we can
// exclude the majority of files using only the path name,
// without the need to look at the fstate content.
//
const string_list_ty *flp =
change_file_list_get(cp, view_path_extreme);
for (size_t k = 0; k < flp->size(); ++k)
{
string_ty *file_name = flp->operator[](k);
if (!os_isa_path_prefix(dir, file_name))
continue;
fstate_src_ty *src =
change_file_find(cp, file_name, view_path_extreme);
if (!src)
break;
if (src->usage == file_usage_config)
continue;
if (!candidate(src))
continue;
string_ty *s = change_file_path(cp, src);
if (!s)
s = project_file_path(cp->pp, src);
assert(s);
filename.push_back(s);
str_free(s);
more = true;
//
// This is a hack, but it makes transition from the old
// method to the new method easier. We change the file have
// "config" usage. At some point the file state will be
// written out again, and the file will thus be quietly
// converted to have "config" usage.
//
src->usage = file_usage_config;
}
//
// Only re-read of the list of files has been extended.
//
if (more)
{
pconf_type.free((void *)result);
change_become(cp);
ifp = input_catenate_tricky(&filename);
result = (pconf_ty *)parse_input(ifp, &pconf_type);
// as a side-effect, parse_input will delete ifp
change_become_undo(cp);
}
}
//
// Report success.
//
trace(("return %08lX;\n}\n", (long)result));
return result;
}
static void
pconf_improve_more(change::pointer cp)
{
size_t j;
static string_ty *star_comma_d;
//
// set the architecture default
//
pconf_ty *d = cp->pconf_data;
assert(d);
if (!d->architecture)
{
d->architecture =
(pconf_architecture_list_ty *)
pconf_architecture_list_type.alloc();
}
if (!d->architecture->length)
{
meta_type *type_p = 0;
pconf_architecture_ty **app;
pconf_architecture_ty *ap;
app =
(pconf_architecture_ty **)
pconf_architecture_list_type.list_parse
(
d->architecture,
&type_p
);
assert(type_p == &pconf_architecture_type);
ap = (pconf_architecture_ty *)pconf_architecture_type.alloc();
*app = ap;
ap->name = str_from_c("unspecified");
ap->pattern = str_from_c("*");
}
for (j = 0; j < d->architecture->length; ++j)
{
pconf_architecture_ty *ap;
ap = d->architecture->list[j];
if (!ap->name || !ap->pattern)
{
sub_context_ty *scp;
assert(ap->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", ap->errpos);
change_fatal
(
cp,
scp,
i18n("$filename: arch needs name and pattern")
);
// NOTREACHED
sub_context_delete(scp);
}
//
// Make sure the report generator can see it.
//
ap->mask |= pconf_architecture_mode_mask;
}
//
// set the maximum_filename_length default
//
if (d->mask & pconf_maximum_filename_length_mask)
{
if (d->maximum_filename_length < 9)
d->maximum_filename_length = 9;
if (d->maximum_filename_length > 255)
d->maximum_filename_length = 255;
}
else
d->maximum_filename_length = 255;
//
// set the filename_pattern_accept default
//
if (!d->filename_pattern_accept)
{
d->filename_pattern_accept =
(pconf_filename_pattern_accept_list_ty *)
pconf_filename_pattern_accept_list_type.alloc();
}
if (!d->filename_pattern_accept->length)
{
meta_type *type_p = 0;
string_ty **addr_p;
addr_p =
(string_ty **)
pconf_filename_pattern_accept_list_type.list_parse
(
d->filename_pattern_accept,
&type_p
);
assert(type_p == &string_type);
*addr_p = str_from_c("*");
}
if (!star_comma_d)
star_comma_d = str_from_c("*,D");
if (!d->filename_pattern_reject)
{
d->filename_pattern_reject =
(pconf_filename_pattern_reject_list_ty *)
pconf_filename_pattern_reject_list_type.alloc();
}
for (j = 0; j < d->filename_pattern_reject->length; ++j)
{
if
(
str_equal
(
star_comma_d,
d->filename_pattern_reject->list[j]
)
)
break;
}
if (j >= d->filename_pattern_reject->length)
{
meta_type *type_p = 0;
string_ty **addr_p;
addr_p =
(string_ty **)
pconf_filename_pattern_reject_list_type.list_parse
(
d->filename_pattern_reject,
&type_p
);
assert(type_p == &string_type);
*addr_p = str_copy(star_comma_d);
}
//
// make sure symlink_exceptions is there, even if empty
//
set_pconf_symlink_exceptions_defaults(d);
//
// set the test_command default
//
if (!d->test_command)
d->test_command = str_from_c("$shell $file_name");
if (!d->development_test_command)
d->development_test_command =
str_copy(d->test_command);
//
// set the development directory template default
//
if (!d->development_directory_template)
{
d->development_directory_template =
str_from_c
(
"$ddd/${left $project ${expr ${namemax $ddd} - ${length "
".$magic${zpad $c 3}}}}.$magic${zpad $c 3}"
);
}
//
// Set the test filename template
//
if (!d->new_test_filename)
{
d->new_test_filename =
str_from_c
(
"test/${zpad $hundred 2}/t${zpad $number 4}${left $type 1}.sh"
);
}
//
// Make sure the report generator can see the enums.
//
d->mask |=
pconf_history_put_trashes_file_mask |
pconf_history_content_limitation_mask
;
//
// Default settings for the work area styles.
//
if (!d->development_directory_style)
{
d->development_directory_style =
(work_area_style_ty *)work_area_style_type.alloc();
d->development_directory_style->source_file_link = false;
d->development_directory_style->source_file_symlink =
d->create_symlinks_before_build;
d->development_directory_style->source_file_copy = false;
d->development_directory_style->derived_file_link = false;
d->development_directory_style->derived_file_symlink =
d->create_symlinks_before_build;
d->development_directory_style->derived_file_copy = false;
d->development_directory_style->during_build_only =
d->remove_symlinks_after_build;
if (d->mask & pconf_create_symlinks_before_build_mask)
{
d->development_directory_style->mask |=
work_area_style_source_file_symlink_mask |
work_area_style_derived_file_symlink_mask
;
}
if (d->mask & pconf_remove_symlinks_after_build_mask)
{
d->development_directory_style->mask |=
work_area_style_during_build_only_mask
;
}
}
if
(
!(
d->development_directory_style->mask
&
work_area_style_source_file_whiteout_mask
)
)
{
//
// You only need whiteout files if you don't have some kind of
// mirror in the development directory.
//
d->development_directory_style->source_file_whiteout =
!(
d->development_directory_style->source_file_link
||
d->development_directory_style->source_file_symlink
||
d->development_directory_style->source_file_copy
);
d->development_directory_style->mask |=
work_area_style_source_file_whiteout_mask;
}
if (!d->integration_directory_style)
{
//
// We want the integration directory to behave the same as the
// development directory.
//
d->integration_directory_style =
work_area_style_copy(d->development_directory_style);
//
// Unless they have explicity used obsolete fields to change
// things around.
//
if (d->mask & pconf_create_symlinks_before_integration_build_mask)
{
d->integration_directory_style->source_file_symlink =
d->create_symlinks_before_integration_build;
d->integration_directory_style->derived_file_symlink =
d->create_symlinks_before_integration_build;
}
if (d->mask & pconf_remove_symlinks_after_integration_build_mask)
{
d->integration_directory_style->during_build_only =
d->remove_symlinks_after_integration_build;
}
}
if
(
!(
d->integration_directory_style->mask
&
work_area_style_source_file_whiteout_mask
)
)
{
//
// Omit whiteout from the integration directory. The developer
// is supposed to have removed all sign of it already.
//
d->integration_directory_style->source_file_whiteout = false;
d->integration_directory_style->mask |=
work_area_style_source_file_whiteout_mask;
}
if (!(d->mask & pconf_cache_project_file_list_for_each_delta_mask))
{
d->cache_project_file_list_for_each_delta = true;
d->mask |= pconf_cache_project_file_list_for_each_delta_mask;
}
//
// Default the history_create_command if necessary.
//
if (!d->history_create_command && d->history_put_command)
d->history_create_command = str_copy(d->history_put_command);
}
pconf_ty *
change_pconf_get(change::pointer cp, int required)
{
trace(("change_pconf_get(cp = %8.8lX, required = %d)\n{\n", (long)cp,
required));
assert(cp->reference_count >= 1);
change_lock_sync(cp);
if (!cp->pconf_data)
{
cp->pconf_data = pconf_read_by_usage(cp);
if (!cp->pconf_data)
{
if (required)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_charstar(scp, "File_Name", THE_CONFIG_FILE_NEW);
change_fatal
(
cp,
scp,
i18n("you must create a \"$filename\" file")
);
// NOTREACHED
sub_context_delete(scp);
}
cp->pconf_data = (pconf_ty *)pconf_type.alloc();
cp->pconf_data->errpos = str_from_c(THE_CONFIG_FILE_NEW);
cp->pconf_data->shell_safe_filenames = true;
}
pconf_improve_more(cp);
}
pconf_improve(cp, required);
trace(("return %08lX;\n", (long)cp->pconf_data));
trace(("}\n"));
return cp->pconf_data;
}