/*++

Copyright (c) 2015 Minoca Corp. All Rights Reserved

Module Name:

    setids.c

Abstract:

    This module implements the logic to make setuid and friends calls work
    across all threads.

Author:

    Evan Green 1-May-2015

Environment:

    User Mode C Library

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "pthreadp.h"

//
// ---------------------------------------------------------------- Definitions
//

//
// Define the amount of time to wait for a set ID request to go through, in
// seconds.
//

#define PTHREAD_SETID_TIMEOUT 60

//
// ------------------------------------------------------ Data Type Definitions
//

/*++

Structure Description:

    This structure stores details for a setuid (and friends) request.

Members:

    SetGroups - Stores a boolean indicating if this is a set supplementary
        groups call (TRUE) or a set thread identity call.

    Fields - Stores the fields to set in the thread identity.

    Identity - Stores a pointer to the thread identity to set.

    Groups - Stores a pointer to an array of supplementary group IDs to set.

    GroupCount - Stores the number of elements in the supplementary group ID
        array.

    Thread - Stores the thread the request is directed to.

    Mutex - Stores the mutex guarding the condition.

    Condition - Stores the condition variable.

--*/

typedef struct _PTHREAD_SETID_REQUEST {
    BOOL SetGroups;
    ULONG Fields;
    PTHREAD_IDENTITY Identity;
    PGROUP_ID Groups;
    UINTN GroupCount;
    pthread_t Thread;
    pthread_mutex_t Mutex;
    pthread_cond_t Condition;
} PTHREAD_SETID_REQUEST, *PPTHREAD_SETID_REQUEST;

//
// ----------------------------------------------- Internal Function Prototypes
//

VOID
ClpExecuteSetIdRequest (
    PPTHREAD_SETID_REQUEST Request
    );

//
// -------------------------------------------------------------------- Globals
//

PPTHREAD_SETID_REQUEST ClSetIdRequest;

//
// ------------------------------------------------------------------ Functions
//

VOID
ClpSetThreadIdentityOnAllThreads (
    ULONG Fields,
    PTHREAD_IDENTITY Identity
    )

/*++

Routine Description:

    This routine uses a signal to set the thread identity on all threads
    except the current one (which is assumed to have already been set).

Arguments:

    Fields - Supplies the bitfield of identity fields to set. See
        THREAD_IDENTITY_FIELD_* definitions.

    Identity - Supplies a pointer to the thread identity information to set.

Return Value:

    None.

--*/

{

    PTHREAD_SETID_REQUEST Request;

    //
    // If threading's not been fired up, nothing needs to be done.
    //

    if (ClThreadList.Next == NULL) {
        return;
    }

    RtlZeroMemory(&Request, sizeof(PTHREAD_SETID_REQUEST));
    Request.SetGroups = FALSE;
    Request.Fields = Fields;
    Request.Identity = Identity;
    ClpExecuteSetIdRequest(&Request);
    return;
}

VOID
ClpSetSupplementaryGroupsOnAllThreads (
    PGROUP_ID GroupIds,
    UINTN GroupIdCount
    )

/*++

Routine Description:

    This routine uses a signal to set the supplementary groups on all threads
    except the current one (which is assumed to have already been set).

Arguments:

    GroupIds - Supplies a pointer to the array of group IDs to set.

    GroupIdCount - Supplies the number of elements in the group ID array.

Return Value:

    None.

--*/

{

    PTHREAD_SETID_REQUEST Request;

    //
    // If threading's not been fired up, nothing needs to be done.
    //

    if (ClThreadList.Next == NULL) {
        return;
    }

    RtlZeroMemory(&Request, sizeof(PTHREAD_SETID_REQUEST));
    Request.SetGroups = TRUE;
    Request.Groups = GroupIds;
    Request.GroupCount = GroupIdCount;
    ClpExecuteSetIdRequest(&Request);
    return;
}

void
ClpSetIdSignalHandler (
    int Signal
    )

/*++

Routine Description:

    This routine is a signal handler called to fix up the user identity on a
    thread.

Arguments:

    Signal - Supplies the signal that caused this handler to be invoked.

Return Value:

    None.

--*/

{

    PPTHREAD_SETID_REQUEST Request;

    Request = ClSetIdRequest;

    //
    // Ignore spurious requests. Note that this is not foolproof, as the
    // request might be set now but be destroyed in just a moment if a request
    // is not actually going through.
    //

    if ((Request == NULL) || (Request->Thread != pthread_self())) {
        abort();
    }

    if (Request->SetGroups != FALSE) {
        OsSetSupplementaryGroups(TRUE, Request->Groups, &(Request->GroupCount));

    } else {
        OsSetThreadIdentity(Request->Fields, Request->Identity);
    }

    pthread_mutex_lock(&(Request->Mutex));
    Request->Thread = 0;
    pthread_cond_signal(&(Request->Condition));
    pthread_mutex_unlock(&(Request->Mutex));
    return;
}

//
// --------------------------------------------------------- Internal Functions
//

VOID
ClpExecuteSetIdRequest (
    PPTHREAD_SETID_REQUEST Request
    )

/*++

Routine Description:

    This routine uses a signal to set the thread identity on all threads
    except the current one (which is assumed to have already been set).

Arguments:

    Request - Supplies a pointer to the request to execute.

Return Value:

    None.

--*/

{

    PLIST_ENTRY CurrentEntry;
    KSTATUS KernelStatus;
    PPTHREAD Self;
    int Status;
    PPTHREAD Thread;
    struct timespec Timeout;

    pthread_mutex_init(&(Request->Mutex), NULL);
    pthread_mutex_lock(&(Request->Mutex));
    Self = (PPTHREAD)pthread_self();
    pthread_mutex_lock(&ClThreadListMutex);
    ClSetIdRequest = Request;
    CurrentEntry = ClThreadList.Next;
    while (CurrentEntry != &ClThreadList) {
        Thread = LIST_VALUE(CurrentEntry, PTHREAD, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        if (Thread == Self) {
            continue;
        }

        Request->Thread = (pthread_t)Thread;

        //
        // Fire off the request. Allow for the possibility that the thread has
        // died, which is okay.
        //

        KernelStatus = OsSendSignal(SignalTargetThread,
                                    Thread->ThreadId,
                                    SIGNAL_SETID,
                                    SIGNAL_CODE_USER,
                                    0);

        if (KernelStatus == STATUS_NO_SUCH_THREAD) {
            continue;
        }

        if (!KSUCCESS(KernelStatus)) {
            fprintf(stderr,
                    "Error: Failed to signal thread %x: %x\n",
                    Thread,
                    KernelStatus);

            abort();
        }

        clock_gettime(CLOCK_REALTIME, &Timeout);
        Timeout.tv_sec += PTHREAD_SETID_TIMEOUT;
        while (Request->Thread == (pthread_t)Thread) {
            Status = pthread_cond_timedwait(&(Request->Condition),
                                            &(Request->Mutex),
                                            &Timeout);

            if (Status == ETIMEDOUT) {
                fprintf(stderr,
                        "Error: Thread %x failed to respond to set ID "
                        "request.\n",
                        Thread);

                abort();
            }
        }
    }

    ClSetIdRequest = NULL;
    pthread_mutex_unlock(&ClThreadListMutex);
    pthread_mutex_unlock(&(Request->Mutex));
    pthread_mutex_destroy(&(Request->Mutex));
    return;
}

