//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <ldastoolsal_config.h>

#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>

#include <cstdlib>

#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/UserInfoCache.hh"

// Instantiate generic to Singleton class methods:
// see "general" library
SINGLETON_TS_INST( LDASTools::AL::UserInfoCache );

#if !defined( _SC_GETPW_R_SIZE_MAX )
#define _SC_GETPW_R_SIZE_MAX 1024
#endif /* !defined(_SC_GETPW_R_SIZE_MAX) */

using LDASTools::AL::MemChecker;

namespace LDASTools
{
    namespace AL
    {
        //---------------------------------------------------------------------
        /// Initialize anything that is specific to the class.
        //---------------------------------------------------------------------
        UserInfoCache::UserInfoCache( ) : m_cache( ), m_names( )
        {
            init( );

            MemChecker::Append(
                singleton_suicide,
                "LDASTools::AL::UserInfoCache::singleton_suicide",
                10 );
        }

        //---------------------------------------------------------------------
        /// Release resource associated with this object
        ///
        /// \todo
        ///     Release the m_names and m_cache resource.
        ///     Need to be sure to use the baton to insure exclusive rights.
        //---------------------------------------------------------------------
        UserInfoCache::~UserInfoCache( )
        {
        }

        //---------------------------------------------------------------------
        /// Look up a user in the password database and cache the information
        /// to prevent excessive use of system calls.
        /// If the user id cannot be found in the password database,
        /// an entry is created with the user name being the string
        /// representation of the user id.
        /// \note
        ///     This call is internal and is only called if nothing is known
        ///     about the specified user id.
        //---------------------------------------------------------------------
        const UserInfoCache::UserInfo&
        UserInfoCache::create( UserInfo::uid_type Id )
        {
            MutexLock l( m_baton, __FILE__, __LINE__ );

            static int   BUF_SIZE = sysconf( _SC_GETPW_R_SIZE_MAX );
            static char* pw_buf = (char*)calloc( sizeof( char ), BUF_SIZE );

            struct passwd  pwinfo;
            struct passwd* pwptr;

            ::getpwuid_r( uid_t( Id ), &pwinfo, pw_buf, BUF_SIZE, &pwptr );
            if ( pwptr )
            {
                m_cache[ UserInfo::uid_type( pwinfo.pw_uid ) ] =
                    UserInfo( pwinfo.pw_name,
                              pwinfo.pw_passwd,
                              UserInfo::uid_type( pwinfo.pw_uid ),
                              UserInfo::gid_type( pwinfo.pw_gid ),
                              pwinfo.pw_gecos,
                              pwinfo.pw_dir,
                              pwinfo.pw_shell );
                m_names[ std::string( pwinfo.pw_name ) ] =
                    UserInfo::uid_type( pwinfo.pw_uid );
            }
            else
            {
                std::ostringstream name;
                name << Id;
                m_cache[ Id ] = UserInfo( name.str( ).c_str( ),
                                          "*",
                                          Id,
                                          UserInfo::gid_type( Id ),
                                          name.str( ).c_str( ),
                                          "/",
                                          "/bin/sh" );
                m_names[ name.str( ) ] = Id;
            }
            return m_cache[ Id ];
        }

        bool
        UserInfoCache::init( )
        {
            //---------------------------------------------------------------------
            // Loop over all entries in the password file
            //---------------------------------------------------------------------
            setpwent( ); // Rewind to start of file;
            struct passwd* cur = getpwent( ); // Get first entry
            while ( cur )
            {
                m_cache[ UserInfo::uid_type( cur->pw_uid ) ] =
                    UserInfo( cur->pw_name,
                              cur->pw_passwd,
                              UserInfo::uid_type( cur->pw_uid ),
                              UserInfo::gid_type( cur->pw_gid ),
                              cur->pw_gecos,
                              cur->pw_dir,
                              cur->pw_shell );
                m_names[ std::string( cur->pw_name ) ] =
                    UserInfo::uid_type( cur->pw_uid );
                cur = getpwent( ); // Advance to the next entry
            }
            endpwent( );
            return true;
        }

        const UserInfoCache::UserInfo&
        UserInfoCache::uid( UserInfo::uid_type Id )
        {
            {
                MutexLock l( m_baton, __FILE__, __LINE__ );

                cache_type::const_iterator pos = m_cache.find( Id );
                if ( pos != m_cache.end( ) )
                {
                    return pos->second;
                }
            }

            return create( Id );
        }
    } // namespace AL
} // namespace LDASTools
