Author: fireball
Date: Mon Apr 18 21:48:19 2011
New Revision: 51393

URL: http://svn.reactos.org/svn/reactos?rev=51393&view=rev
Log:
Sam Arun Raj Seeniraj:

[KERNEL32]
- DefineDosDeviceW() is implemented and calls into csrss.exe 
CsrDefineDosDevice().
- Fixed a minor bug in DefineDosDeviceA(), as calling 
RtlCreateUnicodeStringFromAsciiz() prevented NULL lpTargetPath to be passed 
down to DefineDosDeviceW().
- Fixed a minor bug in QueryDosDeviceW() that causes lpTargetPath buffer to be 
returned with NULL string terminator placed at the wrong point in the buffer.

[WIN32CSR.DLL]
- Implemented CsrDefineDosDevice() in win32csr.dll, the symbolic links are 
created in global name space currently.

[SUBST.EXE]
- Implemented a subst.exe clone.
See issue #993 for more details.

Added:
    trunk/reactos/base/system/subst/
    trunk/reactos/base/system/subst/subst.c   (with props)
    trunk/reactos/base/system/subst/subst.rbuild   (with props)
    trunk/reactos/base/system/subst/subst.rc   (with props)
Modified:
    trunk/reactos/base/system/system.rbuild
    trunk/reactos/boot/bootdata/packages/reactos.dff
    trunk/reactos/dll/win32/kernel32/file/dosdev.c
    trunk/reactos/include/reactos/subsys/csrss/csrss.h
    trunk/reactos/subsystems/win32/csrss/win32csr/dllmain.c
    trunk/reactos/subsystems/win32/csrss/win32csr/file.c
    trunk/reactos/subsystems/win32/csrss/win32csr/file.h

Added: trunk/reactos/base/system/subst/subst.c
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/base/system/subst/subst.c?rev=51393&view=auto
==============================================================================
--- trunk/reactos/base/system/subst/subst.c (added)
+++ trunk/reactos/base/system/subst/subst.c [iso-8859-1] Mon Apr 18 21:48:19 
2011
@@ -1,0 +1,257 @@
+/* PROJECT:         ReactOS Kernel
+ * LICENSE:         GPL - See COPYING in the top level directory
+ * FILE:            base/system/subst/subst.c
+ * PURPOSE:         Associates a path with a drive letter
+ * PROGRAMMERS:     Sam Arun Raj
+ */
+
+/* INCLUDES *****************************************************************/
+
+#define LEAN_AND_MEAN
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+#include <tchar.h>
+#define NDEBUG
+#include <debug.h>
+
+/* FUNCTIONS ****************************************************************/
+
+void PrintError(DWORD ErrCode)
+{
+    TCHAR *buffer = (TCHAR*) calloc(2048,
+                                    sizeof(TCHAR));
+    TCHAR *msg = NULL;
+
+    if (buffer)
+    {
+        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
FORMAT_MESSAGE_FROM_SYSTEM,
+                      NULL,
+                      ErrCode,
+                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                      (TCHAR*)&msg,
+                      0,
+                      NULL);
+        _sntprintf(buffer,
+                   2048,
+                   _T("Failed with error code 0x%x: %s\n"),
+                   ErrCode,
+                   msg);
+        _tprintf(_T("%s"),
+                 buffer);
+        if (msg)
+            LocalFree(msg);
+        free(buffer);
+    }
+}
+
+void DisplaySubstUsage()
+{
+    _tprintf(_T("Associates a path with a drive letter.\n\n"));
+    _tprintf(_T("SUBST [drive1: [drive2:]path]\n"));
+    _tprintf(_T("SUBST drive1: /D\n\n"));
+    _tprintf(_T("   drive1:        Specifies a virtual drive to which you want 
to assign a path.\n"));
+    _tprintf(_T("   [drive2:]path  Specifies a physical drive and path you 
want to assign to\n"));
+    _tprintf(_T("                  a virtual drive.\n"));
+    _tprintf(_T("   /D             Deletes a substituted (virtual) 
drive.\n\n"));
+    _tprintf(_T("Type SUBST with no parameters to display a list of current 
virtual drives.\n"));
+}
+
+BOOLEAN IsSubstedDrive(TCHAR *Drive)
+{
+    BOOLEAN Result = FALSE;
+    LPTSTR lpTargetPath = NULL;
+    DWORD CharCount, dwSize;
+
+    if (_tcslen(Drive) > 2)
+        return FALSE;
+
+    dwSize = sizeof(TCHAR) * MAX_PATH;
+    lpTargetPath = (LPTSTR) malloc(sizeof(TCHAR) * MAX_PATH);
+    if ( lpTargetPath)
+    {
+        CharCount = QueryDosDevice(Drive,
+                                   lpTargetPath,
+                                   dwSize / sizeof(TCHAR));
+        while (! CharCount &&
+               GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+        {
+            free(lpTargetPath);
+            dwSize *= 2;
+            lpTargetPath = (LPTSTR) malloc(dwSize);
+            if (lpTargetPath)
+            {
+                CharCount = QueryDosDevice(Drive,
+                                           lpTargetPath,
+                                           dwSize / sizeof(TCHAR));
+            }
+        }
+
+        if (CharCount)
+        {
+            if ( _tcsncmp(lpTargetPath, _T("\\??\\"), 4) == 0 &&
+                ( (lpTargetPath[4] >= _T('A') &&
+                lpTargetPath[4] <= _T('Z')) ||
+                 (lpTargetPath[4] >= _T('a') &&
+                lpTargetPath[4] <= _T('z')) ) )
+            {
+                Result = TRUE;
+            }
+        }
+        free(lpTargetPath);
+    }
+    return Result;
+}
+
+void DumpSubstedDrives()
+{
+    TCHAR Drive[3] = _T("A:");
+    LPTSTR lpTargetPath = NULL;
+    DWORD CharCount, dwSize;
+    INT i = 0;
+
+    dwSize = sizeof(TCHAR) * MAX_PATH;
+    lpTargetPath = (LPTSTR) malloc(sizeof(TCHAR) * MAX_PATH);
+    if (! lpTargetPath)
+        return;
+
+    while (i < 26)
+    {
+        Drive[0] = _T('A') + i;
+        CharCount = QueryDosDevice(Drive,
+                                   lpTargetPath,
+                                   dwSize / sizeof(TCHAR));
+        while (! CharCount &&
+               GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+        {
+            free(lpTargetPath);
+            dwSize *= 2;
+            lpTargetPath = (LPTSTR) malloc(dwSize);
+            if (lpTargetPath)
+            {
+                CharCount = QueryDosDevice(Drive,
+                                           lpTargetPath,
+                                           dwSize / sizeof(TCHAR));
+            }
+        }
+
+        if (! CharCount)
+        {
+            i++;
+            continue;
+        }
+        else
+        {
+            if ( _tcsncmp(lpTargetPath, _T("\\??\\"), 4) == 0 &&
+                ( (lpTargetPath[4] >= _T('A') &&
+                lpTargetPath[4] <= _T('Z')) ||
+                 (lpTargetPath[4] >= _T('a') &&
+                lpTargetPath[4] <= _T('z')) ) )
+            {
+                _tprintf(_T("%s\\: => %s\n"),
+                         Drive,
+                         lpTargetPath + 4);
+            }
+        }
+        i++;
+    }
+    free(lpTargetPath);
+}
+
+int DeleteSubst(TCHAR* Drive)
+{
+    BOOL Result;
+
+    if (_tcslen(Drive) > 2)
+    {
+        _tprintf(_T("Invalid parameter - %s\n"),
+                 Drive);
+        return 1;
+    }
+
+    if (! IsSubstedDrive(Drive))
+    {
+        _tprintf(_T("Invalid Parameter - %s\n"),
+                Drive);
+        return 1;
+    }
+
+    Result = DefineDosDevice(DDD_REMOVE_DEFINITION,
+                             Drive,
+                             NULL);
+    if (! Result)
+    {
+        PrintError(GetLastError());
+        return 1;
+    }
+    return 0;
+}
+
+int AddSubst(TCHAR* Drive, TCHAR *Path)
+{
+    BOOL Result;
+
+    if (_tcslen(Drive) > 2)
+    {
+        _tprintf(_T("Invalid parameter - %s\n"),
+                 Drive);
+        return 1;
+    }
+
+    if (IsSubstedDrive(Drive))
+    {
+        _tprintf(_T("Drive already SUBSTed\n"));
+        return 1;
+    }
+
+    Result = DefineDosDevice(0,
+                             Drive,
+                             Path);
+    if (! Result)
+    {
+        PrintError(GetLastError());
+        return 1;
+    }
+    return 0;
+}
+
+int _tmain(int argc, TCHAR* argv[])
+{
+    INT i;
+
+    for (i = 0; i < argc; i++)
+    {
+        if (!_tcsicmp(argv[i], _T("/?")))
+        {
+            DisplaySubstUsage();
+            return 0;
+        }
+    }
+
+    if (argc < 3)
+    {
+        if (argc >= 2)
+        {
+            _tprintf(_T("Invalid parameter - %s\n"),
+                     argv[1]);
+            return 1;
+        }
+        DumpSubstedDrives();
+        return 0;
+    }
+
+    if (argc > 3)
+    {
+        _tprintf(_T("Incorrect number of parameters - %s\n"),
+                 argv[3]);
+        return 1;
+    }
+
+    if (! _tcsicmp(argv[1], _T("/D")))
+        return DeleteSubst(argv[2]);
+    if (! _tcsicmp(argv[2], _T("/D")))
+        return DeleteSubst(argv[1]);
+    return AddSubst(argv[1], argv[2]);
+}
+
+/* EOF */

Propchange: trunk/reactos/base/system/subst/subst.c
------------------------------------------------------------------------------
    svn:eol-style = native

Added: trunk/reactos/base/system/subst/subst.rbuild
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/base/system/subst/subst.rbuild?rev=51393&view=auto
==============================================================================
--- trunk/reactos/base/system/subst/subst.rbuild (added)
+++ trunk/reactos/base/system/subst/subst.rbuild [iso-8859-1] Mon Apr 18 
21:48:19 2011
@@ -1,0 +1,9 @@
+<?xml version="1.0"?>
+<!DOCTYPE module SYSTEM "../../../tools/rbuild/project.dtd">
+<module name="subst" type="win32cui" installbase="system32" 
installname="subst.exe" >
+       <include base="ReactOS">include/reactos/wine</include>
+       <include base="subst">.</include>
+       <library>kernel32</library>
+       <file>subst.c</file>
+       <file>subst.rc</file>
+</module>

Propchange: trunk/reactos/base/system/subst/subst.rbuild
------------------------------------------------------------------------------
    svn:eol-style = native

Added: trunk/reactos/base/system/subst/subst.rc
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/base/system/subst/subst.rc?rev=51393&view=auto
==============================================================================
--- trunk/reactos/base/system/subst/subst.rc (added)
+++ trunk/reactos/base/system/subst/subst.rc [iso-8859-1] Mon Apr 18 21:48:19 
2011
@@ -1,0 +1,5 @@
+#include <windows.h>
+#define REACTOS_STR_FILE_DESCRIPTION   "ReactOS Virtual Drive Utility Version 
1.0 \0"
+#define REACTOS_STR_INTERNAL_NAME      "subst\0"
+#define REACTOS_STR_ORIGINAL_FILENAME  "subst.exe\0"
+#include <reactos/version.rc>

Propchange: trunk/reactos/base/system/subst/subst.rc
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: trunk/reactos/base/system/system.rbuild
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/base/system/system.rbuild?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/base/system/system.rbuild [iso-8859-1] (original)
+++ trunk/reactos/base/system/system.rbuild [iso-8859-1] Mon Apr 18 21:48:19 
2011
@@ -9,6 +9,9 @@
        </directory>
        <directory name="expand">
                <xi:include href="expand/expand.rbuild" />
+       </directory>
+       <directory name="subst">
+               <xi:include href="subst/subst.rbuild" />
        </directory>
        <directory name="format">
                <xi:include href="format/format.rbuild" />

Modified: trunk/reactos/boot/bootdata/packages/reactos.dff
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/boot/bootdata/packages/reactos.dff?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/boot/bootdata/packages/reactos.dff [iso-8859-1] (original)
+++ trunk/reactos/boot/bootdata/packages/reactos.dff [iso-8859-1] Mon Apr 18 
21:48:19 2011
@@ -115,6 +115,7 @@
 base\system\autochk\autochk.exe                     1
 base\system\bootok\bootok.exe                       1
 base\system\expand\expand.exe                       1
+base\system\subst\subst.exe                         1
 base\system\format\format.exe                       1
 base\system\lsass\lsass.exe                         1
 base\system\msiexec\msiexec.exe                     1

Modified: trunk/reactos/dll/win32/kernel32/file/dosdev.c
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/dll/win32/kernel32/file/dosdev.c?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/dll/win32/kernel32/file/dosdev.c [iso-8859-1] (original)
+++ trunk/reactos/dll/win32/kernel32/file/dosdev.c [iso-8859-1] Mon Apr 18 
21:48:19 2011
@@ -14,6 +14,7 @@
 #include <k32.h>
 #define NDEBUG
 #include <debug.h>
+#include <Dbt.h>
 DEBUG_CHANNEL(kernel32file);
 
 /* FUNCTIONS *****************************************************************/
@@ -29,44 +30,55 @@
     LPCSTR lpTargetPath
     )
 {
-  UNICODE_STRING DeviceNameU;
-  UNICODE_STRING TargetPathU;
-  BOOL Result;
-
-  if (!RtlCreateUnicodeStringFromAsciiz (&DeviceNameU,
-                                        (LPSTR)lpDeviceName))
-  {
-    SetLastError (ERROR_NOT_ENOUGH_MEMORY);
-    return 0;
-  }
-
-  if (!RtlCreateUnicodeStringFromAsciiz (&TargetPathU,
-                                        (LPSTR)lpTargetPath))
-  {
-    RtlFreeHeap (RtlGetProcessHeap (),
-                0,
-                DeviceNameU.Buffer);
-    SetLastError (ERROR_NOT_ENOUGH_MEMORY);
-    return 0;
-  }
-
-  Result = DefineDosDeviceW (dwFlags,
-                            DeviceNameU.Buffer,
-                            TargetPathU.Buffer);
-
-  RtlFreeHeap (RtlGetProcessHeap (),
-              0,
-              TargetPathU.Buffer);
-  RtlFreeHeap (RtlGetProcessHeap (),
-              0,
-              DeviceNameU.Buffer);
-
-  return Result;
+    UNICODE_STRING DeviceNameU = {0};
+    UNICODE_STRING TargetPathU = {0};
+    BOOL Result;
+
+    if (lpDeviceName &&
+        ! RtlCreateUnicodeStringFromAsciiz(&DeviceNameU,
+        (LPSTR)lpDeviceName))
+    {
+        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+        return 0;
+    }
+
+    if (lpTargetPath &&
+        ! RtlCreateUnicodeStringFromAsciiz(&TargetPathU,
+        (LPSTR)lpTargetPath))
+    {
+        if (DeviceNameU.Buffer)
+        {
+            RtlFreeHeap(RtlGetProcessHeap (),
+                        0,
+                        DeviceNameU.Buffer);
+        }
+        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+        return 0;
+    }
+
+    Result = DefineDosDeviceW(dwFlags,
+                              DeviceNameU.Buffer,
+                              TargetPathU.Buffer);
+
+    if (TargetPathU.Buffer)
+    {
+        RtlFreeHeap(RtlGetProcessHeap (),
+                    0,
+                    TargetPathU.Buffer);
+    }
+
+    if (DeviceNameU.Buffer)
+    {
+        RtlFreeHeap(RtlGetProcessHeap (),
+                    0,
+                    DeviceNameU.Buffer);
+    }
+    return Result;
 }
 
 
 /*
- * @unimplemented
+ * @implemented
  */
 BOOL
 WINAPI
@@ -76,8 +88,154 @@
     LPCWSTR lpTargetPath
     )
 {
-    UNIMPLEMENTED;
-       return FALSE;
+    ULONG ArgumentCount;
+    ULONG BufferSize;
+    PCSR_CAPTURE_BUFFER CaptureBuffer;
+    CSR_API_MESSAGE Request;
+    NTSTATUS Status;
+    UNICODE_STRING NtTargetPathU;
+    UNICODE_STRING DeviceNameU;
+    UNICODE_STRING DeviceUpcaseNameU;
+    HANDLE hUser32;
+    DEV_BROADCAST_VOLUME dbcv;
+    BOOL Result = TRUE;
+    DWORD dwRecipients;
+    typedef long (WINAPI *BSM_type)(DWORD,LPDWORD,UINT,WPARAM,LPARAM);
+    BSM_type BSM_ptr;
+
+    if ( (dwFlags & 0xFFFFFFF0) ||
+        ((dwFlags & DDD_EXACT_MATCH_ON_REMOVE) &&
+        ! (dwFlags & DDD_REMOVE_DEFINITION)) )
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    ArgumentCount = 1;
+    BufferSize = 0;
+    if (! lpTargetPath)
+    {
+        RtlInitUnicodeString(&NtTargetPathU,
+                             NULL);
+    }
+    else
+    {
+        if (dwFlags & DDD_RAW_TARGET_PATH)
+        {
+            RtlInitUnicodeString(&NtTargetPathU,
+                                 lpTargetPath);
+        }
+        else
+        {
+            if (! RtlDosPathNameToNtPathName_U(lpTargetPath,
+                                               &NtTargetPathU,
+                                               0,
+                                               0))
+            {
+                WARN("RtlDosPathNameToNtPathName_U() failed\n");
+                BaseSetLastNTError(STATUS_OBJECT_NAME_INVALID);
+                return FALSE;
+            }
+        }
+        ArgumentCount = 2;
+        BufferSize += NtTargetPathU.Length;
+    }
+
+    RtlInitUnicodeString(&DeviceNameU,
+                         lpDeviceName);
+    RtlUpcaseUnicodeString(&DeviceUpcaseNameU,
+                           &DeviceNameU,
+                           TRUE);
+    BufferSize += DeviceUpcaseNameU.Length;
+
+    CaptureBuffer = CsrAllocateCaptureBuffer(ArgumentCount,
+                                             BufferSize);
+    if (! CaptureBuffer)
+    {
+        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+        Result = FALSE;
+    }
+    else
+    {
+        Request.Data.DefineDosDeviceRequest.dwFlags = dwFlags;
+
+        CsrCaptureMessageBuffer(CaptureBuffer,
+                                (PVOID)DeviceUpcaseNameU.Buffer,
+                                DeviceUpcaseNameU.Length,
+                                
(PVOID*)&Request.Data.DefineDosDeviceRequest.DeviceName.Buffer);
+
+        Request.Data.DefineDosDeviceRequest.DeviceName.Length =
+            DeviceUpcaseNameU.Length;
+        Request.Data.DefineDosDeviceRequest.DeviceName.MaximumLength =
+            DeviceUpcaseNameU.Length;
+
+        if (NtTargetPathU.Buffer)
+        {
+            CsrCaptureMessageBuffer(CaptureBuffer,
+                                    (PVOID)NtTargetPathU.Buffer,
+                                    NtTargetPathU.Length,
+                                    
(PVOID*)&Request.Data.DefineDosDeviceRequest.TargetName.Buffer);
+        }
+        Request.Data.DefineDosDeviceRequest.TargetName.Length =
+            NtTargetPathU.Length;
+        Request.Data.DefineDosDeviceRequest.TargetName.MaximumLength =
+            NtTargetPathU.Length;
+
+        Status = CsrClientCallServer(&Request,
+                                     CaptureBuffer,
+                                     MAKE_CSR_API(DEFINE_DOS_DEVICE, 
CSR_CONSOLE),
+                                     sizeof(CSR_API_MESSAGE));
+        CsrFreeCaptureBuffer(CaptureBuffer);
+
+        if (! NT_SUCCESS(Status) ||
+            ! NT_SUCCESS(Status = Request.Status))
+        {
+            WARN("CsrClientCallServer() failed (Status %lx)\n",
+                Status);
+            SetLastErrorByStatus(Status);
+            Result = FALSE;
+        }
+        else
+        {
+            if (! (dwFlags & DDD_NO_BROADCAST_SYSTEM) &&
+                DeviceUpcaseNameU.Length == 2 * sizeof(WCHAR) &&
+                DeviceUpcaseNameU.Buffer[1] == L':' &&
+                ( (DeviceUpcaseNameU.Buffer[0] - L'A') < 26 ))
+            {
+                hUser32 = LoadLibraryA("user32.dll");
+                if (hUser32)
+                {
+                    BSM_ptr = (BSM_type)
+                        GetProcAddress(hUser32, "BroadcastSystemMessageW");
+                    if (BSM_ptr)
+                    {
+                        dwRecipients = BSM_APPLICATIONS;
+                        dbcv.dbcv_size = sizeof(DEV_BROADCAST_VOLUME);
+                        dbcv.dbcv_devicetype = DBT_DEVTYP_VOLUME;
+                        dbcv.dbcv_reserved = 0;
+                        dbcv.dbcv_unitmask |= 
+                            (1 << (DeviceUpcaseNameU.Buffer[0] - L'A'));
+                        dbcv.dbcv_flags = DBTF_NET;
+                        (void) BSM_ptr(BSF_SENDNOTIFYMESSAGE | BSF_FLUSHDISK,
+                                       &dwRecipients,
+                                       WM_DEVICECHANGE,
+                                       (WPARAM)DBT_DEVICEARRIVAL,
+                                       (LPARAM)&dbcv);
+                    }
+                    FreeLibrary(hUser32);
+                }
+            }
+        }
+    }
+
+    if (NtTargetPathU.Buffer)
+    {
+        RtlFreeHeap(RtlGetProcessHeap(),
+                    0,
+                    NtTargetPathU.Buffer);
+    }
+    RtlFreeUnicodeString(&DeviceUpcaseNameU);
+    return Result;
 }
 
 
@@ -250,7 +408,7 @@
     TRACE ("TargetLength: %hu\n", UnicodeString.Length);
     TRACE ("Target: '%wZ'\n", &UnicodeString);
 
-    Length = ReturnLength / sizeof(WCHAR);
+    Length = UnicodeString.Length / sizeof(WCHAR);
     if (Length < ucchMax)
     {
       /* Append null-charcter */

Modified: trunk/reactos/include/reactos/subsys/csrss/csrss.h
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/include/reactos/subsys/csrss/csrss.h?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/include/reactos/subsys/csrss/csrss.h [iso-8859-1] (original)
+++ trunk/reactos/include/reactos/subsys/csrss/csrss.h [iso-8859-1] Mon Apr 18 
21:48:19 2011
@@ -517,6 +517,13 @@
 {
   UINT UniqueID;
 } CSRSS_GET_TEMP_FILE, *PCSRSS_GET_TEMP_FILE;
+
+typedef struct
+{
+    UNICODE_STRING DeviceName;
+    UNICODE_STRING TargetName;
+    DWORD dwFlags;
+} CSRSS_DEFINE_DOS_DEVICE, *PCSRSS_DEFINE_DOS_DEVICE;
 
 #define CSR_API_MESSAGE_HEADER_SIZE(Type)       (FIELD_OFFSET(CSR_API_MESSAGE, 
Data) + sizeof(Type))
 #define CSRSS_MAX_WRITE_CONSOLE                 (LPC_MAX_DATA_LENGTH - 
CSR_API_MESSAGE_HEADER_SIZE(CSRSS_WRITE_CONSOLE))
@@ -598,6 +605,7 @@
 #define GET_HISTORY_INFO              (0x46)
 #define SET_HISTORY_INFO              (0x47)
 #define GET_TEMP_FILE                 (0x48)
+#define DEFINE_DOS_DEVICE                        (0X49)
 
 /* Keep in sync with definition below. */
 #define CSRSS_HEADER_SIZE (sizeof(PORT_MESSAGE) + sizeof(ULONG) + 
sizeof(NTSTATUS))
@@ -680,6 +688,7 @@
         CSRSS_GET_HISTORY_INFO GetHistoryInfo;
         CSRSS_SET_HISTORY_INFO SetHistoryInfo;
         CSRSS_GET_TEMP_FILE GetTempFile;
+        CSRSS_DEFINE_DOS_DEVICE DefineDosDeviceRequest;
     } Data;
 } CSR_API_MESSAGE, *PCSR_API_MESSAGE;
 

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/dllmain.c
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/dllmain.c?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/dllmain.c [iso-8859-1] 
(original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/dllmain.c [iso-8859-1] Mon 
Apr 18 21:48:19 2011
@@ -9,11 +9,14 @@
 /* INCLUDES ******************************************************************/
 #define NDEBUG
 #include "w32csr.h"
+#include "file.h"
 #include <debug.h>
 
 /* Not defined in any header file */
 extern VOID WINAPI PrivateCsrssManualGuiCheck(LONG Check);
 extern VOID WINAPI InitializeAppSwitchHook();
+extern LIST_ENTRY DosDeviceHistory;
+extern RTL_CRITICAL_SECTION Win32CsrDefineDosDeviceCritSec;
 
 /* GLOBALS *******************************************************************/
 
@@ -88,6 +91,7 @@
     CSRSS_DEFINE_API(GET_HISTORY_INFO,             CsrGetHistoryInfo),
     CSRSS_DEFINE_API(SET_HISTORY_INFO,             CsrSetHistoryInfo),
     CSRSS_DEFINE_API(GET_TEMP_FILE,                CsrGetTempFile),
+    CSRSS_DEFINE_API(DEFINE_DOS_DEVICE,            CsrDefineDosDevice),
     { 0, 0, NULL }
 };
 
@@ -104,6 +108,10 @@
         InitializeAppSwitchHook();
     }
 
+    if (DLL_PROCESS_DETACH == dwReason)
+    {
+        CsrCleanupDefineDosDevice();
+    }
     return TRUE;
 }
 
@@ -174,6 +182,8 @@
     ServerProcs->ProcessInheritProc = Win32CsrDuplicateHandleTable;
     ServerProcs->ProcessDeletedProc = Win32CsrReleaseConsole;
 
+    Status = RtlInitializeCriticalSection(&Win32CsrDefineDosDeviceCritSec);
+    InitializeListHead(&DosDeviceHistory);
     return TRUE;
 }
 

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/file.c
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/file.c?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/file.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/file.c [iso-8859-1] Mon Apr 
18 21:48:19 2011
@@ -10,13 +10,15 @@
 /* INCLUDES ******************************************************************/
 
 #include <w32csr.h>
-
 #define NDEBUG
 #include <debug.h>
+#include "file.h"
 
 /* GLOBALS *******************************************************************/
 
 UINT CsrGetTempFileUnique;
+LIST_ENTRY DosDeviceHistory;
+RTL_CRITICAL_SECTION Win32CsrDefineDosDeviceCritSec;
 
 /* FUNCTIONS *****************************************************************/
 
@@ -31,3 +33,508 @@
 
     return STATUS_SUCCESS;
 }
+
+CSR_API(CsrDefineDosDevice)
+{
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    HANDLE LinkHandle = NULL;
+    NTSTATUS Status;
+    UNICODE_STRING DeviceName = {0};
+    UNICODE_STRING RequestDeviceName = {0};
+    UNICODE_STRING LinkTarget = {0};
+    PUNICODE_STRING RequestLinkTarget;
+    ULONG Length;
+    SID_IDENTIFIER_AUTHORITY WorldAuthority = {SECURITY_WORLD_SID_AUTHORITY};
+    SID_IDENTIFIER_AUTHORITY SystemAuthority = {SECURITY_NT_AUTHORITY};
+    PSECURITY_DESCRIPTOR SecurityDescriptor;
+    PACL Dacl;
+    PSID AdminSid;
+    PSID SystemSid;
+    PSID WorldSid;
+    ULONG SidLength;
+    PCSRSS_DOS_DEVICE_HISTORY_ENTRY HistoryEntry;
+    PLIST_ENTRY Entry;
+    PLIST_ENTRY ListHead;
+    BOOLEAN Matched, AddHistory;
+    DWORD dwFlags;
+    PWSTR lpBuffer;
+
+    DPRINT("CsrDefineDosDevice entered, Flags:%d, DeviceName:%wZ, 
TargetName:%wZ\n", 
+           Request->Data.DefineDosDeviceRequest.dwFlags,
+           &Request->Data.DefineDosDeviceRequest.DeviceName,
+           &Request->Data.DefineDosDeviceRequest.TargetName);
+
+    Matched = AddHistory = FALSE;
+    HistoryEntry = NULL;
+    AdminSid = SystemSid = WorldSid = NULL;
+    SecurityDescriptor = NULL;
+    ListHead = &DosDeviceHistory;
+    dwFlags = Request->Data.DefineDosDeviceRequest.dwFlags;
+
+    /* Validate the flags */
+    if ( (dwFlags & 0xFFFFFFF0) ||
+        ((dwFlags & DDD_EXACT_MATCH_ON_REMOVE) &&
+            ! (dwFlags & DDD_REMOVE_DEFINITION)) )
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    Status = RtlEnterCriticalSection(&Win32CsrDefineDosDeviceCritSec);
+    if (! NT_SUCCESS(Status))
+    {
+        DPRINT1("RtlEnterCriticalSection() failed (Status %lx)",
+                Status);
+        return Status;
+    }
+
+    _SEH2_TRY
+    {
+        Status =
+            RtlUpcaseUnicodeString(&RequestDeviceName,
+                                   
&Request->Data.DefineDosDeviceRequest.DeviceName,
+                                   TRUE);
+        if (! NT_SUCCESS(Status))
+            _SEH2_LEAVE;
+
+        RequestLinkTarget =
+            &Request->Data.DefineDosDeviceRequest.TargetName;
+        lpBuffer = (PWSTR) RtlAllocateHeap(Win32CsrApiHeap,
+                                           HEAP_ZERO_MEMORY,
+                                           RequestDeviceName.MaximumLength + 5 
* sizeof(WCHAR));
+        if (! lpBuffer)
+        {
+            DPRINT1("Failed to allocate memory\n");
+            Status = STATUS_NO_MEMORY;
+            _SEH2_LEAVE;
+        }
+
+        swprintf(lpBuffer,
+                 L"\\??\\%wZ",
+                 &RequestDeviceName);
+        RtlInitUnicodeString(&DeviceName,
+                             lpBuffer);
+        InitializeObjectAttributes(&ObjectAttributes,
+                                   &DeviceName,
+                                   OBJ_CASE_INSENSITIVE,
+                                   NULL,
+                                   NULL);
+        Status = NtOpenSymbolicLinkObject(&LinkHandle,
+                                          DELETE | 0x1,
+                                          &ObjectAttributes);
+        if (NT_SUCCESS(Status))
+        {
+            Status = NtQuerySymbolicLinkObject(LinkHandle,
+                                               &LinkTarget,
+                                               &Length);
+            if (! NT_SUCCESS(Status) &&
+                Status == STATUS_BUFFER_TOO_SMALL)
+            {
+                LinkTarget.Length = 0;
+                LinkTarget.MaximumLength = Length;
+                LinkTarget.Buffer = (PWSTR)
+                    RtlAllocateHeap(Win32CsrApiHeap,
+                                    HEAP_ZERO_MEMORY,
+                                    Length);
+                if (! LinkTarget.Buffer)
+                {
+                    DPRINT1("Failed to allocate memory\n");
+                    Status = STATUS_NO_MEMORY;
+                    _SEH2_LEAVE;
+                }
+
+                Status = NtQuerySymbolicLinkObject(LinkHandle,
+                                                   &LinkTarget,
+                                                   &Length);
+            }
+            
+            if (! NT_SUCCESS(Status))
+            {
+                DPRINT1("NtQuerySymbolicLinkObject(%wZ) failed (Status %lx)",
+                     &DeviceName, Status);
+                _SEH2_LEAVE;
+            }
+
+            if ((dwFlags & DDD_REMOVE_DEFINITION))
+            {
+                /* If no target name specified we remove the current symlink 
target */
+                if (RequestLinkTarget->Length == 0)
+                    Matched = TRUE;
+                else
+                {
+                    if (dwFlags & DDD_EXACT_MATCH_ON_REMOVE)
+                        Matched = ! RtlCompareUnicodeString(RequestLinkTarget,
+                                                            &LinkTarget,
+                                                            TRUE);
+                    else
+                        Matched = RtlPrefixUnicodeString(RequestLinkTarget,
+                                                         &LinkTarget,
+                                                         TRUE);
+                }
+
+                if (Matched && IsListEmpty(ListHead))
+                {
+                    /* Current symlink target macthed and there is nothing to 
revert to */
+                    RequestLinkTarget = NULL;
+                }
+                else if (Matched && ! IsListEmpty(ListHead))
+                {
+                    /* Fetch the first history entry we come across for the 
device name */
+                    /* This will become the current symlink target for the 
device name */
+                    Matched = FALSE;
+                    Entry = ListHead->Flink;
+                    while (Entry != ListHead)
+                    {
+                        HistoryEntry = (PCSRSS_DOS_DEVICE_HISTORY_ENTRY)
+                            CONTAINING_RECORD(Entry,
+                                              CSRSS_DOS_DEVICE_HISTORY_ENTRY,
+                                              Entry);
+                        Matched = 
+                            ! RtlCompareUnicodeString(&RequestDeviceName,
+                                                      &HistoryEntry->Device,
+                                                      FALSE);
+                        if (Matched)
+                        {
+                            RemoveEntryList(&HistoryEntry->Entry);
+                            RequestLinkTarget = &HistoryEntry->Target;
+                            break;
+                        }
+                        Entry = Entry->Flink;
+                        HistoryEntry = NULL;
+                    }
+
+                    /* Nothing to revert to so delete the symlink */
+                    if (! Matched)
+                        RequestLinkTarget = NULL;
+                }
+                else if (! Matched)
+                {
+                    /* Locate a previous symlink target as we did not get a 
hit earlier */
+                    /* If we find one we need to remove it */
+                    Entry = ListHead->Flink;
+                    while (Entry != ListHead)
+                    {
+                        HistoryEntry = (PCSRSS_DOS_DEVICE_HISTORY_ENTRY)
+                            CONTAINING_RECORD(Entry,
+                                              CSRSS_DOS_DEVICE_HISTORY_ENTRY,
+                                              Entry);
+                        Matched =
+                            ! RtlCompareUnicodeString(&RequestDeviceName,
+                                                      &HistoryEntry->Device,
+                                                      FALSE);
+                        if (! Matched)
+                        {
+                            HistoryEntry = NULL;
+                            Entry = Entry->Flink;
+                            continue;
+                        }
+
+                        Matched = FALSE;
+                        if (dwFlags & DDD_EXACT_MATCH_ON_REMOVE)
+                        {
+                            if (! RtlCompareUnicodeString(RequestLinkTarget,
+                                                          
&HistoryEntry->Target,
+                                                          TRUE))
+                            {
+                                Matched = TRUE;
+                            }
+                        }
+                        else if (RtlPrefixUnicodeString(RequestLinkTarget,
+                                                        &HistoryEntry->Target,
+                                                        TRUE))
+                        {
+                            Matched = TRUE;
+                        }
+
+                        if (Matched)
+                        {
+                            RemoveEntryList(&HistoryEntry->Entry);
+                            break;
+                        }
+                        Entry = Entry->Flink;
+                        HistoryEntry = NULL;
+                    }
+
+                    /* Leave existing symlink as is */
+                    if (! Matched)
+                        Status = STATUS_OBJECT_NAME_NOT_FOUND;
+                    else
+                        Status = STATUS_SUCCESS;
+                    _SEH2_LEAVE;
+                }
+            }
+            else
+            {
+                AddHistory = TRUE;
+            }
+
+            Status = NtMakeTemporaryObject(LinkHandle);
+            if (! NT_SUCCESS(Status))
+            {
+                DPRINT1("NtMakeTemporaryObject(%wZ) failed (Status %lx)",
+                     &DeviceName, Status);
+                _SEH2_LEAVE;
+            }
+
+            Status = NtClose(LinkHandle);
+            LinkHandle = NULL;
+            if (! NT_SUCCESS(Status))
+            {
+                DPRINT1("NtClose(%wZ) failed (Status %lx)",
+                     &DeviceName, Status);
+                _SEH2_LEAVE;
+            }
+        }
+
+        /* Don't create symlink if we don't have a target */
+        if (! RequestLinkTarget || RequestLinkTarget->Length == 0)
+            _SEH2_LEAVE;
+
+        if (AddHistory)
+        {
+            HistoryEntry = (PCSRSS_DOS_DEVICE_HISTORY_ENTRY)
+                RtlAllocateHeap(Win32CsrApiHeap,
+                                HEAP_ZERO_MEMORY,
+                                sizeof(CSRSS_DOS_DEVICE_HISTORY_ENTRY));
+            if (! HistoryEntry)
+            {
+                DPRINT1("Failed to allocate memory\n");
+                Status = STATUS_NO_MEMORY;
+                _SEH2_LEAVE;
+            }
+
+            HistoryEntry->Target.Buffer =
+                RtlAllocateHeap(Win32CsrApiHeap,
+                                HEAP_ZERO_MEMORY,
+                                LinkTarget.Length);
+            if (! HistoryEntry->Target.Buffer)
+            {
+                DPRINT1("Failed to allocate memory\n");
+                Status = STATUS_NO_MEMORY;
+                _SEH2_LEAVE;
+            }
+            HistoryEntry->Target.Length =
+                HistoryEntry->Target.MaximumLength =
+                    LinkTarget.Length;
+            RtlCopyUnicodeString(&HistoryEntry->Target,
+                                 &LinkTarget);
+
+            HistoryEntry->Device.Buffer =
+                RtlAllocateHeap(Win32CsrApiHeap,
+                                HEAP_ZERO_MEMORY,
+                                RequestDeviceName.Length);
+            if (! HistoryEntry->Device.Buffer)
+            {
+                DPRINT1("Failed to allocate memory\n");
+                Status = STATUS_NO_MEMORY;
+                _SEH2_LEAVE;
+            }
+            HistoryEntry->Device.Length =
+                HistoryEntry->Device.MaximumLength =
+                    RequestDeviceName.Length;
+            RtlCopyUnicodeString(&HistoryEntry->Device,
+                                 &RequestDeviceName);
+
+            /* Remember previous symlink target for this device */
+            InsertHeadList(ListHead,
+                           &HistoryEntry->Entry);
+            HistoryEntry = NULL;
+        }
+
+        RtlAllocateAndInitializeSid(&WorldAuthority,
+                                    1,
+                                    SECURITY_WORLD_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    &WorldSid);
+
+        RtlAllocateAndInitializeSid(&SystemAuthority,
+                                    1,
+                                    SECURITY_LOCAL_SYSTEM_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    &SystemSid);
+
+        RtlAllocateAndInitializeSid(&SystemAuthority,
+                                    2,
+                                    SECURITY_BUILTIN_DOMAIN_RID,
+                                    DOMAIN_ALIAS_RID_ADMINS,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    SECURITY_NULL_RID,
+                                    &AdminSid);
+
+        SidLength = RtlLengthSid(SystemSid) +
+            RtlLengthSid(AdminSid) +
+            RtlLengthSid(WorldSid);
+        Length = sizeof(ACL) + SidLength + 3 * sizeof(ACCESS_ALLOWED_ACE);
+
+        SecurityDescriptor = RtlAllocateHeap(Win32CsrApiHeap,
+                                             0,
+                                             SECURITY_DESCRIPTOR_MIN_LENGTH + 
Length);
+        if (! SecurityDescriptor)
+        {
+            DPRINT1("Failed to allocate memory\n");
+            Status = STATUS_NO_MEMORY;
+            _SEH2_LEAVE;
+        }
+
+        Dacl = (PACL)((ULONG_PTR)SecurityDescriptor + 
SECURITY_DESCRIPTOR_MIN_LENGTH);
+        Status = RtlCreateSecurityDescriptor(SecurityDescriptor,
+                                             SECURITY_DESCRIPTOR_REVISION);
+        if (! NT_SUCCESS(Status))
+        {
+            DPRINT1("RtlCreateSecurityDescriptor() failed (Status %lx)",
+                 Status);
+            _SEH2_LEAVE;
+        }
+
+        Status = RtlCreateAcl(Dacl,
+                              Length,
+                              ACL_REVISION);
+        if (! NT_SUCCESS(Status))
+        {
+            DPRINT1("RtlCreateAcl() failed (Status %lx)",
+                 Status);
+            _SEH2_LEAVE;
+        }
+
+        (void) RtlAddAccessAllowedAce(Dacl,
+                                      ACL_REVISION,
+                                      GENERIC_ALL,
+                                      SystemSid);
+        (void) RtlAddAccessAllowedAce(Dacl,
+                                      ACL_REVISION,
+                                      GENERIC_ALL,
+                                      AdminSid);
+        (void) RtlAddAccessAllowedAce(Dacl,
+                                      ACL_REVISION,
+                                      STANDARD_RIGHTS_READ,
+                                      WorldSid);
+
+        Status = RtlSetDaclSecurityDescriptor(SecurityDescriptor,
+                                              TRUE,
+                                              Dacl,
+                                              FALSE);
+        if (! NT_SUCCESS(Status))
+        {
+            DPRINT1("RtlSetDaclSecurityDescriptor() failed (Status %lx)",
+                 Status);
+            _SEH2_LEAVE;
+        }
+
+        InitializeObjectAttributes(&ObjectAttributes,
+                                   &DeviceName,
+                                   OBJ_CASE_INSENSITIVE,
+                                   NULL,
+                                   SecurityDescriptor);
+        Status = NtCreateSymbolicLinkObject(&LinkHandle,
+                                            SYMBOLIC_LINK_ALL_ACCESS,
+                                            &ObjectAttributes,
+                                            RequestLinkTarget);
+        if (NT_SUCCESS(Status))
+        {
+            Status = NtMakePermanentObject(LinkHandle);
+            if (! NT_SUCCESS(Status))
+            {
+                DPRINT1("NtMakePermanentObject(%wZ) failed (Status %lx)",
+                     &DeviceName, Status);
+            }
+        }
+        else
+        {
+            DPRINT1("NtCreateSymbolicLinkObject(%wZ) failed (Status %lx)",
+                 &DeviceName, Status);
+        }
+    }
+    _SEH2_FINALLY 
+    {
+        (void) RtlLeaveCriticalSection(&Win32CsrDefineDosDeviceCritSec);
+        if (DeviceName.Buffer)
+            (void) RtlFreeHeap(Win32CsrApiHeap,
+                               0,
+                               DeviceName.Buffer);
+        if (LinkTarget.Buffer)
+            (void) RtlFreeHeap(Win32CsrApiHeap,
+                               0,
+                               LinkTarget.Buffer);
+        if (SecurityDescriptor)
+            (void) RtlFreeHeap(Win32CsrApiHeap,
+                               0,
+                               SecurityDescriptor);
+        if (LinkHandle)
+            (void) NtClose(LinkHandle);
+        if (SystemSid)
+            (void) RtlFreeSid(SystemSid);
+        if (AdminSid)
+            (void) RtlFreeSid(AdminSid);
+        if (WorldSid)
+            (void) RtlFreeSid(WorldSid);
+        RtlFreeUnicodeString(&RequestDeviceName);
+        if (HistoryEntry)
+        {
+            if (HistoryEntry->Target.Buffer)
+                (void) RtlFreeHeap(Win32CsrApiHeap,
+                                   0,
+                                   HistoryEntry->Target.Buffer);
+            if (HistoryEntry->Device.Buffer)
+                (void) RtlFreeHeap(Win32CsrApiHeap,
+                                   0,
+                                   HistoryEntry->Device.Buffer);
+            (void) RtlFreeHeap(Win32CsrApiHeap,
+                               0,
+                               HistoryEntry);
+        }
+    }
+    _SEH2_END
+
+    DPRINT("CsrDefineDosDevice Exit, Statux: 0x%x\n", Status);
+    return Status;
+}
+
+void CsrCleanupDefineDosDevice()
+{
+    PLIST_ENTRY Entry, ListHead;
+    PCSRSS_DOS_DEVICE_HISTORY_ENTRY HistoryEntry;
+
+    (void) RtlDeleteCriticalSection(&Win32CsrDefineDosDeviceCritSec);
+    
+    ListHead = &DosDeviceHistory;
+    Entry = ListHead->Flink;
+    while (Entry != ListHead)
+    {
+        HistoryEntry = (PCSRSS_DOS_DEVICE_HISTORY_ENTRY)
+            CONTAINING_RECORD(Entry,
+                              CSRSS_DOS_DEVICE_HISTORY_ENTRY,
+                              Entry);
+        Entry = Entry->Flink;
+
+        if (HistoryEntry)
+        {
+            if (HistoryEntry->Target.Buffer)
+                (void) RtlFreeHeap(Win32CsrApiHeap,
+                                   0,
+                                   HistoryEntry->Target.Buffer);
+            if (HistoryEntry->Device.Buffer)
+                (void) RtlFreeHeap(Win32CsrApiHeap,
+                                   0,
+                                   HistoryEntry->Device.Buffer);
+            (void) RtlFreeHeap(Win32CsrApiHeap,
+                               0,
+                               HistoryEntry);
+        }
+    }
+}
+/* EOF */

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/file.h
URL: 
http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/file.h?rev=51393&r1=51392&r2=51393&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/file.h [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/file.h [iso-8859-1] Mon Apr 
18 21:48:19 2011
@@ -11,7 +11,18 @@
 
 #include "api.h"
 
+typedef struct tagCSRSS_DOS_DEVICE_HISTORY_ENTRY
+{
+    UNICODE_STRING Device;
+    UNICODE_STRING Target;
+    LIST_ENTRY Entry;
+} CSRSS_DOS_DEVICE_HISTORY_ENTRY, *PCSRSS_DOS_DEVICE_HISTORY_ENTRY;
+
 /* Api functions */
 CSR_API(CsrGetTempFile);
+CSR_API(CsrDefineDosDevice);
+
+/* functions */
+void CsrCleanupDefineDosDevice();
 
 /* EOF */


Reply via email to