void env_universal_set(const wcstring &name, const wcstring &value, bool exportv) { if (!s_env_univeral_inited) return; debug(3, L"env_universal_set( \"%ls\", \"%ls\" )", name.c_str(), value.c_str()); if (! synchronizes_via_fishd() || is_dead()) { env_universal_common_set(name.c_str(), value.c_str(), exportv); env_universal_barrier(); } else { message_t *msg = create_message(exportv?SET_EXPORT:SET, name.c_str(), value.c_str()); if (!msg) { debug(1, L"Could not create universal variable message"); return; } msg->count=1; env_universal_server.unsent.push(msg); env_universal_barrier(); } }
void env_universal_set( const wcstring &name, const wcstring &value, int exportv ) { message_t *msg; if( !init ) return; debug( 3, L"env_universal_set( \"%ls\", \"%ls\" )", name.c_str(), value.c_str() ); if( is_dead() ) { env_universal_common_set( name.c_str(), value.c_str(), exportv ); } else { msg = create_message( exportv?SET_EXPORT:SET, name.c_str(), value.c_str()); if( !msg ) { debug( 1, L"Could not create universal variable message" ); return; } msg->count=1; env_universal_server.unsent->push(msg); env_universal_barrier(); } }
static void update_export_array_if_necessary(bool recalc) { ASSERT_IS_MAIN_THREAD(); if (recalc && !get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (has_changed_exported) { std::map<wcstring, wcstring> vals; debug(4, L"env_export_arr() recalc"); get_exported(top, &vals); if (uvars()) { const wcstring_list_t uni = uvars()->get_names(true, false); for (size_t i = 0; i < uni.size(); i++) { const wcstring &key = uni.at(i); const env_var_t val = uvars()->get(key); if (!val.missing() && val != ENV_NULL) { // Note that std::map::insert does NOT overwrite a value already in the map, // which we depend on here. vals.insert(std::pair<wcstring, wcstring>(key, val)); } } } std::vector<std::string> local_export_buffer; export_func(vals, local_export_buffer); export_array.set(local_export_buffer); has_changed_exported = false; } }
int env_universal_remove(const wchar_t *name) { int res; if (!s_env_univeral_inited) return 1; CHECK(name, 1); wcstring name_str = name; res = env_universal_common_get(name_str).missing(); debug(3, L"env_universal_remove( \"%ls\" )", name); if (! synchronizes_via_fishd() || is_dead()) { env_universal_common_remove(name_str); } else { message_t *msg = create_message(ERASE, name, 0); msg->count=1; env_universal_server.unsent.push(msg); env_universal_barrier(); } return res; }
int env_universal_remove( const wchar_t *name ) { int res; message_t *msg; if( !init ) return 1; CHECK( name, 1 ); res = !env_universal_common_get( name ); debug( 3, L"env_universal_remove( \"%ls\" )", name ); if( is_dead() ) { env_universal_common_remove( wcstring(name) ); } else { msg= create_message( ERASE, name, 0); msg->count=1; env_universal_server.unsent->push(msg); env_universal_barrier(); } return res; }
void env_universal_init(wchar_t * p, wchar_t *u, void (*sf)(), void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)) { if (! synchronizes_via_fishd()) { external_callback = cb; env_universal_common_init(&callback); s_env_univeral_inited = true; } else { path=p; user=u; start_fishd=sf; external_callback = cb; env_universal_server.fd = get_socket(); env_universal_common_init(&callback); env_universal_read_all(); s_env_univeral_inited = true; if (env_universal_server.fd >= 0) { env_universal_barrier(); } } }
int env_remove(const wcstring &key, int var_mode) { ASSERT_IS_MAIN_THREAD(); env_node_t *first_node; int erased = 0; if ((var_mode & ENV_USER) && is_read_only(key)) { return 2; } first_node = top; if (!(var_mode & ENV_UNIVERSAL)) { if (var_mode & ENV_GLOBAL) { first_node = global_env; } if (try_remove(first_node, key.c_str(), var_mode)) { event_t ev = event_t::variable_event(key); ev.arguments.push_back(L"VARIABLE"); ev.arguments.push_back(L"ERASE"); ev.arguments.push_back(key); event_fire(&ev); erased = 1; } } if (!erased && !(var_mode & ENV_GLOBAL) && !(var_mode & ENV_LOCAL)) { bool is_exported = uvars()->get_export(key); erased = uvars() && uvars()->remove(key); if (erased) { env_universal_barrier(); event_t ev = event_t::variable_event(key); ev.arguments.push_back(L"VARIABLE"); ev.arguments.push_back(L"ERASE"); ev.arguments.push_back(key); event_fire(&ev); } if (is_exported) mark_changed_exported(); } react_to_variable_change(key); return !erased; }
bool env_exist(const wchar_t *key, env_mode_flags_t mode) { CHECK(key, false); const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool test_local = !has_scope || (mode & ENV_LOCAL); const bool test_global = !has_scope || (mode & ENV_GLOBAL); const bool test_universal = !has_scope || (mode & ENV_UNIVERSAL); const bool test_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT); const bool test_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT); if (is_electric(key)) { // Electric variables all exist, and they are all global. A local or universal version can // not exist. They are also never exported. if (test_global && test_unexported) { return true; } return false; } if (test_local || test_global) { const env_node_t *env = test_local ? top : global_env; while (env != NULL) { if (env == global_env && !test_global) { break; } var_table_t::const_iterator result = env->env.find(key); if (result != env->env.end()) { const var_entry_t &res = result->second; return res.exportv ? test_exported : test_unexported; } env = env->next_scope_to_search(); } } if (test_universal) { if (!get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (uvars() && !uvars()->get(key).missing()) { return uvars()->get_export(key) ? test_exported : test_unexported; } } return 0; }
/** Try to establish a new connection to fishd. If successfull, end with call to env_universal_barrier(), to make sure everything is in sync. */ static void reconnect() { if( get_socket_count >= RECONNECT_COUNT ) return; debug( 3, L"Get new fishd connection" ); init = 0; env_universal_server.buffer_consumed = env_universal_server.buffer_used = 0; env_universal_server.fd = get_socket(1); init = 1; if( env_universal_server.fd >= 0 ) { env_universal_remove_all(); env_universal_barrier(); } }
/** Try to establish a new connection to fishd. If successfull, end with call to env_universal_barrier(), to make sure everything is in sync. */ static void reconnect() { if (get_socket_count >= RECONNECT_COUNT) return; debug(3, L"Get new fishd connection"); s_env_univeral_inited = false; env_universal_server.buffer_consumed = 0; env_universal_server.read_buffer.clear(); env_universal_server.fd = get_socket(); s_env_univeral_inited = true; if (env_universal_server.fd >= 0) { env_universal_remove_all(); env_universal_barrier(); } }
void env_universal_init( wchar_t * p, wchar_t *u, void (*sf)(), void (*cb)( int type, const wchar_t *name, const wchar_t *val )) { path=p; user=u; start_fishd=sf; external_callback = cb; connection_init( &env_universal_server, -1 ); env_universal_server.fd = get_socket(1); env_universal_common_init( &callback ); env_universal_read_all(); init = 1; if( env_universal_server.fd >= 0 ) { env_universal_barrier(); } }
static void update_export_array_if_necessary(bool recalc) { ASSERT_IS_MAIN_THREAD(); if (recalc && ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (has_changed_exported) { std::map<wcstring, wcstring> vals; size_t i; debug(4, L"env_export_arr() recalc"); get_exported(top, vals); wcstring_list_t uni; env_universal_get_names2(uni, 1, 0); for (i=0; i<uni.size(); i++) { const wcstring &key = uni.at(i); const wchar_t *val = env_universal_get(key.c_str()); if (wcscmp(val, ENV_NULL)) { // Note that std::map::insert does NOT overwrite a value already in the map, // which we depend on here vals.insert(std::pair<wcstring, wcstring>(key, val)); } } std::vector<std::string> local_export_buffer; export_func(vals, local_export_buffer); export_array.set(local_export_buffer); has_changed_exported=false; } }
bool env_exist(const wchar_t *key, int mode) { env_node_t *env; const wchar_t *item = NULL; CHECK(key, false); /* Read only variables all exist, and they are all global. A local version can not exist. */ if (!(mode & ENV_LOCAL) && !(mode & ENV_UNIVERSAL)) { if (is_read_only(key) || is_electric(key)) { //Such variables are never exported if (mode & ENV_EXPORT) { return false; } else if (mode & ENV_UNEXPORT) { return true; } return true; } } if (!(mode & ENV_UNIVERSAL)) { env = (mode & ENV_GLOBAL)?global_env:top; while (env != 0) { var_table_t::iterator result = env->env.find(key); if (result != env->env.end()) { const var_entry_t &res = result->second; if (mode & ENV_EXPORT) { return res.exportv; } else if (mode & ENV_UNEXPORT) { return ! res.exportv; } return true; } if (mode & ENV_LOCAL) break; env = env->next_scope_to_search(); } } if (!(mode & ENV_LOCAL) && !(mode & ENV_GLOBAL)) { if (! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } item = env_universal_get(key); if (item != NULL) { if (mode & ENV_EXPORT) { return env_universal_get_export(key) == 1; } else if (mode & ENV_UNEXPORT) { return env_universal_get_export(key) == 0; } return 1; } } return 0; }
env_var_t env_get_string(const wcstring &key) { /* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */ const bool is_main = is_main_thread(); if (key == L"history" && is_main) { env_var_t result; history_t *history = reader_get_history(); if (! history) { history = &history_t::history_with_name(L"fish"); } if (history) history->get_string_representation(result, ARRAY_SEP_STR); return result; } else if (key == L"COLUMNS") { return to_string(common_get_width()); } else if (key == L"LINES") { return to_string(common_get_height()); } else if (key == L"status") { return to_string(proc_get_last_status()); } else if (key == L"umask") { return format_string(L"0%0.3o", get_umask()); } else { { /* Lock around a local region */ scoped_lock lock(env_lock); env_node_t *env = top; wcstring result; while (env != NULL) { const var_entry_t *entry = env->find_entry(key); if (entry != NULL) { if (entry->val == ENV_NULL) { return env_var_t::missing_var(); } else { return entry->val; } } env = env->next_scope_to_search(); } } /* Another big hack - only do a universal barrier on the main thread (since it can change variable values) Make sure we do this outside the env_lock because it may itself call env_get_string */ if (is_main && ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } const wchar_t *item = env_universal_get(key); if (!item || (wcscmp(item, ENV_NULL)==0)) { return env_var_t::missing_var(); } else { return item; } } }
/// Executes a process \p in job \j, using the read pipe \p pipe_current_read. /// If the process pipes to a command, the read end of the created pipe is returned in /// out_pipe_next_read. \returns true on success, false on exec error. static bool exec_process_in_job(parser_t &parser, process_t *p, job_t *j, autoclose_fd_t pipe_current_read, autoclose_fd_t *out_pipe_next_read, const io_chain_t &all_ios, size_t stdout_read_limit) { // The IO chain for this process. It starts with the block IO, then pipes, and then gets any // from the process. io_chain_t process_net_io_chain = j->block_io_chain(); // See if we need a pipe. const bool pipes_to_next_command = !p->is_last_in_job; // The write end of any pipe we create. autoclose_fd_t pipe_current_write{}; // The pipes the current process write to and read from. Unfortunately these can't be just // allocated on the stack, since j->io wants shared_ptr. // // The write pipe (destined for stdout) needs to occur before redirections. For example, // with a redirection like this: // // `foo 2>&1 | bar` // // what we want to happen is this: // // dup2(pipe, stdout) // dup2(stdout, stderr) // // so that stdout and stderr both wind up referencing the pipe. // // The read pipe (destined for stdin) is more ambiguous. Imagine a pipeline like this: // // echo alpha | cat < beta.txt // // Should cat output alpha or beta? bash and ksh output 'beta', tcsh gets it right and // complains about ambiguity, and zsh outputs both (!). No shells appear to output 'alpha', // so we match bash here. That would mean putting the pipe first, so that it gets trumped by // the file redirection. // // However, eval does this: // // echo "begin; $argv "\n" ;end <&3 3<&-" | source 3<&0 // // which depends on the redirection being evaluated before the pipe. So the write end of the // pipe comes first, the read pipe of the pipe comes last. See issue #966. shared_ptr<io_pipe_t> pipe_write; shared_ptr<io_pipe_t> pipe_read; // Write pipe goes first. if (pipes_to_next_command) { pipe_write.reset(new io_pipe_t(p->pipe_write_fd, false)); process_net_io_chain.push_back(pipe_write); } // The explicit IO redirections associated with the process. process_net_io_chain.append(p->io_chain()); // Read pipe goes last. if (!p->is_first_in_job) { pipe_read.reset(new io_pipe_t(p->pipe_read_fd, true)); // Record the current read in pipe_read. pipe_read->pipe_fd[0] = pipe_current_read.fd(); process_net_io_chain.push_back(pipe_read); } // This call is used so the global environment variable array is regenerated, if needed, // before the fork. That way, we avoid a lot of duplicate work where EVERY child would need // to generate it, since that result would not get written back to the parent. This call // could be safely removed, but it would result in slightly lower performance - at least on // uniprocessor systems. if (p->type == EXTERNAL) { // Apply universal barrier so we have the most recent uvar changes if (!get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } env_export_arr(); } // Set up fds that will be used in the pipe. if (pipes_to_next_command) { // debug( 1, L"%ls|%ls" , p->argv[0], p->next->argv[0]); int local_pipe[2] = {-1, -1}; if (exec_pipe(local_pipe) == -1) { debug(1, PIPE_ERROR); wperror(L"pipe"); job_mark_process_as_failed(j, p); return false; } // Ensure our pipe fds not conflict with any fd redirections. E.g. if the process is // like 'cat <&5' then fd 5 must not be used by the pipe. if (!pipe_avoid_conflicts_with_io_chain(local_pipe, all_ios)) { // We failed. The pipes were closed for us. wperror(L"dup"); job_mark_process_as_failed(j, p); return false; } // This tells the redirection about the fds, but the redirection does not close them. assert(local_pipe[0] >= 0); assert(local_pipe[1] >= 0); memcpy(pipe_write->pipe_fd, local_pipe, sizeof(int) * 2); // Record our pipes. pipe_current_write.reset(local_pipe[1]); out_pipe_next_read->reset(local_pipe[0]); } // Execute the process. switch (p->type) { case INTERNAL_FUNCTION: case INTERNAL_BLOCK_NODE: { if (!exec_block_or_func_process(parser, j, p, all_ios, process_net_io_chain)) { return false; } break; } case INTERNAL_BUILTIN: { io_streams_t builtin_io_streams{stdout_read_limit}; if (!exec_internal_builtin_proc(parser, j, p, pipe_read.get(), process_net_io_chain, builtin_io_streams)) { return false; } if (!handle_builtin_output(j, p, &process_net_io_chain, builtin_io_streams)) { return false; } break; } case EXTERNAL: { if (!exec_external_command(j, p, process_net_io_chain)) { return false; } break; } case INTERNAL_EXEC: { // We should have handled exec up above. DIE("INTERNAL_EXEC process found in pipeline, where it should never be. Aborting."); break; } } return true; }
/** Internal function used by input_common_readch to read one byte from fd 0. This function should only be called by input_common_readch(). */ static wint_t readb() { /* do_loop must be set on every path through the loop; leaving it uninitialized allows the static analyzer to assist in catching mistakes. */ unsigned char arr[1]; bool do_loop; do { /* Flush callbacks */ input_flush_callbacks(); fd_set fdset; int fd_max = 0; int ioport = iothread_port(); int res; FD_ZERO(&fdset); FD_SET(0, &fdset); if (ioport > 0) { FD_SET(ioport, &fdset); fd_max = maxi(fd_max, ioport); } /* Get our uvar notifier */ universal_notifier_t ¬ifier = universal_notifier_t::default_notifier(); /* Get the notification fd (possibly none) */ int notifier_fd = notifier.notification_fd(); if (notifier_fd > 0) { FD_SET(notifier_fd, &fdset); fd_max = maxi(fd_max, notifier_fd); } /* Get its suggested delay (possibly none) */ struct timeval tv = {}; const unsigned long usecs_delay = notifier.usec_delay_between_polls(); if (usecs_delay > 0) { unsigned long usecs_per_sec = 1000000; tv.tv_sec = (int)(usecs_delay / usecs_per_sec); tv.tv_usec = (int)(usecs_delay % usecs_per_sec); } res = select(fd_max + 1, &fdset, 0, 0, usecs_delay > 0 ? &tv : NULL); if (res==-1) { switch (errno) { case EINTR: case EAGAIN: { if (interrupt_handler) { int res = interrupt_handler(); if (res) { return res; } if (has_lookahead()) { return lookahead_pop(); } } do_loop = true; break; } default: { /* The terminal has been closed. Save and exit. */ return R_EOF; } } } else { /* Assume we loop unless we see a character in stdin */ do_loop = true; /* Check to see if we want a universal variable barrier */ bool barrier_from_poll = notifier.poll(); bool barrier_from_readability = false; if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset)) { barrier_from_readability = notifier.notification_fd_became_readable(notifier_fd); } if (barrier_from_poll || barrier_from_readability) { env_universal_barrier(); } if (ioport > 0 && FD_ISSET(ioport, &fdset)) { iothread_service_completion(); if (has_lookahead()) { return lookahead_pop(); } } if (FD_ISSET(STDIN_FILENO, &fdset)) { if (read_blocked(0, arr, 1) != 1) { /* The teminal has been closed. Save and exit. */ return R_EOF; } /* We read from stdin, so don't loop */ do_loop = false; } } } while (do_loop); return arr[0]; }
int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) { ASSERT_IS_MAIN_THREAD(); bool has_changed_old = has_changed_exported; bool has_changed_new = false; int done=0; if (val && contains(key, L"PWD", L"HOME")) { /* Canoncalize our path; if it changes, recurse and try again. */ wcstring val_canonical = val; path_make_canonical(val_canonical); if (val != val_canonical) { return env_set(key, val_canonical.c_str(), var_mode); } } if ((var_mode & (ENV_LOCAL | ENV_UNIVERSAL)) && (is_read_only(key) || is_electric(key))) { return ENV_SCOPE; } if ((var_mode & ENV_EXPORT) && is_electric(key)) { return ENV_SCOPE; } if ((var_mode & ENV_USER) && is_read_only(key)) { return ENV_PERM; } if (key == L"umask") { wchar_t *end; /* Set the new umask */ if (val && wcslen(val)) { errno=0; long mask = wcstol(val, &end, 8); if (!errno && (!*end) && (mask <= 0777) && (mask >= 0)) { umask(mask); /* Do not actually create a umask variable, on env_get, it will be calculated dynamically */ return 0; } } return ENV_INVALID; } /* Zero element arrays are internaly not coded as null but as this placeholder string */ if (!val) { val = ENV_NULL; } if (var_mode & ENV_UNIVERSAL) { const bool old_export = uvars() && uvars()->get_export(key); bool new_export; if (var_mode & ENV_EXPORT) { // export new_export = true; } else if (var_mode & ENV_UNEXPORT) { // unexport new_export = false; } else { // not changing the export new_export = old_export; } if (uvars()) { uvars()->set(key, val, new_export); env_universal_barrier(); if (old_export || new_export) { mark_changed_exported(); } } } else { // Determine the node env_node_t *preexisting_node = env_get_node(key); bool preexisting_entry_exportv = false; if (preexisting_node != NULL) { var_table_t::const_iterator result = preexisting_node->env.find(key); assert(result != preexisting_node->env.end()); const var_entry_t &entry = result->second; if (entry.exportv) { preexisting_entry_exportv = true; has_changed_new = true; } } env_node_t *node = NULL; if (var_mode & ENV_GLOBAL) { node = global_env; } else if (var_mode & ENV_LOCAL) { node = top; } else if (preexisting_node != NULL) { node = preexisting_node; if ((var_mode & (ENV_EXPORT | ENV_UNEXPORT)) == 0) { // use existing entry's exportv var_mode = preexisting_entry_exportv ? ENV_EXPORT : 0; } } else { if (! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (uvars() && ! uvars()->get(key).missing()) { bool exportv; if (var_mode & ENV_EXPORT) { exportv = true; } else if (var_mode & ENV_UNEXPORT) { exportv = false; } else { exportv = uvars()->get_export(key); } uvars()->set(key, val, exportv); env_universal_barrier(); done = 1; } else { /* New variable with unspecified scope. The default scope is the innermost scope that is shadowing, which will be either the current function or the global scope. */ node = top; while (node->next && !node->new_scope) { node = node->next; } } } if (!done) { // Set the entry in the node // Note that operator[] accesses the existing entry, or creates a new one var_entry_t &entry = node->env[key]; if (entry.exportv) { // this variable already existed, and was exported has_changed_new = true; } entry.val = val; if (var_mode & ENV_EXPORT) { // the new variable is exported entry.exportv = true; node->exportv = true; has_changed_new = true; } else { entry.exportv = false; } if (has_changed_old || has_changed_new) mark_changed_exported(); } } event_t ev = event_t::variable_event(key); ev.arguments.reserve(3); ev.arguments.push_back(L"VARIABLE"); ev.arguments.push_back(L"SET"); ev.arguments.push_back(key); // debug( 1, L"env_set: fire events on variable %ls", key ); event_fire(&ev); // debug( 1, L"env_set: return from event firing" ); react_to_variable_change(key); return 0; }
int env_set(const wcstring &key, const wchar_t *val, int var_mode) { ASSERT_IS_MAIN_THREAD(); env_node_t *node = NULL; bool has_changed_old = has_changed_exported; bool has_changed_new = false; var_entry_t *e=0; int done=0; int is_universal = 0; if( val && contains( key, L"PWD", L"HOME" ) ) { /* Canoncalize our path; if it changes, recurse and try again. */ wcstring val_canonical = val; path_make_canonical(val_canonical); if (val != val_canonical) { return env_set( key, val_canonical.c_str(), var_mode ); } } if( (var_mode & ENV_USER ) && is_read_only(key) ) { return ENV_PERM; } if (key == L"umask") { wchar_t *end; /* Set the new umask */ if( val && wcslen(val) ) { errno=0; long mask = wcstol( val, &end, 8 ); if( !errno && (!*end) && (mask <= 0777) && (mask >= 0) ) { umask( mask ); } } /* Do not actually create a umask variable, on env_get, it will be calculated dynamically */ return 0; } /* Zero element arrays are internaly not coded as null but as this placeholder string */ if( !val ) { val = ENV_NULL; } if( var_mode & ENV_UNIVERSAL ) { int exportv = 0; if( !(var_mode & ENV_EXPORT ) && !(var_mode & ENV_UNEXPORT ) ) { env_universal_get_export( key ); } else { exportv = (var_mode & ENV_EXPORT ); } env_universal_set(key, val, exportv); is_universal = 1; } else { node = env_get_node( key ); if( node ) { var_table_t::iterator result = node->env.find(key); assert(result != node->env.end()); e = result->second; if( e->exportv ) { has_changed_new = true; } } if( (var_mode & ENV_LOCAL) || (var_mode & ENV_GLOBAL) ) { node = ( var_mode & ENV_GLOBAL )?global_env:top; } else { if( node ) { if( !(var_mode & ENV_EXPORT ) && !(var_mode & ENV_UNEXPORT ) ) { var_mode = e->exportv?ENV_EXPORT:0; } } else { if( ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if( env_universal_get( key ) ) { int exportv = 0; if( !(var_mode & ENV_EXPORT ) && !(var_mode & ENV_UNEXPORT ) ) { env_universal_get_export( key ); } else { exportv = (var_mode & ENV_EXPORT ); } env_universal_set(key, val, exportv); is_universal = 1; done = 1; } else { /* New variable with unspecified scope. The default scope is the innermost scope that is shadowing, which will be either the current function or the global scope. */ node = top; while( node->next && !node->new_scope ) { node = node->next; } } } } if( !done ) { var_entry_t *old_entry = NULL; var_table_t::iterator result = node->env.find(key); if ( result != node->env.end() ) { old_entry = result->second; node->env.erase(result); } var_entry_t *entry = NULL; if( old_entry ) { entry = old_entry; if( (var_mode & ENV_EXPORT) || entry->exportv ) { entry->exportv = !!(var_mode & ENV_EXPORT); has_changed_new = true; } } else { entry = new var_entry_t; if( var_mode & ENV_EXPORT) { entry->exportv = 1; has_changed_new = true; } else { entry->exportv = 0; } } entry->val = val; node->env[key] = entry; if( entry->exportv ) { node->exportv=1; } if (has_changed_old || has_changed_new) mark_changed_exported(); } } if( !is_universal ) { event_t ev = event_t::variable_event(key); ev.arguments.reset(new wcstring_list_t); ev.arguments->push_back(L"VARIABLE"); ev.arguments->push_back(L"SET"); ev.arguments->push_back(key); // debug( 1, L"env_set: fire events on variable %ls", key ); event_fire( &ev ); // debug( 1, L"env_set: return from event firing" ); ev.arguments.reset(NULL); } react_to_variable_change(key); return 0; }
env_var_t env_get_string(const wcstring &key, env_mode_flags_t mode) { const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool search_local = !has_scope || (mode & ENV_LOCAL); const bool search_global = !has_scope || (mode & ENV_GLOBAL); const bool search_universal = !has_scope || (mode & ENV_UNIVERSAL); const bool search_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT); const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT); /* Make the assumption that electric keys can't be shadowed elsewhere, since we currently block that in env_set() */ if (is_electric(key)) { if (!search_global) return env_var_t::missing_var(); /* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */ if (key == L"history" && is_main_thread()) { env_var_t result; history_t *history = reader_get_history(); if (! history) { history = &history_t::history_with_name(L"fish"); } if (history) history->get_string_representation(&result, ARRAY_SEP_STR); return result; } else if (key == L"COLUMNS") { return to_string(common_get_width()); } else if (key == L"LINES") { return to_string(common_get_height()); } else if (key == L"status") { return to_string(proc_get_last_status()); } else if (key == L"umask") { return format_string(L"0%0.3o", get_umask()); } // we should never get here unless the electric var list is out of sync } if (search_local || search_global) { /* Lock around a local region */ scoped_lock lock(env_lock); env_node_t *env = search_local ? top : global_env; while (env != NULL) { const var_entry_t *entry = env->find_entry(key); if (entry != NULL && (entry->exportv ? search_exported : search_unexported)) { if (entry->val == ENV_NULL) { return env_var_t::missing_var(); } else { return entry->val; } } if (has_scope) { if (!search_global || env == global_env) break; env = global_env; } else { env = env->next_scope_to_search(); } } } if (!search_universal) return env_var_t::missing_var(); /* Another big hack - only do a universal barrier on the main thread (since it can change variable values) Make sure we do this outside the env_lock because it may itself call env_get_string */ if (is_main_thread() && ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (uvars()) { env_var_t env_var = uvars()->get(key); if (env_var == ENV_NULL || !(uvars()->get_export(key) ? search_exported : search_unexported)) { env_var = env_var_t::missing_var(); } return env_var; } return env_var_t::missing_var(); }
/// Internal function used by input_common_readch to read one byte from fd 0. This function should /// only be called by input_common_readch(). static char_event_t readb() { // do_loop must be set on every path through the loop; leaving it uninitialized allows the // static analyzer to assist in catching mistakes. unsigned char arr[1]; bool do_loop; do { // Flush callbacks. input_flush_callbacks(); fd_set fdset; int fd_max = 0; int ioport = iothread_port(); int res; FD_ZERO(&fdset); FD_SET(0, &fdset); if (ioport > 0) { FD_SET(ioport, &fdset); fd_max = std::max(fd_max, ioport); } // Get our uvar notifier. universal_notifier_t ¬ifier = universal_notifier_t::default_notifier(); // Get the notification fd (possibly none). int notifier_fd = notifier.notification_fd(); if (notifier_fd > 0) { FD_SET(notifier_fd, &fdset); fd_max = std::max(fd_max, notifier_fd); } // Get its suggested delay (possibly none). struct timeval tv = {}; const unsigned long usecs_delay = notifier.usec_delay_between_polls(); if (usecs_delay > 0) { unsigned long usecs_per_sec = 1000000; tv.tv_sec = (int)(usecs_delay / usecs_per_sec); tv.tv_usec = (int)(usecs_delay % usecs_per_sec); } res = select(fd_max + 1, &fdset, 0, 0, usecs_delay > 0 ? &tv : NULL); if (res == -1) { if (errno == EINTR || errno == EAGAIN) { if (interrupt_handler) { if (auto interrupt_evt = interrupt_handler()) { return *interrupt_evt; } else if (auto mc = lookahead_pop_evt()) { return *mc; } } do_loop = true; } else { // The terminal has been closed. return char_event_type_t::eof; } } else { // Assume we loop unless we see a character in stdin. do_loop = true; // Check to see if we want a universal variable barrier. bool barrier_from_poll = notifier.poll(); bool barrier_from_readability = false; if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset)) { barrier_from_readability = notifier.notification_fd_became_readable(notifier_fd); } if (barrier_from_poll || barrier_from_readability) { env_universal_barrier(); } if (ioport > 0 && FD_ISSET(ioport, &fdset)) { iothread_service_completion(); if (auto mc = lookahead_pop_evt()) { return *mc; } } if (FD_ISSET(STDIN_FILENO, &fdset)) { if (read_blocked(0, arr, 1) != 1) { // The teminal has been closed. return char_event_type_t::eof; } // We read from stdin, so don't loop. do_loop = false; } } } while (do_loop); return arr[0]; }