reactos/drivers/filesystems/udfs/Include/user_lib.cpp

900 lines
23 KiB
C++
Raw Normal View History

////////////////////////////////////////////////////////////////////
// Copyright (C) Alexander Telyatnikov, Ivan Keliukh, Yegor Anchishkin, SKIF Software, 1999-2013. Kiev, Ukraine
// All rights reserved
// This file was released under the GPLv2 on June 2015.
////////////////////////////////////////////////////////////////////
/*************************************************************************
*
* File: user_lib.cpp
*
* Module: User-mode library
*
* Description: common useful user-mode functions
*
* Author: Ivan
*
*************************************************************************/
#ifndef __USER_LIB_CPP__
#define __USER_LIB_CPP__
#include "user_lib.h"
TCHAR* MediaTypeStrings[] =
{
"CD-ROM" ,
"CD-R" ,
"CD-RW" ,
"DVD-ROM" ,
"DVD-RAM" ,
"DVD-R" ,
"DVD-RW" ,
"DVD+R" ,
"DVD+RW" ,
"DD CD-ROM" ,
"DD CD-R" ,
"DD CD-RW" ,
"BD-ROM" ,
"BD-RE" ,
"[BUSY]" ,
"Unknown"
};
void * __cdecl mymemchr (
const void * buf,
int chr,
size_t cnt
)
{
while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) {
buf = (unsigned char *)buf + 1;
cnt--;
}
return(cnt ? (void *)buf : NULL);
} // end mymemchr()
char * __cdecl mystrrchr(
const char * string,
int ch
)
{
char *start = (char *)string;
while (*string++) {;}
while (--string != start && *string != (char)ch) {;}
if (*string == (char)ch) {
return( (char *)string );
}
return(NULL);
} // end mystrrchr()
char * __cdecl mystrchr(
const char * string,
int ch
)
{
while (*string != (char)ch && *string != '\0' ) {
string++;
}
if (*string == (char)ch) {
return( (char *)string );
}
return(NULL);
} // end mystrchr()
int __cdecl Exist (
PCHAR path
)
{
DWORD attr;
attr = GetFileAttributes((LPTSTR)path);
if (attr == 0xffffffff) {
return 0;
}
return 1;
} // end Exist()
ULONG
MyMessageBox(
HINSTANCE hInst,
HWND hWnd,
LPCSTR pszFormat,
LPCSTR pszTitle,
UINT fuStyle,
...
)
{
CHAR szTitle[80];
CHAR szFormat[1024];
LPSTR pszMessage;
BOOL fOk;
int result;
va_list ArgList;
if (!HIWORD(pszTitle)) {
LoadString(hInst, LOWORD(pszTitle), szTitle, sizeof(szTitle)/sizeof(szTitle[0]));
pszTitle = szTitle;
}
if (!HIWORD(pszFormat)) {
// Allow this to be a resource ID
LoadString(hInst, LOWORD(pszFormat), szFormat, sizeof(szFormat)/sizeof(szFormat[0]));
pszFormat = szFormat;
}
va_start(ArgList, fuStyle);
fOk = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_STRING,
pszFormat, 0, 0, (LPTSTR)&pszMessage, 0, &ArgList);
va_end(ArgList);
if (fOk && pszMessage) {
result = MessageBox(hWnd, pszMessage, pszTitle, fuStyle | MB_SETFOREGROUND);
LocalFree(pszMessage);
} else {
return -1;
}
return result;
} // end MyMessageBox()
/// Return service status by service name.
JS_SERVICE_STATE
ServiceInfo(
LPCTSTR ServiceName
)
{
SC_HANDLE schService;
DWORD RC;
SERVICE_STATUS ssStatus;
JS_SERVICE_STATE return_value;
SC_HANDLE schSCManager;
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_ALL_ACCESS // access required
);
if (!schSCManager) {
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_CONNECT // access required
);
}
if (!schSCManager)
return JS_ERROR_STATUS;
schService = OpenService(schSCManager, ServiceName, SERVICE_QUERY_STATUS);
if (!schService) {
RC = GetLastError();
CloseServiceHandle(schSCManager);
if (RC == ERROR_SERVICE_DOES_NOT_EXIST) return JS_SERVICE_NOT_PRESENT;
else return JS_ERROR_STATUS;
}
QueryServiceStatus(schService, &ssStatus);
if(ssStatus.dwCurrentState == SERVICE_RUNNING) {
return_value = JS_SERVICE_RUNNING;
} else {
return_value = JS_SERVICE_NOT_RUNNING;
}
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return return_value;
} // end ServiceInfo()
BOOL
CheckCdrwFilter(
BOOL ReInstall
)
{
char CdromUpperFilters[1024];
bool found = false;
if (LOBYTE(LOWORD(GetVersion())) < 5) {
return true;
}
if (GetRegString(CDROM_CLASS_PATH,REG_UPPER_FILTER_NAME,&CdromUpperFilters[0],arraylen(CdromUpperFilters))) {
char *token = &CdromUpperFilters[0];
while (*token) {
if (!strcmp(token,CDRW_SERVICE)) {
found = true;
break;
}
token += strlen(token)+1;
}
if (!found) {
memcpy(token,CDRW_SERVICE,sizeof(CDRW_SERVICE));
*(token+sizeof(CDRW_SERVICE)) = '\0';
*(token+sizeof(CDRW_SERVICE)+1) = '\0';
if(ReInstall) {
RegisterString(CDROM_CLASS_PATH,REG_UPPER_FILTER_NAME,&CdromUpperFilters[0],TRUE,token-&CdromUpperFilters[0]+sizeof(CDRW_SERVICE)+1);
found = true;
}
}
} else {
memcpy(CdromUpperFilters,CDRW_SERVICE,sizeof(CDRW_SERVICE));
CdromUpperFilters[sizeof(CDRW_SERVICE)] = '\0';
CdromUpperFilters[sizeof(CDRW_SERVICE)+1] = '\0';
if(ReInstall) {
RegisterString(CDROM_CLASS_PATH,REG_UPPER_FILTER_NAME,&CdromUpperFilters[0],TRUE,sizeof(CDRW_SERVICE)+1);
found = true;
}
}
return found;
} // end CheckCdrwFilter()
BOOL
RegisterString(
LPSTR pszKey,
LPSTR pszValue,
LPSTR pszData,
BOOLEAN MultiSz,
DWORD size
)
{
HKEY hKey;
DWORD dwDisposition;
// Create the key, if it exists it will be opened
if (ERROR_SUCCESS !=
RegCreateKeyEx(
HKEY_LOCAL_MACHINE, // handle of an open key
pszKey, // address of subkey name
0, // reserved
NULL, // address of class string
REG_OPTION_NON_VOLATILE, // special options flag
KEY_ALL_ACCESS, // desired security access
NULL, // address of key security structure
&hKey, // address of buffer for opened handle
&dwDisposition)) // address of disposition value buffer
{
return FALSE;
}
// Write the value and it's data to the key
if (ERROR_SUCCESS !=
RegSetValueEx(
hKey, // handle of key to set value for
pszValue, // address of value to set
0, // reserved
MultiSz ? REG_MULTI_SZ : REG_SZ, // flag for value type
(CONST BYTE *)pszData, // address of value data
MultiSz ? size : strlen(pszData) )) // size of value data
{
RegCloseKey(hKey);
return FALSE;
}
// Close the key
RegCloseKey(hKey);
return TRUE;
} // end RegisterString()
BOOL
RegDelString(
LPSTR pszKey,
LPSTR pszValue
)
{
HKEY hKey;
DWORD dwDisposition;
// Create the key, if it exists it will be opened
if (ERROR_SUCCESS !=
RegCreateKeyEx(
HKEY_LOCAL_MACHINE, // handle of an open key
pszKey, // address of subkey name
0, // reserved
NULL, // address of class string
REG_OPTION_NON_VOLATILE, // special options flag
KEY_ALL_ACCESS, // desired security access
NULL, // address of key security structure
&hKey, // address of buffer for opened handle
&dwDisposition)) // address of disposition value buffer
{
return FALSE;
}
// Write the value and it's data to the key
if (ERROR_SUCCESS !=
RegDeleteValue(
hKey, // handle of key to set value for
pszValue))
{
RegCloseKey(hKey);
return FALSE;
}
// Close the key
RegCloseKey(hKey);
return TRUE;
} // end RegDelString()
/// Get string from registry by Key path and Value name
BOOL
GetRegString(
LPSTR pszKey,
LPSTR pszValue,
LPSTR pszData,
DWORD dwBufSize
)
{
HKEY hKey;
DWORD dwDataSize = dwBufSize;
DWORD dwValueType = REG_SZ;
if(!dwBufSize)
return FALSE;
RegOpenKeyEx(
HKEY_LOCAL_MACHINE, // handle of open key
pszKey, // address of name of subkey to open
0, // reserved
KEY_QUERY_VALUE, // security access mask
&hKey // address of handle of open key
);
if (ERROR_SUCCESS != RegQueryValueEx(
hKey, // handle of key to query
pszValue, // address of name of value to query
0, // reserved
&dwValueType, // address of buffer for value type
(BYTE *)pszData, // address of data buffer
&dwDataSize // address of data buffer size
)) return FALSE;
if (pszData[dwDataSize-1] != '\0')
pszData[dwDataSize-1] = '\0';
return TRUE;
} // end GetRegString()
BOOL
RegisterDword(
LPSTR pszKey,
LPSTR pszValue,
DWORD dwData
)
{
HKEY hKey;
DWORD dwDisposition;
// Create the key, if it exists it will be opened
if (ERROR_SUCCESS !=
RegCreateKeyEx(
HKEY_LOCAL_MACHINE, // handle of an open key
pszKey, // address of subkey name
0, // reserved
NULL, // address of class string
REG_OPTION_NON_VOLATILE, // special options flag
KEY_ALL_ACCESS, // desired security access
NULL, // address of key security structure
&hKey, // address of buffer for opened handle
&dwDisposition)) // address of disposition value buffer
{
return FALSE;
}
// Write the value and it's data to the key
if (ERROR_SUCCESS !=
RegSetValueEx(
hKey, // handle of key to set value for
pszValue, // address of value to set
0, // reserved
REG_DWORD, // flag for value type
(CONST BYTE *)&dwData, // address of value data
4 )) // size of value data
{
RegCloseKey(hKey);
return FALSE;
}
// Close the key
RegCloseKey(hKey);
return TRUE;
} // end RegisterDword()
BOOL
GetRegUlong(
LPSTR pszKey,
LPSTR pszValue,
PULONG pszData
)
{
HKEY hKey;
DWORD dwDataSize = 4;
DWORD dwValueType = REG_DWORD;
ULONG origValue = *pszData;
if(RegOpenKeyEx(
HKEY_LOCAL_MACHINE, // handle of open key
pszKey, // address of name of subkey to open
0, // reserved
KEY_QUERY_VALUE, // security access mask
&hKey // address of handle of open key
) != ERROR_SUCCESS) {
(*pszData) = origValue;
return FALSE;
}
if(RegQueryValueEx(
hKey, // handle of key to query
pszValue, // address of name of value to query
0, // reserved
&dwValueType,// address of buffer for value type
(BYTE *)pszData, // address of data buffer
&dwDataSize // address of data buffer size
) != ERROR_SUCCESS) {
(*pszData) = origValue;
}
RegCloseKey(hKey);
return TRUE;
} // end GetRegUlong()
BOOL
SetRegUlong(
LPSTR pszKey,
LPSTR pszValue,
PULONG pszData
)
{
HKEY hKey;
DWORD dwDataSize = 4;
DWORD dwValueType = REG_DWORD;
LPVOID lpMsgBuf;
LONG RC;
if (!(ERROR_SUCCESS == (RC = RegOpenKeyEx(
HKEY_LOCAL_MACHINE, // handle of open key
pszKey, // address of name of subkey to open
0, // reserved
KEY_ALL_ACCESS , // security access mask
&hKey // address of handle of open key
)))) {
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
RC,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
// Display the string.
MessageBox( NULL, (CCHAR*)lpMsgBuf, "Error", MB_OK|MB_ICONHAND );
// Free the buffer.
LocalFree( lpMsgBuf );
return FALSE;
}
if (!(ERROR_SUCCESS == (RC = RegSetValueEx(
hKey, // handle of key to query
pszValue, // address of name of value to query
0, // reserved
REG_DWORD,// address of buffer for value type
(BYTE *)pszData, // address of data buffer
sizeof(ULONG) // data buffer size
)))) {
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
RC,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
// Display the string.
MessageBox( NULL, (CCHAR*)lpMsgBuf, "Error", MB_OK|MB_ICONHAND );
// Free the buffer.
LocalFree( lpMsgBuf );
}
RegCloseKey(hKey);
return TRUE;
} // end SetRegUlong()
BOOL
Privilege(
LPTSTR pszPrivilege,
BOOL bEnable
)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
//
// obtain the token, first check the thread and then the process
//
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, TRUE, &hToken)){
if (GetLastError() == ERROR_NO_TOKEN) {
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
}
else return FALSE;
}
//
// get the luid for the privilege
//
if (!LookupPrivilegeValue(NULL, pszPrivilege, &tp.Privileges[0].Luid))
return FALSE;
tp.PrivilegeCount = 1;
if (bEnable)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
//
// enable or disable the privilege
//
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0))
return FALSE;
if (!CloseHandle(hToken))
return FALSE;
return TRUE;
} // end Privilege()
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
BOOL IsWow64(VOID)
{
BOOL bIsWow64 = FALSE;
LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle("kernel32"),"IsWow64Process");
if (NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
return FALSE;
}
}
return bIsWow64;
} // end IsWow64()
HANDLE
CreatePublicEvent(
PWCHAR EventName
)
{
SECURITY_DESCRIPTOR sdPublic;
SECURITY_ATTRIBUTES saPublic;
InitializeSecurityDescriptor(
&sdPublic,
SECURITY_DESCRIPTOR_REVISION
);
SetSecurityDescriptorDacl(
&sdPublic,
TRUE,
NULL,
FALSE
);
saPublic.nLength = sizeof(saPublic);
saPublic.lpSecurityDescriptor = &sdPublic;
return CreateEventW(
&saPublic,
TRUE,
FALSE,
EventName);
} // end CreatePublicEvent()
/// Send Device IO Controls to undelaying level via handle
ULONG
UDFPhSendIOCTL(
IN ULONG IoControlCode,
IN HANDLE DeviceObject,
IN PVOID InputBuffer ,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer ,
IN ULONG OutputBufferLength,
IN BOOLEAN OverrideVerify,
IN PVOID Dummy
)
{
ULONG real_read;
ULONG ret;
LONG offh=0;
ULONG RC = DeviceIoControl(DeviceObject,IoControlCode,
InputBuffer,InputBufferLength,
OutputBuffer,OutputBufferLength,
&real_read,NULL);
if (!RC) {
ret = GetLastError();
}
return RC ? 1 : -1;
} // end UDFPhSendIOCTL()
CHAR RealDeviceName[MAX_PATH+1];
PCHAR
UDFGetDeviceName(
PCHAR szDeviceName
)
{
HANDLE hDevice;
WCHAR DeviceName[MAX_PATH+1];
ULONG RC;
ODS(" UDFGetDeviceName\r\n");
hDevice = CreateFile(szDeviceName, GENERIC_READ ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == ((HANDLE)-1)) {
strcpy(RealDeviceName,"");
return (PCHAR)&RealDeviceName;
}
RC = UDFPhSendIOCTL(IOCTL_CDRW_GET_DEVICE_NAME,hDevice,
&DeviceName,(MAX_PATH+1)*sizeof(WCHAR),
&DeviceName,(MAX_PATH+1)*sizeof(WCHAR), FALSE,NULL);
if(RC == 1) {
wcstombs((PCHAR)&RealDeviceName,&DeviceName[1],(USHORT)DeviceName[0]);
RealDeviceName[(USHORT)DeviceName[0]/sizeof(USHORT)] = '\0';
} else {
strcpy(RealDeviceName, szDeviceName+4);
}
CloseHandle(hDevice);
return (PCHAR)(strrchr(RealDeviceName, '\\')+1);
} // end UDFGetDeviceName()
BOOL
GetOptUlong(
PCHAR Path,
PCHAR OptName,
PULONG OptVal
)
{
if(!Path) {
return FALSE;
}
if(Path[0] && Path[1] == ':') {
CHAR SettingFile[MAX_PATH];
CHAR Setting[16];
sprintf(SettingFile, "%s\\%s", Path, UDF_CONFIG_STREAM_NAME);
GetPrivateProfileString("DiskSettings", OptName, "d", &Setting[0], 10, SettingFile);
Setting[15]=0;
if (Setting[0] != 'd') {
if(Setting[0] == '0' && Setting[1] == 'x') {
sscanf(Setting+2, "%x", OptVal);
} else {
sscanf(Setting, "%d", OptVal);
}
return TRUE;
}
return FALSE;
}
return GetRegUlong(Path, OptName, OptVal);
} // end GetOptUlong()
BOOL
SetOptUlong(
PCHAR Path,
PCHAR OptName,
PULONG OptVal
)
{
if(!Path) {
return FALSE;
}
if(Path[0] && Path[1] == ':') {
CHAR SettingFile[MAX_PATH];
CHAR Setting[16];
if(Path[2] != '\\') {
sprintf(SettingFile, "%s\\%s", Path, UDF_CONFIG_STREAM_NAME);
} else {
sprintf(SettingFile, "%s%s", Path, UDF_CONFIG_STREAM_NAME);
}
sprintf(Setting, "%d\n", (*OptVal));
return WritePrivateProfileString("DiskSettings", OptName, Setting, SettingFile);
}
return SetRegUlong(Path, OptName, OptVal);
} // end SetOptUlong()
ULONG
UDFGetOptUlongInherited(
PCHAR Drive,
PCHAR OptName,
PULONG OptVal,
ULONG CheckDepth
)
{
CHAR LocalPath[1024];
ULONG retval = 0;
ODS(" UDFGetOptUlongInherited\r\n");
if(GetOptUlong(UDF_SERVICE_PARAM_PATH, OptName, OptVal)) {
retval = UDF_OPTION_GLOBAL;
}
if(CheckDepth <= UDF_OPTION_GLOBAL) {
return retval;
}
strcpy(LocalPath,UDF_SERVICE_PARAM_PATH);
strcat(LocalPath,"\\");
strcat(LocalPath,UDFGetDeviceName(Drive));
if(GetOptUlong(LocalPath, OptName, OptVal)) {
retval = UDF_OPTION_DEVSPEC;
}
if(CheckDepth <= UDF_OPTION_DEVSPEC) {
return retval;
}
if(GetOptUlong(Drive, OptName, OptVal))
retval = UDF_OPTION_DISKSPEC;
return retval;
} // end UDFGetOptUlongInherited()
HANDLE
OpenOurVolume(
PCHAR szDeviceName
)
{
HANDLE hDevice;
// Open device volume
hDevice = CreateFile(szDeviceName, GENERIC_READ ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == ((HANDLE)-1)) {
hDevice = CreateFile(szDeviceName, GENERIC_READ ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
}
return hDevice;
} // end OpenOurVolume()
ULONG
drv_letter_to_index(
WCHAR a
)
{
if(a >= 'a' && a <= 'z') {
return a - 'a';
}
if(a >= 'A' && a <= 'Z') {
return a - 'A';
}
return -1;
} // end drv_letter_to_index()
/// Start app with desired parameters
DWORD
WINAPI
LauncherRoutine2(
LPVOID lpParameter
)
{
PCHAR ParamStr = (PCHAR)lpParameter;
STARTUPINFO proc_startup_info;
PROCESS_INFORMATION proc_info;
CHAR szLaunchPath[MAX_PATH],ErrMes[50];
INT index;
ULONG MkUdfRetCode;
CHAR szTemp[256];
GetRegString(UDF_KEY,"ToolsPath",szLaunchPath, sizeof(szLaunchPath));
SetCurrentDirectory(szLaunchPath);
strcat(szLaunchPath,"\\");
//strcat(szLaunchPath,UDFFMT);
strcat(szLaunchPath,ParamStr);
//strcpy(MkUdfStatus,"");
#ifndef TESTMODE
proc_startup_info.cb = sizeof(proc_startup_info);
proc_startup_info.lpReserved = 0;
proc_startup_info.lpReserved2 = 0;
proc_startup_info.cbReserved2 = 0;
proc_startup_info.lpDesktop = 0;
proc_startup_info.lpTitle = 0;
proc_startup_info.dwFlags = 0;
proc_startup_info.hStdInput = NULL;
proc_startup_info.hStdOutput = NULL;
proc_startup_info.hStdError = NULL;
if(CreateProcess(NULL, szLaunchPath, 0,0, TRUE, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
0,0, &proc_startup_info, &proc_info)) {
//hFmtProc[i] = proc_info.hProcess;
WaitForSingleObject(proc_info.hProcess, -1);
GetExitCodeProcess(proc_info.hProcess, &MkUdfRetCode);
index=0;
/*
while (mkudf_err_msg[index].err_code != 0xffffffff){
if (mkudf_err_msg[index].err_code == MkUdfRetCode) break;
index++;
}
*/
//strcpy(MkUdfStatus,mkudf_err_msg[index].err_msg);
CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess);
} else {
strcpy(ErrMes,"Stop: Cannot launch ");
strcat(ErrMes,szLaunchPath);
sprintf(szTemp," error %d",GetLastError());
strcat(ErrMes,szTemp);
MessageBox(NULL,ErrMes,"UDFFormat",MB_OK | MB_ICONHAND);
}
#else
MessageBox(NULL,szLaunchPath,"Message",MB_OK);
Sleep(500);
MkUdfRetCode = MKUDF_OK;
// MkUdfRetCode = MKUDF_FORMAT_REQUIRED;
// MkUdfRetCode = MKUDF_CANT_BLANK;
index = 0;
while (mkudf_err_msg[index].err_code != 0xffffffff){
if (mkudf_err_msg[index].err_code == MkUdfRetCode) break;
index++;
}
//strcpy(MkUdfStatus,mkudf_err_msg[index].err_msg);
#endif
return MkUdfRetCode;
} // end LauncherRoutine2()
#endif // __USER_LIB_CPP__