2008-03-05 09:52:00 -05:00
|
|
|
//
|
2008-11-17 18:29:00 -05:00
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
2008-03-05 09:52:00 -05:00
|
|
|
//
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Text;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using Microsoft.Singularity;
|
|
|
|
using Microsoft.SingSharp;
|
|
|
|
using Microsoft.Singularity.Channels;
|
|
|
|
using Microsoft.Singularity.Directory;
|
|
|
|
using FileSystem.Utils;
|
|
|
|
using Microsoft.Singularity.V1.Services;
|
|
|
|
|
|
|
|
namespace System
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
[CLSCompliant(false)]
|
2008-03-05 09:52:00 -05:00
|
|
|
internal sealed class Native
|
|
|
|
{
|
|
|
|
|
|
|
|
internal static int GetFileType(IntPtr handle)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note, these are const used to extract handles, and are NOT handles.
|
|
|
|
internal const int STD_INPUT_HANDLE = -10;
|
|
|
|
internal const int STD_OUTPUT_HANDLE = -11;
|
|
|
|
internal const int STD_ERROR_HANDLE = -12;
|
|
|
|
|
|
|
|
// From WinBase.h
|
|
|
|
internal const int FILE_TYPE_DISK = 0x0001;
|
|
|
|
internal const int FILE_TYPE_CHAR = 0x0002;
|
|
|
|
internal const int FILE_TYPE_PIPE = 0x0003;
|
|
|
|
|
|
|
|
// Error codes from WinError.h
|
|
|
|
internal const int ERROR_FILE_NOT_FOUND = 0x2;
|
|
|
|
internal const int ERROR_PATH_NOT_FOUND = 0x3;
|
|
|
|
internal const int ERROR_ACCESS_DENIED = 0x5;
|
|
|
|
internal const int ERROR_INVALID_HANDLE = 0x6;
|
|
|
|
internal const int ERROR_NO_MORE_FILES = 0x12;
|
|
|
|
internal const int ERROR_NOT_READY = 0x15;
|
|
|
|
internal const int ERROR_SHARING_VIOLATION = 0x20;
|
|
|
|
internal const int ERROR_FILE_EXISTS = 0x50;
|
|
|
|
internal const int ERROR_INVALID_PARAMETER = 0x57;
|
|
|
|
internal const int ERROR_CALL_NOT_IMPLEMENTED = 0x78;
|
|
|
|
internal const int ERROR_PATH_EXISTS = 0xB7;
|
|
|
|
internal const int ERROR_FILENAME_EXCED_RANGE = 0xCE; // filename too long.
|
|
|
|
internal const int ERROR_DLL_INIT_FAILED = 0x45A;
|
|
|
|
|
|
|
|
internal const int ERROR_NOT_SUPPORTED = 50;
|
|
|
|
internal const int ERROR_DUP_NAME = 52;
|
|
|
|
internal const int ERROR_DISK_FULL = 112;
|
|
|
|
internal const int ERROR_DIR_NOT_EMPTY = 145;
|
|
|
|
internal const int ERROR_ALREADY_EXISTS = 183;
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
internal struct FILE_ATTRIBUTE_DATA {
|
|
|
|
internal int fileAttributes;
|
|
|
|
internal long ftCreationTime;
|
|
|
|
internal long ftLastAccessTime;
|
|
|
|
internal long ftLastWriteTime;
|
|
|
|
internal long fileSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static TRef<DirectoryServiceContract.Imp:Ready> m_epDS= null;
|
|
|
|
|
|
|
|
private static DirectoryServiceContract.Imp:Ready! GetDirectoryServiceContract()
|
|
|
|
{
|
|
|
|
if (m_epDS == null) {
|
|
|
|
m_epDS = new TRef<DirectoryServiceContract.Imp:Ready>(DirectoryService.NewClientEndpoint());
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_epDS.Acquire();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void ReleaseDirectoryServiceContract([Claims] DirectoryServiceContract.Imp:Ready! imp)
|
|
|
|
{
|
|
|
|
m_epDS.Release(imp);
|
|
|
|
}
|
|
|
|
|
|
|
|
// From WinBase.h
|
|
|
|
internal static int ExpandEnvironmentStrings(String lpSrc, StringBuilder lpDst, int nSize)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by System.Runtime.InteropServices.Marshal.AllocHGlobal
|
2008-11-17 18:29:00 -05:00
|
|
|
// for creation of delegates.
|
2008-03-05 09:52:00 -05:00
|
|
|
internal static IntPtr LocalAlloc(int sizetdwBytes)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by System.Runtime.InteropServices.Marshal.FreeHGlobal
|
2008-11-17 18:29:00 -05:00
|
|
|
// for destruction of delegates.
|
2008-03-05 09:52:00 -05:00
|
|
|
internal static IntPtr LocalFree(IntPtr handle)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static uint GetTempPath(int bufferLen, StringBuilder buffer)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// For GetFullPathName, the last param is a useless TCHAR**, set by native.
|
|
|
|
internal static int GetFullPathName(String path, int numBufferChars, StringBuilder! buffer)
|
|
|
|
{
|
|
|
|
#if false // uncomment to debug file access.
|
|
|
|
Console.WriteLine("GetFullPathName({0})", path);
|
|
|
|
#endif
|
|
|
|
buffer.Append(path);
|
|
|
|
return buffer.Length;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static int SearchPath(String path, String fileName, String extension, int numBufferChars, StringBuilder buffer, int[] filePart)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not use these directly, use the safe or unsafe versions above.
|
|
|
|
// The safe version does not support devices (aka if will only open
|
|
|
|
// files on disk), while the unsafe version give you the full semantic
|
|
|
|
// of the native version.
|
|
|
|
|
|
|
|
internal static IntPtr CreateFile(String lpFileName,
|
|
|
|
int dwDesiredAccess,
|
|
|
|
int dwShareMode,
|
|
|
|
int dwCreationDisposition,
|
|
|
|
int dwFlagsAndAttributes)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool CloseHandle(IntPtr handle)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool SetEndOfFile(IntPtr hFile)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static long SetFilePointer(IntPtr handle, long offset, System.IO.SeekOrigin origin)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static int GetSystemDirectory(StringBuilder sb, int length)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool GetFileTime(IntPtr hFile, long[] creationTime,
|
|
|
|
long[] lastAccessTime, long[] lastWriteTime)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool SetFileTime(IntPtr hFile, long[] creationTime,
|
|
|
|
long[] lastAccessTime, long[] lastWriteTime)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static long GetFileSize(IntPtr hFile)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool LockFile(IntPtr handle, long offset, long count)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool UnlockFile(IntPtr handle,long offset,long count)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constants from WinNT.h
|
|
|
|
internal const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
|
|
|
|
|
|
|
|
// Win32 Structs in N/Direct style
|
|
|
|
internal class FIND_DATA {
|
|
|
|
internal int dwFileAttributes = 0;
|
|
|
|
// ftCreationTime was a by-value FILETIME structure
|
|
|
|
internal long ftCreationTime = 0 ;
|
|
|
|
// ftLastAccessTime was a by-value FILETIME structure
|
|
|
|
internal long ftLastAccessTime = 0;
|
|
|
|
// ftLastWriteTime was a by-value FILETIME structure
|
|
|
|
internal long ftLastWriteTime = 0;
|
|
|
|
internal long nFileSize = 0;
|
|
|
|
internal String cFileName = null;
|
|
|
|
internal String cAlternateFileName = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool CopyFile(String src, String dst, bool failIfExists) {
|
2008-11-17 18:29:00 -05:00
|
|
|
// Modifications to try to make this work on Singularity
|
2008-03-05 09:52:00 -05:00
|
|
|
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Entering Native.CopyFile");
|
|
|
|
// Console.WriteLine("src = {0}, dst = {1}, failIfExists = {2}", src, dst, failIfExists);
|
|
|
|
// END DEBUG
|
|
|
|
ErrorCode error = ErrorCode.Unknown;
|
|
|
|
|
|
|
|
if (src == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (dst == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
DirectoryServiceContract.Imp ds = GetDirectoryServiceContract();
|
|
|
|
if (ds == null) {
|
|
|
|
throw new Exception("Unable to acquire handle to the directory");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (failIfExists) {
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Native.CopyFile: before existence check on dst");
|
|
|
|
// END DEBUG
|
|
|
|
bool good = FileUtils.FileExists(ds, dst);
|
|
|
|
if (!good) {
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("File copy failed - destination exists and overwrite not set");
|
|
|
|
// END DEBUG
|
|
|
|
ReleaseDirectoryServiceContract(ds);
|
|
|
|
return good;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Native.CopyFile: before copy");
|
|
|
|
// END DEBUG
|
|
|
|
bool ok = CopyFile(src, dst, ds, out error);
|
|
|
|
ReleaseDirectoryServiceContract(ds);
|
|
|
|
return ok;
|
|
|
|
|
|
|
|
} // end CopyFile(String src, String dst, bool failIfExists)
|
|
|
|
|
|
|
|
// true is success
|
|
|
|
internal static bool CopyFile(String! srcpath, String! destpath, DirectoryServiceContract.Imp:Ready! ds, out ErrorCode error) {
|
|
|
|
int readSize = 4096;
|
|
|
|
long readOffset = 0;
|
|
|
|
long bytesRead;
|
|
|
|
long bytesWrite;
|
|
|
|
byte [] bytes = new byte[readSize];
|
|
|
|
|
|
|
|
error = ErrorCode.Unknown;
|
|
|
|
|
|
|
|
if (!FileUtils.CreateFile(destpath, ds, out error)) {
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Native.CopyFile: destination file creation failed");
|
|
|
|
// END DEBUG
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FileContract.Imp:Ready fileImp = FileUtils.OpenFile(srcpath, ds);
|
|
|
|
if (fileImp == null) {
|
|
|
|
delete fileImp;
|
|
|
|
error = ErrorCode.NotFound;
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Native.CopyFile: unable to open file " + srcpath);
|
|
|
|
// END DEBUG
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FileContract.Imp:Ready fileOut = FileUtils.OpenFile(destpath, ds, out error);
|
|
|
|
if (fileOut == null) {
|
|
|
|
delete fileOut;
|
|
|
|
// FOR DEBUG
|
|
|
|
//Console.WriteLine("Native.CopyFile: unable to open file " + destpath + ". Reason=" + SdsUtils.ErrorCodeToString(error));
|
|
|
|
// END DEBUG
|
|
|
|
delete fileImp;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
bytesRead = FileUtils.Read(fileImp, 0, readSize, readOffset, bytes);
|
|
|
|
bytesWrite = FileUtils.Write(fileOut, 0, (int)bytesRead, readOffset, bytes);
|
|
|
|
if (bytesRead < readSize) { // error or done with the file
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (bytesWrite < bytesRead) { // error on write
|
|
|
|
// FOR DEBUG
|
|
|
|
// Console.WriteLine("Native.CopyFile: error writing " + destpath);
|
|
|
|
// END DEBUG
|
|
|
|
delete fileImp;
|
|
|
|
delete fileOut;
|
|
|
|
error = ErrorCode.Unknown;
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
readOffset += bytesRead; // if we got to this point, increment the offset...
|
|
|
|
} while (true);
|
|
|
|
delete fileImp;
|
|
|
|
delete fileOut;
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} // end CopyFile (src, dst, ds, out error)
|
|
|
|
|
|
|
|
internal static bool CreateDirectory(
|
|
|
|
String path, int lpSecurityAttributes)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
#if false
|
|
|
|
internal static bool DeleteFile(String path)
|
|
|
|
{
|
|
|
|
ErrorCode error;
|
|
|
|
if (path == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
DirectoryServiceContract.Imp ds = GetDirectoryServiceContract();
|
|
|
|
bool ok = DeleteFile(path, ds, out error);
|
|
|
|
ReleaseDirectoryServiceContract(ds);
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
internal static bool DeleteFile(String path, out ErrorCode error) {
|
|
|
|
error = ErrorCode.Unknown;
|
|
|
|
if (path == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
DirectoryServiceContract.Imp ds = GetDirectoryServiceContract();
|
|
|
|
bool ok = DeleteFile(path, ds, out error);
|
|
|
|
ReleaseDirectoryServiceContract(ds);
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool DeleteFile(String! path, DirectoryServiceContract.Imp:Ready! ds, out ErrorCode error)
|
|
|
|
{
|
|
|
|
FileUtils.DeleteFile(path, ds, out error);
|
|
|
|
bool ok = (error == ErrorCode.NoError);
|
|
|
|
if (!ok) {
|
2008-11-17 18:29:00 -05:00
|
|
|
DebugStub.WriteLine(" File ({0}) delete failed. reason:{1}",
|
|
|
|
__arglist(path, SdsUtils.ErrorCodeToString(error)) );
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool FindClose(IntPtr hndFindFile)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static IntPtr FindFirstFile(String pFileName,
|
|
|
|
out FIND_DATA pFindFileData)
|
|
|
|
{
|
|
|
|
pFindFileData = null;
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool FindNextFile(IntPtr hndFindFile,
|
|
|
|
out FIND_DATA lpFindFileData)
|
|
|
|
{
|
|
|
|
lpFindFileData = null;
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static int GetCurrentDirectory(
|
|
|
|
int nBufferLength,
|
|
|
|
StringBuilder lpBuffer)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool GetFileAttributesEx(String name,
|
|
|
|
int fileInfoLevel,
|
|
|
|
ref FILE_ATTRIBUTE_DATA lpFileInformation)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
FileAttributesRecord fileAttributes;
|
|
|
|
ErrorCode error;
|
|
|
|
|
|
|
|
if (name == null) return false;
|
|
|
|
|
|
|
|
DirectoryServiceContract.Imp ds = GetDirectoryServiceContract();
|
|
|
|
if (ds == null) {
|
|
|
|
throw new Exception("Unable to acquire handle to the directory");
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
|
|
|
|
// get extended file info
|
|
|
|
bool ok = FileUtils.GetAttributes(name, ds,
|
|
|
|
out fileAttributes, out error);
|
|
|
|
ReleaseDirectoryServiceContract(ds);
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
lpFileInformation.ftCreationTime =
|
|
|
|
fileAttributes.CreationTime;
|
|
|
|
lpFileInformation.ftLastAccessTime =
|
|
|
|
fileAttributes.LastAccessTime;
|
|
|
|
lpFileInformation.ftLastWriteTime =
|
|
|
|
fileAttributes.LastWriteTime;
|
|
|
|
lpFileInformation.fileSize =
|
|
|
|
fileAttributes.FileSize;
|
|
|
|
}
|
|
|
|
return ok;
|
2008-03-05 09:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool SetFileAttributes(String name, int attr)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static int GetLogicalDrives()
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static uint GetTempFileName(String tmpPath, String prefix, uint uniqueIdOrZero, StringBuilder tmpFileName)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool MoveFile(String src, String dst)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool RemoveDirectory(String path, out ErrorCode error)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool SetCurrentDirectory(String path)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal const int SHGFP_TYPE_CURRENT = 0; // the current (user) folder path setting
|
|
|
|
internal const int UOI_FLAGS = 1;
|
|
|
|
internal const int WSF_VISIBLE = 1;
|
|
|
|
internal const int CSIDL_APPDATA = 0x001a;
|
|
|
|
internal const int CSIDL_COMMON_APPDATA = 0x0023;
|
|
|
|
internal const int CSIDL_LOCAL_APPDATA = 0x001c;
|
|
|
|
internal const int CSIDL_COOKIES = 0x0021;
|
|
|
|
internal const int CSIDL_FAVORITES = 0x0006;
|
|
|
|
internal const int CSIDL_HISTORY = 0x0022;
|
|
|
|
internal const int CSIDL_INTERNET_CACHE = 0x0020;
|
|
|
|
internal const int CSIDL_PROGRAMS = 0x0002;
|
|
|
|
internal const int CSIDL_RECENT = 0x0008;
|
|
|
|
internal const int CSIDL_SENDTO = 0x0009;
|
|
|
|
internal const int CSIDL_STARTMENU = 0x000b;
|
|
|
|
internal const int CSIDL_STARTUP = 0x0007;
|
|
|
|
internal const int CSIDL_SYSTEM = 0x0025;
|
|
|
|
internal const int CSIDL_TEMPLATES = 0x0015;
|
|
|
|
internal const int CSIDL_DESKTOPDIRECTORY = 0x0010;
|
|
|
|
internal const int CSIDL_PERSONAL = 0x0005;
|
|
|
|
internal const int CSIDL_PROGRAM_FILES = 0x0026;
|
|
|
|
internal const int CSIDL_PROGRAM_FILES_COMMON = 0x002b;
|
|
|
|
internal const int CSIDL_DESKTOP = 0x0000;
|
|
|
|
internal const int CSIDL_DRIVES = 0x0011;
|
|
|
|
internal const int CSIDL_MYMUSIC = 0x000d;
|
|
|
|
internal const int CSIDL_MYPICTURES = 0x0027;
|
|
|
|
|
|
|
|
|
|
|
|
internal static int GetModuleFileName(IntPtr hModule, StringBuilder buffer, int length)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool GetUserName(StringBuilder lpBuffer, int[] nSize)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, StringBuilder lpszPath)
|
|
|
|
{
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static bool LookupAccountName(string machineName, string accountName, byte[] sid,
|
|
|
|
ref int sidLen, StringBuilder domainName, ref int domainNameLen, out int peUse)
|
|
|
|
{
|
|
|
|
peUse = 0;
|
|
|
|
throw new NotSupportedException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|