Пример #1
0
void env_universal_t::load_from_fd(int fd, callback_data_list_t *callbacks)
{
    ASSERT_IS_LOCKED(lock);
    assert(fd >= 0);
    /* Get the dev / inode */
    const file_id_t current_file = file_id_for_fd(fd);
    if (current_file == last_read_file)
    {
        UNIVERSAL_LOG("Sync elided based on fstat()");
    }
    else
    {
        /* Read a variables table from the file. */
        var_table_t new_vars = this->read_message_internal(fd);
        
        /* Announce changes */
        if (callbacks != NULL)
        {
            this->generate_callbacks(new_vars, callbacks);
        }
        
        /* Acquire the new variables */
        this->acquire_variables(&new_vars);
        
        last_read_file = current_file;
    }
}
Пример #2
0
bool env_universal_t::load_from_path(const wcstring &path, callback_data_list_t *callbacks)
{
    ASSERT_IS_LOCKED(lock);
    
    /* Check to see if the file is unchanged. We do this again in load_from_fd, but this avoids opening the file unnecessarily. */
    if (last_read_file != kInvalidFileID && file_id_for_path(path) == last_read_file)
    {
        UNIVERSAL_LOG("Sync elided based on fast stat()");
        return true;
    }
    
    bool result = false;
    int fd = wopen_cloexec(path, O_RDONLY);
    if (fd >= 0)
    {
        UNIVERSAL_LOG("Reading from file");
        this->load_from_fd(fd, callbacks);
        close(fd);
        result = true;
    }
    return result;
}
Пример #3
0
/* Returns true if modified variables were written, false if not. (There may still be variable changes due to other processes on a false return). */
bool env_universal_t::sync(callback_data_list_t *callbacks)
{
    UNIVERSAL_LOG("sync");
    scoped_lock locker(lock);
    /* Our saving strategy:
    
    1. Open the file, producing an fd.
    2. Lock the file (may be combined with step 1 on systems with O_EXLOCK)
    3. After taking the lock, check if the file at the given path is different from what we opened. If so, start over.
    4. Read from the file. This can be elided if its dev/inode is unchanged since the last read
    5. Open an adjacent temporary file
    6. Write our changes to an adjacent file
    7. Move the adjacent file into place via rename. This is assumed to be atomic.
    8. Release the lock and close the file
    
    Consider what happens if Process 1 and 2 both do this simultaneously. Can there be data loss? Process 1 opens the file and then attempts to take the lock. Now, either process 1 will see the original file, or process 2's new file. If it sees the new file, we're OK: it's going to read from the new file, and so there's no data loss. If it sees the old file, then process 2 must have locked it (if process 1 locks it, switch their roles). The lock will block until process 2 reaches step 7; at that point process 1 will reach step 2, notice that the file has changed, and then start over.
    
    It's possible that the underlying filesystem does not support locks (lockless NFS). In this case, we risk data loss if two shells try to write their universal variables simultaneously. In practice this is unlikely, since uvars are usually written interactively.
    
    Prior versions of fish used a hard link scheme to support file locking on lockless NFS. The risk here is that if the process crashes or is killed while holding the lock, future instances of fish will not be able to obtain it. This seems to be a greater risk than that of data loss on lockless NFS. Users who put their home directory on lockless NFS are playing with fire anyways.
    */
    const wcstring vars_path = explicit_vars_path.empty() ? default_vars_path() : explicit_vars_path;
    
    /* If we have no changes, just load */
    if (modified.empty())
    {
        this->load_from_path(vars_path, callbacks);
        return false;
    }
    
    const wcstring directory = wdirname(vars_path);
    bool success = true;
    int vars_fd = -1;
    int private_fd = -1;
    wcstring private_file_path;
    
    UNIVERSAL_LOG("Performing full sync");
    
    /* Open the file */
    if (success)
    {
        success = this->open_and_acquire_lock(vars_path, &vars_fd);
    }

    /* Read from it */
    if (success)
    {
        assert(vars_fd >= 0);
        this->load_from_fd(vars_fd, callbacks);
    }

    /* Open adjacent temporary file */
    if (success)
    {
        success = this->open_temporary_file(directory, &private_file_path, &private_fd);
    }
    
    /* Write to it */
    if (success)
    {
        assert(private_fd >= 0);
        success = this->write_to_fd(private_fd, private_file_path);
    }
    
    if (success)
    {
        /* Apply new file */
        success = this->move_new_vars_file_into_place(private_file_path, vars_path);
    }
    
    if (success)
    {
        /* Since we moved the new file into place, clear the path so we don't try to unlink it */
         private_file_path.clear();
    }
    
    /* Clean up */
    if (vars_fd >= 0)
    {
        close(vars_fd);
    }
    if (private_fd >= 0)
    {
        close(private_fd);
    }
    if (! private_file_path.empty())
    {
        wunlink(private_file_path);
    }
    
    if (success)
    {
        /* All of our modified variables have now been written out. */
        modified.clear();
    }
    
    return success;
}
Пример #4
0
void env_universal_barrier()
{
    ASSERT_IS_MAIN_THREAD();
    UNIVERSAL_LOG("BARRIER");
    message_t *msg;
    fd_set fds;
    
    if (! synchronizes_via_fishd())
    {
        env_universal_common_sync();
        return;
    }

    if (!s_env_univeral_inited || is_dead())
        return;

    barrier_reply = 0;

    /*
      Create barrier request
    */
    msg= create_message(BARRIER, 0, 0);
    msg->count=1;
    env_universal_server.unsent.push(msg);

    /*
      Wait until barrier request has been sent
    */
    debug(3, L"Create barrier");
    while (1)
    {
        try_send_all(&env_universal_server);
        check_connection();

        if (env_universal_server.unsent.empty())
            break;

        if (env_universal_server.fd == -1)
        {
            reconnect();
            debug(2, L"barrier interrupted, exiting");
            return;
        }

        FD_ZERO(&fds);
        FD_SET(env_universal_server.fd, &fds);
        select(env_universal_server.fd+1, 0, &fds, 0, 0);
    }

    /*
      Wait for barrier reply
    */
    debug(3, L"Sent barrier request");
    while (!barrier_reply)
    {
        if (env_universal_server.fd == -1)
        {
            reconnect();
            debug(2, L"barrier interrupted, exiting (2)");
            return;
        }
        FD_ZERO(&fds);
        FD_SET(env_universal_server.fd, &fds);
        select(env_universal_server.fd+1, &fds, 0, 0, 0);
        env_universal_read_all();
    }
    debug(3, L"End barrier");
}