/* Linker command language support.
Copyright 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
- 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
Free Software Foundation, Inc.
This file is part of the GNU Binutils.
#include "fnmatch.h"
#include "demangle.h"
#include "hashtab.h"
+#include "libbfd.h"
+#ifdef ENABLE_PLUGINS
+#include "plugin.h"
+#endif /* ENABLE_PLUGINS */
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) & (((TYPE*) 0)->MEMBER))
#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free
-static const char *startup_file;
static const char *entry_symbol_default = "start";
static bfd_boolean placed_commons = FALSE;
static bfd_boolean stripped_excluded_sections = FALSE;
struct bfd_sym_chain entry_symbol = { NULL, NULL };
const char *entry_section = ".text";
bfd_boolean entry_from_cmdline;
+bfd_boolean undef_from_cmdline;
bfd_boolean lang_has_input_file = FALSE;
bfd_boolean had_output_filename = FALSE;
bfd_boolean lang_float_flag = FALSE;
return TRUE;
}
+/* Return the numerical value of the init_priority attribute from
+ section name NAME. */
+
+static unsigned long
+get_init_priority (const char *name)
+{
+ char *end;
+ unsigned long init_priority;
+
+ /* GCC uses the following section names for the init_priority
+ attribute with numerical values 101 and 65535 inclusive. A
+ lower value means a higher priority.
+
+ 1: .init_array.NNNN/.fini_array.NNNN: Where NNNN is the
+ decimal numerical value of the init_priority attribute.
+ The order of execution in .init_array is forward and
+ .fini_array is backward.
+ 2: .ctors.NNNN/.ctors.NNNN: Where NNNN is 65535 minus the
+ decimal numerical value of the init_priority attribute.
+ The order of execution in .ctors is backward and .dtors
+ is forward.
+ */
+ if (strncmp (name, ".init_array.", 12) == 0
+ || strncmp (name, ".fini_array.", 12) == 0)
+ {
+ init_priority = strtoul (name + 12, &end, 10);
+ return *end ? 0 : init_priority;
+ }
+ else if (strncmp (name, ".ctors.", 7) == 0
+ || strncmp (name, ".dtors.", 7) == 0)
+ {
+ init_priority = strtoul (name + 7, &end, 10);
+ return *end ? 0 : 65535 - init_priority;
+ }
+
+ return 0;
+}
+
/* Compare sections ASEC and BSEC according to SORT. */
static int
compare_section (sort_type sort, asection *asec, asection *bsec)
{
int ret;
+ unsigned long ainit_priority, binit_priority;
switch (sort)
{
default:
abort ();
+ case by_init_priority:
+ ainit_priority
+ = get_init_priority (bfd_get_section_name (asec->owner, asec));
+ binit_priority
+ = get_init_priority (bfd_get_section_name (bsec->owner, bsec));
+ if (ainit_priority == 0 || binit_priority == 0)
+ goto sort_by_name;
+ ret = ainit_priority - binit_priority;
+ if (ret)
+ break;
+ else
+ goto sort_by_name;
+
case by_alignment_name:
ret = (bfd_section_alignment (bsec->owner, bsec)
- bfd_section_alignment (asec->owner, asec));
/* Fall through. */
case by_name:
+sort_by_name:
ret = strcmp (bfd_get_section_name (asec->owner, asec),
bfd_get_section_name (bsec->owner, bsec));
break;
}
/* lang_for_each_statement walks the parse tree and calls the provided
- function for each node. */
+ function for each node, except those inside output section statements
+ with constraint set to -1. */
-static void
+void
lang_for_each_statement_worker (void (*func) (lang_statement_union_type *),
lang_statement_union_type *s)
{
lang_for_each_statement_worker (func, constructor_list.head);
break;
case lang_output_section_statement_enum:
- lang_for_each_statement_worker
- (func, s->output_section_statement.children.head);
+ if (s->output_section_statement.constraint != -1)
+ lang_for_each_statement_worker
+ (func, s->output_section_statement.children.head);
break;
case lang_wild_statement_enum:
lang_for_each_statement_worker (func,
{
case lang_input_file_is_symbols_only_enum:
p->filename = name;
- p->is_archive = FALSE;
+ p->maybe_archive = FALSE;
p->real = TRUE;
p->local_sym_name = name;
p->just_syms_flag = TRUE;
break;
case lang_input_file_is_fake_enum:
p->filename = name;
- p->is_archive = FALSE;
+ p->maybe_archive = FALSE;
p->real = FALSE;
p->local_sym_name = name;
p->just_syms_flag = FALSE;
p->search_dirs_flag = FALSE;
break;
case lang_input_file_is_l_enum:
- p->is_archive = TRUE;
+ p->maybe_archive = TRUE;
p->filename = name;
p->real = TRUE;
p->local_sym_name = concat ("-l", name, (const char *) NULL);
break;
case lang_input_file_is_marker_enum:
p->filename = name;
- p->is_archive = FALSE;
+ p->maybe_archive = FALSE;
p->real = FALSE;
p->local_sym_name = name;
p->just_syms_flag = FALSE;
case lang_input_file_is_search_file_enum:
p->sysrooted = ldlang_sysrooted_script;
p->filename = name;
- p->is_archive = FALSE;
+ p->maybe_archive = FALSE;
p->real = TRUE;
p->local_sym_name = name;
p->just_syms_flag = FALSE;
break;
case lang_input_file_is_file_enum:
p->filename = name;
- p->is_archive = FALSE;
+ p->maybe_archive = FALSE;
p->real = TRUE;
p->local_sym_name = name;
p->just_syms_flag = FALSE;
already been created. If we are not inside a MEMORY block it is
dubious to use an undeclared region name (except DEFAULT_MEMORY_REGION)
and so we issue a warning.
-
+
Each region has at least one name. The first name is either
DEFAULT_MEMORY_REGION or the name given in the MEMORY block. You can add
alias names to an existing region within a script with
sprintf (symname + (symname[0] != 0), "__start_%s", secname);
e_align = exp_unop (ALIGN_K,
exp_intop ((bfd_vma) 1 << s->alignment_power));
- lang_add_assignment (exp_assop ('=', ".", e_align));
+ lang_add_assignment (exp_assign (".", e_align));
lang_add_assignment (exp_provide (symname,
exp_unop (ABSOLUTE,
exp_nameop (NAME, ".")),
sort_def_symbol (struct bfd_link_hash_entry *hash_entry,
void *info ATTRIBUTE_UNUSED)
{
+ if (hash_entry->type == bfd_link_hash_warning)
+ hash_entry = (struct bfd_link_hash_entry *) hash_entry->u.i.link;
+
if (hash_entry->type == bfd_link_hash_defined
|| hash_entry->type == bfd_link_hash_defweak)
{
to see .text with SEC_LINK_ONCE set. */
if (!link_info.relocatable)
- flags &= ~ (SEC_LINK_ONCE | SEC_LINK_DUPLICATES);
+ flags &= ~(SEC_LINK_ONCE | SEC_LINK_DUPLICATES | SEC_RELOC);
switch (output->sectype)
{
case noload_section:
flags &= ~SEC_LOAD;
flags |= SEC_NEVER_LOAD;
+ /* Unfortunately GNU ld has managed to evolve two different
+ meanings to NOLOAD in scripts. ELF gets a .bss style noload,
+ alloc, no contents section. All others get a noload, noalloc
+ section. */
+ if (bfd_get_flavour (link_info.output_bfd) == bfd_target_elf_flavour)
+ flags &= ~SEC_HAS_CONTENTS;
+ else
+ flags &= ~SEC_ALLOC;
break;
}
lang_input_statement_type *file,
asection *section)
{
- const char *section_name;
lang_statement_union_type *l;
if (!wild->filenames_sorted
&& (sec == NULL || sec->spec.sorted == none))
return NULL;
- section_name = bfd_get_section_name (file->the_bfd, section);
for (l = wild->children.head; l != NULL; l = l->header.next)
{
lang_input_section_type *ls;
for (;;)
{
+ bfd *subsbfd;
member = bfd_openr_next_archived_file (entry->the_bfd, member);
if (member == NULL)
loaded = FALSE;
}
- if (! ((*link_info.callbacks->add_archive_element)
- (&link_info, member, "--whole-archive")))
+ subsbfd = member;
+ if (!(*link_info.callbacks
+ ->add_archive_element) (&link_info, member,
+ "--whole-archive", &subsbfd))
abort ();
- if (! bfd_link_add_symbols (member, &link_info))
+ /* Potentially, the add_archive_element hook may have set a
+ substitute BFD for us. */
+ if (!bfd_link_add_symbols (subsbfd, &link_info))
{
einfo (_("%F%B: could not read symbols: %E\n"), member);
loaded = FALSE;
}
}
break;
+ case lang_assignment_statement_enum:
+ if (s->assignment_statement.exp->assign.hidden)
+ /* This is from a --defsym on the command line. */
+ exp_fold_tree_no_dot (s->assignment_statement.exp);
+ break;
default:
break;
}
#define ldlang_undef_chain_list_head entry_symbol.next
void
-ldlang_add_undef (const char *const name)
+ldlang_add_undef (const char *const name, bfd_boolean cmdline)
{
- ldlang_undef_chain_list_type *new_undef = (ldlang_undef_chain_list_type *)
- stat_alloc (sizeof (ldlang_undef_chain_list_type));
+ ldlang_undef_chain_list_type *new_undef;
+ undef_from_cmdline = undef_from_cmdline || cmdline;
+ new_undef = (ldlang_undef_chain_list_type *) stat_alloc (sizeof (*new_undef));
new_undef->next = ldlang_undef_chain_list_head;
ldlang_undef_chain_list_head = new_undef;
/* Make sure that any sections mentioned in the expression
are initialized. */
exp_init_os (s->data_statement.exp);
- /* The output section gets CONTENTS, and usually ALLOC and
- LOAD, but the latter two may be overridden by the script. */
+ /* The output section gets CONTENTS, ALLOC and LOAD, but
+ these may be overridden by the script. */
flags = SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD;
switch (os->sectype)
{
flags = SEC_HAS_CONTENTS;
break;
case noload_section:
- flags = SEC_HAS_CONTENTS | SEC_NEVER_LOAD;
+ if (bfd_get_flavour (link_info.output_bfd)
+ == bfd_target_elf_flavour)
+ flags = SEC_NEVER_LOAD | SEC_ALLOC;
+ else
+ flags = SEC_NEVER_LOAD | SEC_HAS_CONTENTS;
break;
}
if (os->bfd_section == NULL)
switch (rhs->type.node_class)
{
case etree_binary:
- return scan_for_self_assignment (dst, rhs->binary.lhs)
- || scan_for_self_assignment (dst, rhs->binary.rhs);
+ return (scan_for_self_assignment (dst, rhs->binary.lhs)
+ || scan_for_self_assignment (dst, rhs->binary.rhs));
case etree_trinary:
- return scan_for_self_assignment (dst, rhs->trinary.lhs)
- || scan_for_self_assignment (dst, rhs->trinary.rhs);
+ return (scan_for_self_assignment (dst, rhs->trinary.lhs)
+ || scan_for_self_assignment (dst, rhs->trinary.rhs));
case etree_assign:
case etree_provided:
bfd_boolean is_dot;
bfd_boolean computation_is_valid = TRUE;
etree_type *tree;
+ asection *osec;
for (i = 0; i < SECTION_NAME_MAP_LENGTH; i++)
print_space ();
is_dot = (dst[0] == '.' && dst[1] == 0);
tree = assignment->exp->assign.src;
- computation_is_valid = is_dot || (scan_for_self_assignment (dst, tree) == FALSE);
+ computation_is_valid = is_dot || !scan_for_self_assignment (dst, tree);
}
- exp_fold_tree (tree, output_section->bfd_section, &print_dot);
+ osec = output_section->bfd_section;
+ if (osec == NULL)
+ osec = bfd_abs_section_ptr;
+ exp_fold_tree (tree, osec, &print_dot);
if (expld.result.valid_p)
{
bfd_vma value;
{
value = expld.result.value;
- if (expld.result.section)
+ if (expld.result.section != NULL)
value += expld.result.section->vma;
minfo ("0x%V", value);
{
value = h->u.def.value;
- if (expld.result.section)
+ if (expld.result.section != NULL)
value += expld.result.section->vma;
minfo ("[0x%V]", value);
return;
*ud->map_symbol_def_tail = 0;
-
+
/* Sort the symbols by address. */
entries = (struct bfd_link_hash_entry **)
obstack_alloc (&map_obstack, ud->map_symbol_def_count * sizeof (*entries));
}
#define IGNORE_SECTION(s) \
- ((s->flags & SEC_NEVER_LOAD) != 0 \
- || (s->flags & SEC_ALLOC) == 0 \
- || ((s->flags & SEC_THREAD_LOCAL) != 0 \
+ ((s->flags & SEC_ALLOC) == 0 \
+ || ((s->flags & SEC_THREAD_LOCAL) != 0 \
&& (s->flags & SEC_LOAD) == 0))
/* Check to see if any allocated sections overlap with other allocated
static void
lang_check_section_addresses (void)
{
- asection *s, *os;
+ asection *s, *p;
asection **sections, **spp;
unsigned int count;
bfd_vma s_start;
bfd_vma s_end;
- bfd_vma os_start;
- bfd_vma os_end;
+ bfd_vma p_start;
+ bfd_vma p_end;
bfd_size_type amt;
lang_memory_region_type *m;
for (s = link_info.output_bfd->sections; s != NULL; s = s->next)
{
/* Only consider loadable sections with real contents. */
- if ((s->flags & SEC_NEVER_LOAD)
- || !(s->flags & SEC_LOAD)
+ if (!(s->flags & SEC_LOAD)
|| !(s->flags & SEC_ALLOC)
|| s->size == 0)
continue;
spp = sections;
s = *spp++;
- s_start = bfd_section_lma (link_info.output_bfd, s);
+ s_start = s->lma;
s_end = s_start + TO_ADDR (s->size) - 1;
for (count--; count; count--)
{
/* We must check the sections' LMA addresses not their VMA
addresses because overlay sections can have overlapping VMAs
but they must have distinct LMAs. */
- os = s;
- os_start = s_start;
- os_end = s_end;
+ p = s;
+ p_start = s_start;
+ p_end = s_end;
s = *spp++;
- s_start = bfd_section_lma (link_info.output_bfd, s);
+ s_start = s->lma;
s_end = s_start + TO_ADDR (s->size) - 1;
- /* Look for an overlap. */
- if (s_end >= os_start && s_start <= os_end)
+ /* Look for an overlap. We have sorted sections by lma, so we
+ know that s_start >= p_start. Besides the obvious case of
+ overlap when the current section starts before the previous
+ one ends, we also must have overlap if the previous section
+ wraps around the address space. */
+ if (s_start <= p_end
+ || p_end < p_start)
einfo (_("%X%P: section %s loaded at [%V,%V] overlaps section %s loaded at [%V,%V]\n"),
- s->name, s_start, s_end, os->name, os_start, os_end);
+ s->name, s_start, s_end, p->name, p_start, p_end);
}
free (sections);
bfd_vma newdot, after;
lang_output_section_statement_type *os;
lang_memory_region_type *r;
+ int section_alignment = 0;
os = &s->output_section_statement;
+ if (os->constraint == -1)
+ break;
+
/* FIXME: We shouldn't need to zero section vmas for ld -r
here, in lang_insert_orphan, or in the default linker scripts.
This is covering for coff backend linker bugs. See PR6945. */
exp_fold_tree (os->addr_tree, bfd_abs_section_ptr, &dot);
if (expld.result.valid_p)
- dot = expld.result.value + expld.result.section->vma;
+ {
+ dot = expld.result.value;
+ if (expld.result.section != NULL)
+ dot += expld.result.section->vma;
+ }
else if (expld.phase != lang_mark_phase_enum)
einfo (_("%F%S: non constant or forward reference"
" address expression for section %s\n"),
}
else
{
- int align;
-
if (os->addr_tree == NULL)
{
/* No address specified for this section, get one
}
newdot = os->region->current;
- align = os->bfd_section->alignment_power;
+ section_alignment = os->bfd_section->alignment_power;
}
else
- align = os->section_alignment;
+ section_alignment = os->section_alignment;
/* Align to what the section needs. */
- if (align > 0)
+ if (section_alignment > 0)
{
bfd_vma savedot = newdot;
- newdot = align_power (newdot, align);
+ newdot = align_power (newdot, section_alignment);
if (newdot != savedot
&& (config.warn_section_align
{
bfd_vma lma = os->lma_region->current;
- if (os->section_alignment != -1)
- lma = align_power (lma, os->section_alignment);
+ if (section_alignment > 0)
+ lma = align_power (lma, section_alignment);
os->bfd_section->lma = lma;
}
else if (r->last_os != NULL
else
lma = dot + last->lma - last->vma;
- if (os->section_alignment != -1)
- lma = align_power (lma, os->section_alignment);
+ if (section_alignment > 0)
+ lma = align_power (lma, section_alignment);
os->bfd_section->lma = lma;
}
}
/* Update dot in the region ?
We only do this if the section is going to be allocated,
since unallocated sections do not contribute to the region's
- overall size in memory.
-
- If the SEC_NEVER_LOAD bit is not set, it will affect the
- addresses of sections after it. We have to update
- dot. */
+ overall size in memory. */
if (os->region != NULL
- && ((os->bfd_section->flags & SEC_NEVER_LOAD) == 0
- || (os->bfd_section->flags & (SEC_ALLOC | SEC_LOAD))))
+ && (os->bfd_section->flags & (SEC_ALLOC | SEC_LOAD)))
{
os->region->current = dot;
/* If dot is advanced, this implies that the section
should have space allocated to it, unless the
user has explicitly stated that the section
- should never be loaded. */
- if (!(output_section_statement->flags & SEC_NEVER_LOAD))
+ should not be allocated. */
+ if (output_section_statement->sectype != noalloc_section
+ && (output_section_statement->sectype != noload_section
+ || (bfd_get_flavour (link_info.output_bfd)
+ == bfd_target_elf_flavour)))
output_section_statement->bfd_section->flags |= SEC_ALLOC;
}
dot = newdot;
lang_reset_memory_regions ();
one_lang_size_sections_pass (relax, check_regions);
}
+ else
+ expld.dataseg.phase = exp_dataseg_done;
}
-
- expld.phase = lang_final_phase_enum;
+ else
+ expld.dataseg.phase = exp_dataseg_done;
}
/* Worker function for lang_do_assignments. Recursiveness goes here. */
case lang_data_statement_enum:
exp_fold_tree (s->data_statement.exp, bfd_abs_section_ptr, &dot);
if (expld.result.valid_p)
- s->data_statement.value = (expld.result.value
- + expld.result.section->vma);
+ {
+ s->data_statement.value = expld.result.value;
+ if (expld.result.section != NULL)
+ s->data_statement.value += expld.result.section->vma;
+ }
else
einfo (_("%F%P: invalid data statement\n"));
{
/* Force the user to specify a root when generating a relocatable with
--gc-sections. */
if (link_info.gc_sections && link_info.relocatable
- && (entry_symbol.name == NULL
- && ldlang_undef_chain_list_head == NULL))
+ && !(entry_from_cmdline || undef_from_cmdline))
einfo (_("%P%F: gc-sections requires either an entry or "
"an undefined symbol\n"));
os = lang_output_section_statement_lookup (name,
constraint,
TRUE);
+ if (os->addr_tree == NULL
+ && (link_info.relocatable
+ || (s->flags & (SEC_LOAD | SEC_ALLOC)) == 0))
+ os->addr_tree = exp_intop (0);
lang_add_section (&os->children, s, os);
}
}
current_target = default_target;
open_input_bfds (statement_list.head, FALSE);
+#ifdef ENABLE_PLUGINS
+ {
+ union lang_statement_union **listend;
+ /* Now all files are read, let the plugin(s) decide if there
+ are any more to be added to the link before we call the
+ emulation's after_open hook. */
+ listend = statement_list.tail;
+ ASSERT (!*listend);
+ if (plugin_call_all_symbols_read ())
+ einfo (_("%P%F: %s: plugin reported error after all symbols read\n"),
+ plugin_error_plugin ());
+ /* If any new files were added, they will be on the end of the
+ statement list, and we can open them now by getting open_input_bfds
+ to carry on from where it ended last time. */
+ if (*listend)
+ open_input_bfds (*listend, FALSE);
+ }
+#endif /* ENABLE_PLUGINS */
+
link_info.gc_sym_list = &entry_symbol;
if (entry_symbol.name == NULL)
link_info.gc_sym_list = ldlang_undef_chain_list_head;
/* Run through the contours of the script and attach input sections
to the correct output sections. */
+ lang_statement_iteration++;
map_input_to_output_sections (statement_list.head, NULL, NULL);
process_insert_statements ();
/* Do all the assignments, now that we know the final resting places
of all the symbols. */
-
+ expld.phase = lang_final_phase_enum;
lang_do_assignments ();
ldemul_finish ();
void
lang_startup (const char *name)
{
- if (startup_file != NULL)
+ if (first_file->filename != NULL)
{
einfo (_("%P%F: multiple STARTUP files\n"));
}
first_file->filename = name;
first_file->local_sym_name = name;
first_file->real = TRUE;
-
- startup_file = name;
}
void
if (!current_section->lma_region && !current_section->load_base
&& current_section->region == current_section->prev->region)
current_section->lma_region = current_section->prev->lma_region;
-
+
current_section->fill = fill;
current_section->phdrs = phdrs;
pop_stat_ptr ();
n->phdrs = phdrs;
n->at = at;
n->flags = flags;
-
+
hdrs = n->type == 1 && (phdrs || filehdr);
for (pp = &lang_phdr_list; *pp != NULL; pp = &(*pp)->next)
overlay region. */
if (overlay_list != NULL)
overlay_list->os->update_dot_tree
- = exp_assop ('=', ".", exp_binop ('+', overlay_vma, overlay_max));
+ = exp_assign (".", exp_binop ('+', overlay_vma, overlay_max));
l = overlay_list;
while (l != NULL)
struct bfd_elf_version_expr *prev,
const char *sym)
{
+ const char *c_sym;
const char *cxx_sym = sym;
const char *java_sym = sym;
struct bfd_elf_version_expr *expr = NULL;
+ enum demangling_styles curr_style;
+
+ curr_style = CURRENT_DEMANGLING_STYLE;
+ cplus_demangle_set_style (no_demangling);
+ c_sym = bfd_demangle (link_info.output_bfd, sym, DMGL_NO_OPTS);
+ if (!c_sym)
+ c_sym = sym;
+ cplus_demangle_set_style (curr_style);
if (head->mask & BFD_ELF_VERSION_CXX_TYPE)
{
- cxx_sym = cplus_demangle (sym, DMGL_PARAMS | DMGL_ANSI);
+ cxx_sym = bfd_demangle (link_info.output_bfd, sym,
+ DMGL_PARAMS | DMGL_ANSI);
if (!cxx_sym)
cxx_sym = sym;
}
if (head->mask & BFD_ELF_VERSION_JAVA_TYPE)
{
- java_sym = cplus_demangle (sym, DMGL_JAVA);
+ java_sym = bfd_demangle (link_info.output_bfd, sym, DMGL_JAVA);
if (!java_sym)
java_sym = sym;
}
case 0:
if (head->mask & BFD_ELF_VERSION_C_TYPE)
{
- e.pattern = sym;
+ e.pattern = c_sym;
expr = (struct bfd_elf_version_expr *)
htab_find ((htab_t) head->htab, &e);
- while (expr && strcmp (expr->pattern, sym) == 0)
+ while (expr && strcmp (expr->pattern, c_sym) == 0)
if (expr->mask == BFD_ELF_VERSION_C_TYPE)
goto out_ret;
else
else if (expr->mask == BFD_ELF_VERSION_CXX_TYPE)
s = cxx_sym;
else
- s = sym;
+ s = c_sym;
if (fnmatch (expr->pattern, s, 0) == 0)
break;
}
out_ret:
+ if (c_sym != sym)
+ free ((char *) c_sym);
if (cxx_sym != sym)
free ((char *) cxx_sym);
if (java_sym != sym)
einfo (_("%X%P: unable to find version dependency `%s'\n"), name);
+ ret->version_needed = NULL;
return ret;
}
lang_append_dynamic_list (dynamic);
}
+
+/* Scan a space and/or comma separated string of features. */
+
+void
+lang_ld_feature (char *str)
+{
+ char *p, *q;
+
+ p = str;
+ while (*p)
+ {
+ char sep;
+ while (*p == ',' || ISSPACE (*p))
+ ++p;
+ if (!*p)
+ break;
+ q = p + 1;
+ while (*q && *q != ',' && !ISSPACE (*q))
+ ++q;
+ sep = *q;
+ *q = 0;
+ if (strcasecmp (p, "SANE_EXPR") == 0)
+ config.sane_expr = TRUE;
+ else
+ einfo (_("%X%P: unknown feature `%s'\n"), p);
+ *q = sep;
+ p = q;
+ }
+}