/*++

Copyright (c) 2015 Minoca Corp. All Rights Reserved

Module Name:

    usershel.c

Abstract:

    This module implements support for the getusershell family of functions.

Author:

    Evan Green 9-Mar-2015

Environment:

    User Mode C Library

--*/

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

#include "libcp.h"
#include <assert.h>
#include <stdio.h>
#include <unistd.h>

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

#define USER_SHELLS_PATH "/etc/shells"

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

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

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

FILE *ClUserShellsFile = NULL;
char *ClUserShellsLine = NULL;
size_t ClUserShellsLineSize = 0;
UINTN ClUserShellsIndex = 0;

PSTR ClBuiltinUserShells[] = {
    "/bin/sh",
    "/bin/csh",
    NULL
};

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

LIBC_API
char *
getusershell (
    void
    )

/*++

Routine Description:

    This routine returns the next permitted user shell in the database of
    valid shells. This opens the file if necessary. This routine is neither
    thread-safe nor reentrant.

Arguments:

    None.

Return Value:

    Returns a pointer to a string containing the next shell on success. This
    buffer may be overwritten by subsequent calls to getusershell.

    NULL on failure or end-of-database.

--*/

{

    size_t LineSize;
    char *Result;

    if (ClUserShellsFile == NULL) {
        ClUserShellsFile = fopen(USER_SHELLS_PATH, "r");
        if (ClUserShellsFile == NULL) {

            assert(ClUserShellsIndex <
                   (sizeof(ClBuiltinUserShells) /
                    sizeof(ClBuiltinUserShells[0])));

            //
            // If there is no user shells file, pretend /bin/sh and /bin/csh
            // are there.
            //

            Result = ClBuiltinUserShells[ClUserShellsIndex];
            if (Result != NULL) {
                ClUserShellsIndex += 1;
            }

            return Result;
        }
    }

    //
    // Loop trying to get a valid line.
    //

    while (TRUE) {
        LineSize = getline(&ClUserShellsLine,
                           &ClUserShellsLineSize,
                           ClUserShellsFile);

        if (LineSize < 0) {

            //
            // If the file was unreadable, return the builtin shells.
            //

            if (ferror(ClUserShellsFile) != 0) {

                assert(ClUserShellsIndex <
                       (sizeof(ClBuiltinUserShells) /
                        sizeof(ClBuiltinUserShells[0])));

                Result = ClBuiltinUserShells[ClUserShellsIndex];
                if (Result != NULL) {
                    ClUserShellsIndex += 1;
                }

                return Result;
            }

            return NULL;
        }

        while ((LineSize != 0) && (isspace(ClUserShellsLine[LineSize - 1]))) {
            LineSize -= 1;
        }

        Result = ClUserShellsLine;
        Result[LineSize] = '\0';

        //
        // Get past whitespace.
        //

        while (isspace(*Result)) {
            Result += 1;
        }

        //
        // Skip any commented lines.
        //

        if ((*Result == '\0') || (*Result == '#')) {
            continue;
        }

        break;
    }

    return Result;
}

LIBC_API
void
setusershell (
    void
    )

/*++

Routine Description:

    This routine rewinds the user shells database back to the beginning.

Arguments:

    None.

Return Value:

    None.

--*/

{

    if (ClUserShellsFile != NULL) {
        if (fseek(ClUserShellsFile, 0, SEEK_SET) != 0) {
            fclose(ClUserShellsFile);
            ClUserShellsFile = NULL;
        }
    }

    ClUserShellsIndex = 0;
    return;
}

LIBC_API
void
endusershell (
    void
    )

/*++

Routine Description:

    This routine closes the permitted user shells database.

Arguments:

    None.

Return Value:

    None.

--*/

{

    if (ClUserShellsFile != NULL) {
        fclose(ClUserShellsFile);
        ClUserShellsFile = NULL;
    }

    ClUserShellsIndex = 0;
    return;
}

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

