//===-- RNBServices.cpp -----------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  Created by Christopher Friesen on 3/21/08.
//
//===----------------------------------------------------------------------===//

#include "RNBServices.h"

#include <CoreFoundation/CoreFoundation.h>
#include <libproc.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include "CFString.h"
#include <vector>
#include "DNBLog.h"
#include "MacOSX/CFUtils.h"

// For now only SpringBoard has a notion of "Applications" that it can list for us.
// So we have to use the SpringBoard API's here.
#if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
#include <SpringBoardServices/SpringBoardServices.h>
#endif

// From DNB.cpp
size_t GetAllInfos (std::vector<struct kinfo_proc>& proc_infos);

int
GetProcesses (CFMutableArrayRef plistMutableArray, bool all_users)
{
    if (plistMutableArray == NULL)
        return -1;

    // Running as root, get all processes
    std::vector<struct kinfo_proc> proc_infos;
    const size_t num_proc_infos = GetAllInfos(proc_infos);
    if (num_proc_infos > 0)
    {
        const pid_t our_pid = getpid();
        const uid_t our_uid = getuid();
        uint32_t i;
        CFAllocatorRef alloc = kCFAllocatorDefault;

        for (i=0; i<num_proc_infos; i++)
        {
            struct kinfo_proc &proc_info = proc_infos[i];
            
            bool kinfo_user_matches;
            // Special case, if lldb is being run as root we can attach to anything.
            if (all_users)
                kinfo_user_matches = true;
            else
                kinfo_user_matches = proc_info.kp_eproc.e_pcred.p_ruid == our_uid;
            

            const pid_t pid = proc_info.kp_proc.p_pid;
            // Skip zombie processes and processes with unset status
            if (kinfo_user_matches == false             || // User is acceptable
                pid == our_pid                          || // Skip this process
                pid == 0                                || // Skip kernel (kernel pid is zero)
                proc_info.kp_proc.p_stat == SZOMB       || // Zombies are bad, they like brains...
                proc_info.kp_proc.p_flag & P_TRACED     || // Being debugged?
                proc_info.kp_proc.p_flag & P_WEXIT      || // Working on exiting?
                proc_info.kp_proc.p_flag & P_TRANSLATED)   // Skip translated ppc (Rosetta)
                continue;
            
            // Create a new mutable dictionary for each application
            CFReleaser<CFMutableDictionaryRef> appInfoDict (::CFDictionaryCreateMutable (alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
            
            // Get the process id for the app (if there is one)
            const int32_t pid_int32 = pid;
            CFReleaser<CFNumberRef> pidCFNumber (::CFNumberCreate (alloc,  kCFNumberSInt32Type, &pid_int32));
            ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PID_KEY, pidCFNumber.get());
            
            // Set the a boolean to indicate if this is the front most
            ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, kCFBooleanFalse);
            
            const char *pid_basename = proc_info.kp_proc.p_comm;
            char proc_path_buf[PATH_MAX];
            
            int return_val = proc_pidpath (pid, proc_path_buf, PATH_MAX);
            if (return_val > 0)
            {
                // Okay, now search backwards from that to see if there is a
                // slash in the name.  Note, even though we got all the args we don't care
                // because the list data is just a bunch of concatenated null terminated strings
                // so strrchr will start from the end of argv0.
                
                pid_basename = strrchr(proc_path_buf, '/');
                if (pid_basename)
                {
                    // Skip the '/'
                    ++pid_basename;
                }
                else
                {
                    // We didn't find a directory delimiter in the process argv[0], just use what was in there
                    pid_basename = proc_path_buf;
                }
                CFString cf_pid_path (proc_path_buf);
                if (cf_pid_path.get())
                    ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PATH_KEY, cf_pid_path.get());
            }

            if (pid_basename && pid_basename[0])
            {
                CFString pid_name (pid_basename);
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_DISPLAY_NAME_KEY, pid_name.get());
            }
            
            // Append the application info to the plist array
            ::CFArrayAppendValue (plistMutableArray, appInfoDict.get());
        }
    }
    return 0;
}
int
ListApplications(std::string& plist, bool opt_runningApps, bool opt_debuggable)
{
    int result = -1;
    
    CFAllocatorRef alloc = kCFAllocatorDefault;
    
    // Create a mutable array that we can populate. Specify zero so it can be of any size.
    CFReleaser<CFMutableArrayRef> plistMutableArray (::CFArrayCreateMutable (alloc, 0, &kCFTypeArrayCallBacks));

    const uid_t our_uid = getuid();

#if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
    
    if (our_uid == 0)
    {
        bool all_users = true;
        result = GetProcesses (plistMutableArray.get(), all_users);
    }
    else
    {
        CFReleaser<CFStringRef> sbsFrontAppID (::SBSCopyFrontmostApplicationDisplayIdentifier ());
        CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable));

        // Need to check the return value from SBSCopyApplicationDisplayIdentifiers.
        CFIndex count = sbsAppIDs.get() ? ::CFArrayGetCount (sbsAppIDs.get()) : 0;
        CFIndex i = 0;
        for (i = 0; i < count; i++)
        {
            CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i);

            // Create a new mutable dictionary for each application
            CFReleaser<CFMutableDictionaryRef> appInfoDict (::CFDictionaryCreateMutable (alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));

            // Get the process id for the app (if there is one)
            pid_t pid = INVALID_NUB_PROCESS;
            if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &pid) == true)
            {
                CFReleaser<CFNumberRef> pidCFNumber (::CFNumberCreate (alloc,  kCFNumberSInt32Type, &pid));
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PID_KEY, pidCFNumber.get());
            }

            // Set the a boolean to indicate if this is the front most
            if (sbsFrontAppID.get() && displayIdentifier && (::CFStringCompare (sbsFrontAppID.get(), displayIdentifier, 0) == kCFCompareEqualTo))
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, kCFBooleanTrue);
            else
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, kCFBooleanFalse);


            CFReleaser<CFStringRef> executablePath (::SBSCopyExecutablePathForDisplayIdentifier (displayIdentifier));
            if (executablePath.get() != NULL)
            {
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PATH_KEY, executablePath.get());
            }

            CFReleaser<CFStringRef> iconImagePath (::SBSCopyIconImagePathForDisplayIdentifier (displayIdentifier)) ;
            if (iconImagePath.get() != NULL)
            {
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_ICON_PATH_KEY, iconImagePath.get());
            }

            CFReleaser<CFStringRef> localizedDisplayName (::SBSCopyLocalizedApplicationNameForDisplayIdentifier (displayIdentifier));
            if (localizedDisplayName.get() != NULL)
            {
                ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_DISPLAY_NAME_KEY, localizedDisplayName.get());
            }

            // Append the application info to the plist array
            ::CFArrayAppendValue (plistMutableArray.get(), appInfoDict.get());
        }
    }
#else // #if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
    // When root, show all processes
    bool all_users = (our_uid == 0);
    GetProcesses (plistMutableArray.get(), all_users);
#endif
    
    CFReleaser<CFDataRef> plistData (::CFPropertyListCreateXMLData (alloc, plistMutableArray.get()));
    
    // write plist to service port
    if (plistData.get() != NULL)
    {
        CFIndex size = ::CFDataGetLength (plistData.get());
        const UInt8 *bytes = ::CFDataGetBytePtr (plistData.get());
        if (bytes != NULL && size > 0)
        {
            plist.assign((char *)bytes, size);
            return 0;   // Success
        }
        else
        {
            DNBLogError("empty application property list.");
            result = -2;
        }
    }
    else
    {
        DNBLogError("serializing task list.");
        result = -3;
    }
    
    return result;

}
