/* * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2021, PostgreSQL Global Development Group * * src/bin/psql/variables.c */ #include "postgres_fe.h" #include "common.h" #include "common/logging.h" #include "variables.h" /* * Check whether a variable's name is allowed. * * We allow any non-ASCII character, as well as ASCII letters, digits, and * underscore. Keep this in sync with the definition of variable_char in * psqlscan.l and psqlscanslash.l. */ static bool valid_variable_name(const char *name) { const unsigned char *ptr = (const unsigned char *) name; /* Mustn't be zero-length */ if (*ptr == '\0') return false; while (*ptr) { if (IS_HIGHBIT_SET(*ptr) || strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "_0123456789", *ptr) != NULL) ptr++; else return false; } return true; } /* * A "variable space" is represented by an otherwise-unused struct _variable * that serves as list header. * * The list entries are kept in name order (according to strcmp). This * is mainly to make the results of PrintVariables() more pleasing. */ VariableSpace CreateVariableSpace(void) { struct _variable *ptr; ptr = pg_malloc(sizeof *ptr); ptr->name = NULL; ptr->value = NULL; ptr->substitute_hook = NULL; ptr->assign_hook = NULL; ptr->next = NULL; return ptr; } /* * Get string value of variable, or NULL if it's not defined. * * Note: result is valid until variable is next assigned to. */ const char * GetVariable(VariableSpace space, const char *name) { struct _variable *current; if (!space) return NULL; for (current = space->next; current; current = current->next) { int cmp = strcmp(current->name, name); if (cmp == 0) { /* this is correct answer when value is NULL, too */ return current->value; } if (cmp > 0) break; /* it's not there */ } return NULL; } /* * Try to interpret "value" as a boolean value, and if successful, * store it in *result. Otherwise don't clobber *result. * * Valid values are: true, false, yes, no, on, off, 1, 0; as well as unique * prefixes thereof. * * "name" is the name of the variable we're assigning to, to use in error * report if any. Pass name == NULL to suppress the error report. * * Return true when "value" is syntactically valid, false otherwise. */ bool ParseVariableBool(const char *value, const char *name, bool *result) { size_t len; bool valid = true; /* Treat "unset" as an empty string, which will lead to error below */ if (value == NULL) value = ""; len = strlen(value); if (len > 0 && pg_strncasecmp(value, "true", len) == 0) *result = true; else if (len > 0 && pg_strncasecmp(value, "false", len) == 0) *result = false; else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0) *result = true; else if (len > 0 && pg_strncasecmp(value, "no", len) == 0) *result = false; /* 'o' is not unique enough */ else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) *result = true; else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) *result = false; else if (pg_strcasecmp(value, "1") == 0) *result = true; else if (pg_strcasecmp(value, "0") == 0) *result = false; else { /* string is not recognized; don't clobber *result */ if (name) pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected", value, name); valid = false; } return valid; } /* * Try to interpret "value" as an integer value, and if successful, * store it in *result. Otherwise don't clobber *result. * * "name" is the name of the variable we're assigning to, to use in error * report if any. Pass name == NULL to suppress the error report. * * Return true when "value" is syntactically valid, false otherwise. */ bool ParseVariableNum(const char *value, const char *name, int *result) { char *end; long numval; /* Treat "unset" as an empty string, which will lead to error below */ if (value == NULL) value = ""; errno = 0; numval = strtol(value, &end, 0); if (errno == 0 && *end == '\0' && end != value && numval == (int) numval) { *result = (int) numval; return true; } else { /* string is not recognized; don't clobber *result */ if (name) pg_log_error("invalid value \"%s\" for \"%s\": integer expected", value, name); return false; } } /* * Print values of all variables. */ void PrintVariables(VariableSpace space) { struct _variable *ptr; if (!space) return; for (ptr = space->next; ptr; ptr = ptr->next) { if (ptr->value) printf("%s = '%s'\n", ptr->name, ptr->value); if (cancel_pressed) break; } } /* * Set the variable named "name" to value "value", * or delete it if "value" is NULL. * * Returns true if successful, false if not; in the latter case a suitable * error message has been printed, except for the unexpected case of * space or name being NULL. */ bool SetVariable(VariableSpace space, const char *name, const char *value) { struct _variable *current, *previous; if (!space || !name) return false; if (!valid_variable_name(name)) { /* Deletion of non-existent variable is not an error */ if (!value) return true; pg_log_error("invalid variable name: \"%s\"", name); return false; } for (previous = space, current = space->next; current; previous = current, current = current->next) { int cmp = strcmp(current->name, name); if (cmp == 0) { /* * Found entry, so update, unless assign hook returns false. * * We must duplicate the passed value to start with. This * simplifies the API for substitute hooks. Moreover, some assign * hooks assume that the passed value has the same lifespan as the * variable. Having to free the string again on failure is a * small price to pay for keeping these APIs simple. */ char *new_value = value ? pg_strdup(value) : NULL; bool confirmed; if (current->substitute_hook) new_value = current->substitute_hook(new_value); if (current->assign_hook) confirmed = current->assign_hook(new_value); else confirmed = true; if (confirmed) { if (current->value) pg_free(current->value); current->value = new_value; /* * If we deleted the value, and there are no hooks to * remember, we can discard the variable altogether. */ if (new_value == NULL && current->substitute_hook == NULL && current->assign_hook == NULL) { previous->next = current->next; free(current->name); free(current); } } else if (new_value) pg_free(new_value); /* current->value is left unchanged */ return confirmed; } if (cmp > 0) break; /* it's not there */ } /* not present, make new entry ... unless we were asked to delete */ if (value) { current = pg_malloc(sizeof *current); current->name = pg_strdup(name); current->value = pg_strdup(value); current->substitute_hook = NULL; current->assign_hook = NULL; current->next = previous->next; previous->next = current; } return true; } /* * Attach substitute and/or assign hook functions to the named variable. * If you need only one hook, pass NULL for the other. * * If the variable doesn't already exist, create it with value NULL, just so * we have a place to store the hook function(s). (The substitute hook might * immediately change the NULL to something else; if not, this state is * externally the same as the variable not being defined.) * * The substitute hook, if given, is immediately called on the variable's * value. Then the assign hook, if given, is called on the variable's value. * This is meant to let it update any derived psql state. If the assign hook * doesn't like the current value, it will print a message to that effect, * but we'll ignore it. Generally we do not expect any such failure here, * because this should get called before any user-supplied value is assigned. */ void SetVariableHooks(VariableSpace space, const char *name, VariableSubstituteHook shook, VariableAssignHook ahook) { struct _variable *current, *previous; if (!space || !name) return; if (!valid_variable_name(name)) return; for (previous = space, current = space->next; current; previous = current, current = current->next) { int cmp = strcmp(current->name, name); if (cmp == 0) { /* found entry, so update */ current->substitute_hook = shook; current->assign_hook = ahook; if (shook) current->value = (*shook) (current->value); if (ahook) (void) (*ahook) (current->value); return; } if (cmp > 0) break; /* it's not there */ } /* not present, make new entry */ current = pg_malloc(sizeof *current); current->name = pg_strdup(name); current->value = NULL; current->substitute_hook = shook; current->assign_hook = ahook; current->next = previous->next; previous->next = current; if (shook) current->value = (*shook) (current->value); if (ahook) (void) (*ahook) (current->value); } /* * Return true iff the named variable has substitute and/or assign hook * functions. */ bool VariableHasHook(VariableSpace space, const char *name) { struct _variable *current; Assert(space); Assert(name); for (current = space->next; current; current = current->next) { int cmp = strcmp(current->name, name); if (cmp == 0) return (current->substitute_hook != NULL || current->assign_hook != NULL); if (cmp > 0) break; /* it's not there */ } return false; } /* * Convenience function to set a variable's value to "on". */ bool SetVariableBool(VariableSpace space, const char *name) { return SetVariable(space, name, "on"); } /* * Attempt to delete variable. * * If unsuccessful, print a message and return "false". * Deleting a nonexistent variable is not an error. */ bool DeleteVariable(VariableSpace space, const char *name) { return SetVariable(space, name, NULL); } /* * Emit error with suggestions for variables or commands * accepting enum-style arguments. * This function just exists to standardize the wording. * suggestions should follow the format "fee, fi, fo, fum". */ void PsqlVarEnumError(const char *name, const char *value, const char *suggestions) { pg_log_error("unrecognized value \"%s\" for \"%s\"\n" "Available values are: %s.", value, name, suggestions); }