mirror of
https://github.com/microsoft/WSL.git
synced 2025-12-10 00:44:55 -06:00
* link: report them to the Microsoft Security Response Center (MSRC) Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * link: Microsoft Security Response Center Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: , or Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: adapters Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: at Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: attaching them to the bug, please send them to wsl-gh-logs@microsoft.com with the github issue number in the subject, and include a link to your github issue comment in the message body. Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: disk Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: effect Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: for the file Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: for the mount to be available Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: grandchild Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: last pid Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: nonexistent Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: on which the... Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: path Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: pseudo Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: script Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: service, the Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: session Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: strip Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: that a Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: that the Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: the first one we find in the test Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: to which Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: waitpid Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: whether Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: while the relay was being set up, Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: with an Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: without specifying Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --------- Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Co-authored-by: Ben Hillis <benhillis@gmail.com>
1415 lines
32 KiB
C
1415 lines
32 KiB
C
/*++
|
|
|
|
Copyright (c) Microsoft. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
dev_pt_common.c
|
|
|
|
Abstract:
|
|
|
|
This file is a test for the Pseudo Terminals: /dev/ptmx, /dev/pts/<n>
|
|
devices.
|
|
|
|
--*/
|
|
|
|
#include "dev_pt_common.h"
|
|
|
|
pid_t ForkPtyCommon(int* PtmFdOut, int* PtsFdOut, bool UseMasterEndpoint);
|
|
|
|
void DumpBuffer(const char Data[], size_t DataSize)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will log the Data.
|
|
|
|
Arguments:
|
|
|
|
Data - Supplies the buffer to be filled.
|
|
|
|
DataSize - Supplies the size of the data buffer.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
size_t Index;
|
|
for (Index = 0; Index < DataSize; Index++)
|
|
{
|
|
printf("%d:(", Data[Index]);
|
|
if (Data[Index] == '\n')
|
|
{
|
|
printf("\\n");
|
|
}
|
|
else if (Data[Index] == '\r')
|
|
{
|
|
printf("\\r");
|
|
}
|
|
else if (Data[Index] == '\t')
|
|
{
|
|
printf("\\t");
|
|
}
|
|
else
|
|
{
|
|
printf("%c", Data[Index]);
|
|
}
|
|
|
|
printf(") ");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int GetPtSerialNumFromDeviceString(const char PtsNameString[])
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will parse the PTS (Pseudo Terminal Slave) device name and
|
|
retrieve the Serial Number from the string.
|
|
|
|
Arguments:
|
|
|
|
PtsNameString - Supplies the device name of the PTS. The name should be
|
|
of the format "/dev/pts/<n>" where 'n' is the Serial Number. The string
|
|
should also be NULL terminated.
|
|
|
|
Return Value:
|
|
|
|
Returns the Serial Number, which is >=0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int NumberOfItemsScanned;
|
|
int Result;
|
|
int SerialNumber;
|
|
|
|
SerialNumber = 0;
|
|
|
|
NumberOfItemsScanned = sscanf(PtsNameString, "/dev/pts/%d", &SerialNumber);
|
|
if (NumberOfItemsScanned != 1)
|
|
{
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = SerialNumber;
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int GetRandomMessage(char Message[], size_t MessageSize, bool CompleteMessage)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will fill the message buffer with random bytes for the
|
|
specified size. If a complete message is requested, then it will set the
|
|
last byte in the message with the completion character.
|
|
|
|
Arguments:
|
|
|
|
Message - Supplies the buffer to be filled with data.
|
|
|
|
MessageSize - Supplies the size to be filled. Size should be >=1.
|
|
|
|
CompleteMessage - Supplies the flag which indicates whether the message
|
|
should be completed with a terminating character or not.
|
|
|
|
Return Value:
|
|
|
|
Returns 0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
size_t Itr;
|
|
size_t NumBytesToFill;
|
|
|
|
//
|
|
// If the message has to be completed, last byte is reserved for the
|
|
// terminating character.
|
|
//
|
|
|
|
if (CompleteMessage != FALSE)
|
|
{
|
|
NumBytesToFill = MessageSize - 1;
|
|
Message[NumBytesToFill] = '\n';
|
|
}
|
|
else
|
|
{
|
|
NumBytesToFill = MessageSize;
|
|
}
|
|
|
|
for (Itr = 0; Itr < NumBytesToFill; Itr += 1)
|
|
{
|
|
|
|
//
|
|
// TODO_LX_PTYT: Randomize the data.
|
|
//
|
|
|
|
Message[Itr] = 'A' + (Itr % 26);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int OpenMasterSubordinate(int* PtmFd, int* PtsFd, char* PtsDevName, int* SerialNumber)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will open a master and subordinate pseudo terminal.
|
|
It will also extract the serial number of the subordinate and
|
|
return it in 'SerialNumber'.
|
|
|
|
Arguments:
|
|
|
|
PtmFd - Supplies the pointer which will be set to the FD of the
|
|
master, on success. This parameter is not optional.
|
|
|
|
PtsFd - Supplies the pointer which will be set to the FD of the
|
|
subordinate, on success. This parameter is not optional.
|
|
|
|
PtsDevName - Supplies the pointer to the buffer that will hold
|
|
the subordinate device name, on success. This parameter is
|
|
optional.
|
|
|
|
SerialNumber - Supplies the pointer which will be set to the
|
|
serial number of the subordinate pseudo terminal. This
|
|
parameter is optional.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
--*/
|
|
|
|
{
|
|
|
|
int Fdm;
|
|
int Fds;
|
|
char LocalBuffer[PTS_DEV_NAME_BUFFER_SIZE];
|
|
int Result;
|
|
int SubordinateSerialNumber;
|
|
|
|
//
|
|
// Initialize locals
|
|
//
|
|
|
|
Result = -1;
|
|
Fdm = -1;
|
|
Fds = -1;
|
|
|
|
LxtCheckErrno((Fdm = open("/dev/ptmx", O_RDWR)));
|
|
LxtCheckErrno(grantpt(Fdm));
|
|
LxtCheckErrno(unlockpt(Fdm));
|
|
LxtCheckErrno(ptsname_r(Fdm, LocalBuffer, PTS_DEV_NAME_BUFFER_SIZE));
|
|
LxtCheckErrno(Fds = open(LocalBuffer, O_RDWR));
|
|
if (PtsDevName != NULL)
|
|
{
|
|
strcpy(PtsDevName, LocalBuffer);
|
|
}
|
|
|
|
if (SerialNumber != NULL)
|
|
{
|
|
LxtCheckErrno(SubordinateSerialNumber = GetPtSerialNumFromDeviceString(LocalBuffer));
|
|
*SerialNumber = SubordinateSerialNumber;
|
|
}
|
|
|
|
*PtmFd = Fdm;
|
|
*PtsFd = Fds;
|
|
Fdm = -1;
|
|
Fds = -1;
|
|
Result = 0;
|
|
|
|
ErrorExit:
|
|
if (Fdm != -1)
|
|
{
|
|
close(Fdm);
|
|
}
|
|
|
|
if (Fds != -1)
|
|
{
|
|
close(Fds);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
pid_t ForkPty(int* PtmFdOut, int* PtsFdOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up a new process as a foreground process using PtsFd as
|
|
its controlling terminal.
|
|
|
|
Arguments:
|
|
|
|
PtmFdOut - Supplies a pointer to receive the master file descriptor.
|
|
|
|
PtsFdOut - Supplies a pointer to receive the subordinate file descriptor.
|
|
|
|
Return Value:
|
|
|
|
Returns pid of newly forked process to the parent, zero to the child and
|
|
<0 on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return ForkPtyCommon(PtmFdOut, PtsFdOut, false);
|
|
}
|
|
|
|
int ForkPtyBackground(int* PtmFdOut, int* PtsFdOut, pid_t* ForegroundIdOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up a new process as a background process using PtsFd as
|
|
its controlling terminal.
|
|
|
|
Arguments:
|
|
|
|
PtmFdOut - Supplies a pointer to receive the master file descriptor.
|
|
|
|
PtsFdOut - Supplies a pointer to receive the subordinate file descriptor.
|
|
|
|
ForegroundId - Supplies a pointer to receive the ID of the foreground
|
|
process.
|
|
|
|
Return Value:
|
|
|
|
Returns pid of newly forked process to the parent, zero to the child and
|
|
<0 on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int ChildPid;
|
|
int GrandChildPid;
|
|
int GrandChildStatus;
|
|
int PtmFd;
|
|
int PtsFd;
|
|
int Result;
|
|
|
|
ChildPid = -1;
|
|
GrandChildPid = -1;
|
|
PtmFd = -1;
|
|
PtsFd = -1;
|
|
|
|
LxtCheckErrno(ChildPid = ForkPty(&PtmFd, &PtsFd));
|
|
if (ChildPid == 0)
|
|
{
|
|
*ForegroundIdOut = getpid();
|
|
LxtCheckErrno(GrandChildPid = fork());
|
|
if (GrandChildPid == 0)
|
|
{
|
|
LxtCheckErrno(setpgid(0, 0));
|
|
}
|
|
else
|
|
{
|
|
LxtCheckErrno(TEMP_FAILURE_RETRY(Result = waitpid(GrandChildPid, &GrandChildStatus, 0)));
|
|
LxtCheckResult(WIFEXITED(GrandChildStatus) ? 0 : -1);
|
|
LxtCheckResult((int)(char)WEXITSTATUS(GrandChildStatus));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ForegroundIdOut = ChildPid;
|
|
}
|
|
|
|
*PtmFdOut = PtmFd;
|
|
PtmFd = -1;
|
|
*PtsFdOut = PtsFd;
|
|
PtsFd = -1;
|
|
|
|
ErrorExit:
|
|
if (PtmFd != -1)
|
|
{
|
|
close(PtmFd);
|
|
}
|
|
|
|
if (PtsFd != -1)
|
|
{
|
|
close(PtsFd);
|
|
}
|
|
|
|
if ((ChildPid == 0) && (GrandChildPid > 0))
|
|
{
|
|
exit(Result);
|
|
}
|
|
|
|
return ChildPid;
|
|
}
|
|
|
|
pid_t ForkPtyCommon(int* PtmFdOut, int* PtsFdOut, bool UseMasterEndpoint)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up a new process as a foreground process using PtsFd as
|
|
its controlling terminal.
|
|
|
|
Arguments:
|
|
|
|
PtmFdOut - Supplies a pointer to receive the master file descriptor.
|
|
|
|
PtsFdOut - Supplies a pointer to receive the subordinate file descriptor.
|
|
|
|
UseMasterEndpoint - Supplies a flag indicating whether the master or
|
|
subordinate endpoint should be set as the controlling terminal.
|
|
|
|
Return Value:
|
|
|
|
Returns pid of newly forked process to the parent, zero to the child and
|
|
<0 on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
pid_t ChildPid;
|
|
int PtmFd;
|
|
int PtsFd;
|
|
int Result;
|
|
int SerialNumber;
|
|
pid_t SessionId;
|
|
|
|
//
|
|
// Initialize locals
|
|
//
|
|
|
|
ChildPid = -1;
|
|
PtmFd = -1;
|
|
PtsFd = -1;
|
|
|
|
//
|
|
// Open Master-Subordinate
|
|
//
|
|
|
|
LxtCheckErrno(OpenMasterSubordinate(&PtmFd, &PtsFd, NULL, &SerialNumber));
|
|
LxtLogInfo("Master opened at FD:%d", PtmFd);
|
|
LxtLogInfo("Subordinate Serial Number: %d", SerialNumber);
|
|
LxtLogInfo("Subordinate opened at FD:%d", PtsFd);
|
|
|
|
LxtCheckErrno(ChildPid = fork());
|
|
if (ChildPid == 0)
|
|
{
|
|
|
|
//
|
|
// Move to a new session
|
|
//
|
|
|
|
LxtCheckErrno(SessionId = setsid());
|
|
|
|
//
|
|
// Set the fd as the controlling terminal for the session, calling
|
|
// again should not fail.
|
|
//
|
|
|
|
LxtCheckErrno(ioctl((UseMasterEndpoint) ? PtmFd : PtsFd, TIOCSCTTY, (char*)NULL));
|
|
LxtCheckErrno(ioctl((UseMasterEndpoint) ? PtmFd : PtsFd, TIOCSCTTY, (char*)NULL));
|
|
}
|
|
|
|
*PtmFdOut = PtmFd;
|
|
PtmFd = -1;
|
|
*PtsFdOut = PtsFd;
|
|
PtsFd = -1;
|
|
|
|
ErrorExit:
|
|
if (PtmFd != -1)
|
|
{
|
|
close(PtmFd);
|
|
}
|
|
|
|
if (PtsFd != -1)
|
|
{
|
|
close(PtsFd);
|
|
}
|
|
|
|
return ChildPid;
|
|
}
|
|
|
|
pid_t ForkPtyMaster(int* PtmFdOut, int* PtsFdOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up a new process as a foreground process using PtmFd as
|
|
its controlling terminal.
|
|
|
|
Arguments:
|
|
|
|
PtmFdOut - Supplies a pointer to receive the master file descriptor.
|
|
|
|
PtsFdOut - Supplies a pointer to receive the subordinate file descriptor.
|
|
|
|
Return Value:
|
|
|
|
Returns pid of newly forked process to the parent, zero to the child and
|
|
<0 on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return ForkPtyCommon(PtmFdOut, PtsFdOut, true);
|
|
}
|
|
|
|
int RawInit(int Fd)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the FD for raw input/output.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
cc_t ControlArray[NCCS];
|
|
int Result;
|
|
|
|
//
|
|
// After the switch to RAW mode want no timeout and a minimum of 1 char.
|
|
//
|
|
|
|
Result = TerminalSettingsGetControlArray(Fd, ControlArray);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
ControlArray[VTIME] = 0;
|
|
ControlArray[VMIN] = 1;
|
|
Result = TerminalSettingsSetControlArray(Fd, ControlArray);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Disable echo, cannon and other flags. Set TOSTOP so signals are
|
|
// generated by default.
|
|
//
|
|
|
|
Result = TerminalSettingsSetLocalFlags(Fd, TOSTOP);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int SimpleReadWriteCheck(int PtmFd, int PtsFd)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs a simple read/write check on the master-subordinate
|
|
pseudo terminal pair. The check is as follows:
|
|
- Write to the master.
|
|
- Read from the subordinate. Read data should match the data written
|
|
by master.
|
|
- Write to the subordinate.
|
|
- Read data from the master. Data read from the mater should match
|
|
what was written by the subordinate.
|
|
|
|
Arguments:
|
|
|
|
PtmFd - Supplies the FD for the master.
|
|
|
|
PtsFd - Supplies the FD for the subordinate.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return SimpleReadWriteCheckEx(PtmFd, PtsFd, SimpleReadWriteForeground);
|
|
}
|
|
|
|
int SimpleReadWriteCheckEx(int PtmFd, int PtsFd, SIMPLE_READ_WRITE_MODE Mode)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs a simple read/write check on the master-subordinate
|
|
pseudo terminal pair. The check is as follows:
|
|
- Write to the master.
|
|
- Read from the subordinate. Read data should match the data written
|
|
by master.
|
|
- Write to the subordinate.
|
|
- Read data from the master. Data read from the mater should match
|
|
what was written by the subordinate.
|
|
|
|
Arguments:
|
|
|
|
PtmFd - Supplies the FD for the master.
|
|
|
|
PtsFd - Supplies the FD for the subordinate.
|
|
|
|
Mode - Supplies a value indicating whether this access is from foreground
|
|
or background, and whether signals are enabled.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int BytesReadWrite;
|
|
int ExpectedResult;
|
|
const char* Greetings = "Hello there!!\n";
|
|
size_t GreetingsLength;
|
|
int PacketMode;
|
|
int PtmFlags;
|
|
char ReadBuffer[1024];
|
|
char* ReadBufferMaster;
|
|
const char* Reply = "Hi, how are you?\r";
|
|
size_t ReplyLength;
|
|
int Result;
|
|
struct termios TiosMaster;
|
|
struct termios TiosSubordinate;
|
|
|
|
LxtCheckErrno(PtmFlags = fcntl(PtmFd, F_GETFL, 0));
|
|
|
|
memset(&TiosMaster, 0, sizeof(TiosMaster));
|
|
LxtCheckErrno(tcgetattr(PtmFd, &TiosMaster));
|
|
memset(&TiosSubordinate, 0, sizeof(TiosSubordinate));
|
|
LxtCheckErrno(tcgetattr(PtsFd, &TiosSubordinate));
|
|
LxtCheckMemoryEqual(&TiosMaster, &TiosSubordinate, sizeof(TiosMaster));
|
|
GreetingsLength = strlen(Greetings);
|
|
ReplyLength = strlen(Reply);
|
|
if (TiosSubordinate.c_lflag & ICANON)
|
|
{
|
|
LxtLogInfo("Canonical mode.");
|
|
}
|
|
else
|
|
{
|
|
LxtLogInfo("Raw mode.");
|
|
--GreetingsLength;
|
|
--ReplyLength;
|
|
}
|
|
|
|
LxtCheckErrno(ioctl(PtmFd, TIOCGPKT, &PacketMode));
|
|
if (PacketMode != 0)
|
|
{
|
|
LxtLogInfo("Packet mode enabled.");
|
|
ReadBufferMaster = &ReadBuffer[1];
|
|
}
|
|
else
|
|
{
|
|
ReadBufferMaster = &ReadBuffer[0];
|
|
}
|
|
|
|
//
|
|
// Write the greetings message to the master.
|
|
//
|
|
|
|
LxtLogInfo("Writing to master");
|
|
ExpectedResult = GreetingsLength;
|
|
LxtCheckErrno(BytesReadWrite = write(PtmFd, Greetings, ExpectedResult));
|
|
LxtCheckFnResults("write", BytesReadWrite, ExpectedResult);
|
|
LxtLogInfo("Master(FD:%d) --> subordinate(FD:%d):%*s", PtmFd, PtsFd, GreetingsLength, Greetings);
|
|
|
|
//
|
|
// Canonical mode should echo the input back to the master with a
|
|
// carriage-return and newline.
|
|
//
|
|
|
|
if (TiosSubordinate.c_lflag & ICANON)
|
|
{
|
|
LxtCheckErrno(BytesReadWrite = read(PtmFd, ReadBuffer, sizeof(ReadBuffer)));
|
|
if (PacketMode != 0)
|
|
{
|
|
LxtCheckEqual(ReadBuffer[0], 0, "%hhd");
|
|
if (BytesReadWrite > 0)
|
|
{
|
|
BytesReadWrite -= 1;
|
|
}
|
|
}
|
|
|
|
ReadBufferMaster[BytesReadWrite] = '\0';
|
|
LxtLogInfo("Echo received by master(FD:%d):%s", PtmFd, ReadBufferMaster);
|
|
LxtLogInfo("Last character = %d [\\n = %d, \\r = %d]", ReadBufferMaster[BytesReadWrite - 1], '\n', '\r');
|
|
LxtCheckFnResults("read", BytesReadWrite, (ExpectedResult + 1));
|
|
if ((ReadBufferMaster[BytesReadWrite - 1] != '\n') || (ReadBufferMaster[BytesReadWrite - 2] != '\r'))
|
|
{
|
|
LxtLogError("Echo to master(FD:%d) does not end with \r\n.", PtmFd);
|
|
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
ReadBufferMaster[BytesReadWrite - 2] = '\n';
|
|
if (memcmp(ReadBufferMaster, Greetings, ExpectedResult) != 0)
|
|
{
|
|
LxtLogError(
|
|
"Echo to master(FD:%d) does not match what was "
|
|
"written.",
|
|
PtmFd);
|
|
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read from subordinate.
|
|
//
|
|
|
|
memset(ReadBuffer, 0, sizeof(ReadBuffer));
|
|
LxtLogInfo("Reading from subordinate");
|
|
if (Mode == SimpleReadWriteForeground)
|
|
{
|
|
LxtCheckErrno(BytesReadWrite = read(PtsFd, ReadBuffer, sizeof(ReadBuffer)));
|
|
LxtLogInfo("Message received by subordinate(FD:%d):%s", PtsFd, ReadBuffer);
|
|
LxtLogInfo("Last character = %d [\\n = %d, \\r = %d]", ReadBuffer[BytesReadWrite - 1], '\n', '\r');
|
|
LxtCheckFnResults("read", BytesReadWrite, ExpectedResult);
|
|
|
|
//
|
|
// Compare the messages.
|
|
//
|
|
|
|
if (memcmp(ReadBuffer, Greetings, BytesReadWrite) != 0)
|
|
{
|
|
LxtLogError(
|
|
"Data read from subordinate(FD:%d) does not match what was "
|
|
"written by master(FD:%d).",
|
|
PtsFd,
|
|
PtmFd);
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else if ((Mode == SimpleReadWriteBackgroundSignal) || (Mode == SimpleReadWriteBackgroundSignalNoStop))
|
|
{
|
|
|
|
LxtCheckErrnoFailure(BytesReadWrite = read(PtsFd, ReadBuffer, sizeof(ReadBuffer)), EINTR);
|
|
}
|
|
else
|
|
{
|
|
LxtCheckErrnoFailure(BytesReadWrite = read(PtsFd, ReadBuffer, sizeof(ReadBuffer)), EIO);
|
|
}
|
|
|
|
//
|
|
// So far so good, now write a response from the subordinate.
|
|
//
|
|
|
|
LxtLogInfo("Subordinate(FD:%d) --> master(FD:%d):%*s", PtsFd, PtmFd, ReplyLength, Reply);
|
|
|
|
ExpectedResult = ReplyLength;
|
|
if (Mode != SimpleReadWriteBackgroundSignal)
|
|
{
|
|
LxtCheckErrno(BytesReadWrite = write(PtsFd, Reply, ExpectedResult));
|
|
LxtCheckFnResults("write", BytesReadWrite, ExpectedResult);
|
|
}
|
|
else
|
|
{
|
|
LxtCheckErrnoFailure(BytesReadWrite = write(PtsFd, Reply, ExpectedResult), EINTR);
|
|
BytesReadWrite = ExpectedResult;
|
|
ExpectedResult = 0;
|
|
}
|
|
|
|
//
|
|
// Read from master.
|
|
//
|
|
|
|
LxtLogInfo("Reading from master");
|
|
memset(ReadBuffer, 0, sizeof(ReadBuffer));
|
|
if (Mode != SimpleReadWriteBackgroundSignal)
|
|
{
|
|
LxtCheckErrno(BytesReadWrite = read(PtmFd, ReadBuffer, sizeof(ReadBuffer)));
|
|
if (PacketMode != 0)
|
|
{
|
|
LxtCheckEqual(ReadBuffer[0], 0, "%hhd");
|
|
if (BytesReadWrite > 0)
|
|
{
|
|
BytesReadWrite -= 1;
|
|
}
|
|
}
|
|
|
|
ReadBufferMaster[BytesReadWrite] = '\0';
|
|
LxtLogInfo("Reply received by master(FD:%d):%s", PtmFd, ReadBufferMaster);
|
|
LxtLogInfo("Last character = %d [\\n = %d, \\r = %d]", ReadBufferMaster[BytesReadWrite - 1], '\n', '\r');
|
|
}
|
|
else
|
|
{
|
|
LxtCheckErrno(fcntl(PtmFd, F_SETFL, (PtmFlags | O_NONBLOCK)));
|
|
LxtCheckErrnoFailure(BytesReadWrite = read(PtmFd, ReadBuffer, BytesReadWrite), EAGAIN);
|
|
BytesReadWrite = 0;
|
|
}
|
|
|
|
LxtCheckFnResults("read", BytesReadWrite, ExpectedResult);
|
|
|
|
//
|
|
// Compare the messages.
|
|
//
|
|
|
|
if (memcmp(ReadBufferMaster, Reply, BytesReadWrite) != 0)
|
|
{
|
|
LxtLogError(
|
|
"Data read from master(FD:%d) does not match what was "
|
|
"written by subordinate(FD:%d).",
|
|
PtmFd,
|
|
PtsFd);
|
|
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
ErrorExit:
|
|
fcntl(PtmFd, F_SETFL, PtmFlags);
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsGet(int Fd, cc_t* ControlArrayOut, tcflag_t* ControlFlagsOut, tcflag_t* InputFlagsOut, tcflag_t* LocalFlagsOut, tcflag_t* OutputFlagsOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the settings for the FD.
|
|
All of the *Out variables are optional.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlArrayOut - Supplies an optional pointer to receive the NCCS element
|
|
control array.
|
|
|
|
ControlFlagsOut - Supplies an optional pointer to receive the terminal
|
|
control flags.
|
|
|
|
InputFlagsOut - Supplies an optional pointer to receive the terminal input
|
|
flags.
|
|
|
|
LocalFlagsOut - Supplies an optional pointer to receive the terminal local
|
|
flags.
|
|
|
|
OutputFlagsOut - Supplies an optional pointer to receive the terminal
|
|
output flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios;
|
|
|
|
Result = tcgetattr(Fd, &Tios);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (ControlArrayOut != NULL)
|
|
{
|
|
memcpy(ControlArrayOut, Tios.c_cc, NCCS * sizeof(cc_t));
|
|
}
|
|
|
|
if (ControlFlagsOut != NULL)
|
|
{
|
|
*ControlFlagsOut = Tios.c_cflag;
|
|
}
|
|
|
|
if (InputFlagsOut != NULL)
|
|
{
|
|
*InputFlagsOut = Tios.c_iflag;
|
|
}
|
|
|
|
if (LocalFlagsOut != NULL)
|
|
{
|
|
*LocalFlagsOut = Tios.c_lflag;
|
|
}
|
|
|
|
if (OutputFlagsOut != NULL)
|
|
{
|
|
*OutputFlagsOut = Tios.c_oflag;
|
|
}
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsGetControlArray(int Fd, cc_t* ControlArrayOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the NCCS element control array.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlArrayOut - Supplies a pointer to receive the NCCS element control
|
|
array.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return TerminalSettingsGet(Fd, ControlArrayOut, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
int TerminalSettingsGetControlFlags(int Fd, tcflag_t* ControlFlagsOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the local flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlFlagsOut - Supplies a pointer to receive the terminal control flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return TerminalSettingsGet(Fd, NULL, ControlFlagsOut, NULL, NULL, NULL);
|
|
}
|
|
|
|
int TerminalSettingsGetInputFlags(int Fd, tcflag_t* InputFlagsOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the input flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
InputFlagsOut - Supplies a pointer to receive the terminal input flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return TerminalSettingsGet(Fd, NULL, NULL, InputFlagsOut, NULL, NULL);
|
|
}
|
|
|
|
int TerminalSettingsGetLocalFlags(int Fd, tcflag_t* LocalFlagsOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the local flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
LocalFlagsOut - Supplies a pointer to receive the terminal local flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return TerminalSettingsGet(Fd, NULL, NULL, NULL, LocalFlagsOut, NULL);
|
|
}
|
|
|
|
int TerminalSettingsGetOutputFlags(int Fd, tcflag_t* OutputFlagsOut)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to get the output flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
OutputFlagsOut - Supplies a pointer to receive the terminal output flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
return TerminalSettingsGet(Fd, NULL, NULL, NULL, NULL, OutputFlagsOut);
|
|
}
|
|
|
|
int TerminalSettingsSet(int Fd, cc_t* ControlArray, tcflag_t ControlFlags, tcflag_t InputFlags, tcflag_t LocalFlags, tcflag_t OutputFlags)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to update the settings for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlArray - Supplies a pointer to the new NCCS element control array.
|
|
|
|
ControlFlags - Supplies the new terminal control flags.
|
|
|
|
InputFlagsOut - Supplies the new terminal input flags.
|
|
|
|
LocalFlagsOut - Supplies the new terminal local flags.
|
|
|
|
OutputFlagsOut - Supplies the new terminal output flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
memcpy(Tios.c_cc, ControlArray, NCCS * sizeof(cc_t));
|
|
Tios.c_cflag = ControlFlags;
|
|
Tios.c_iflag = InputFlags;
|
|
Tios.c_lflag = LocalFlags;
|
|
Tios.c_oflag = OutputFlags;
|
|
Result = tcsetattr(Fd, TCSANOW, &Tios);
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsSetControlArray(int Fd, cc_t* ControlArray)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the control array for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlArray - Supplies the new control array.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
Result = TerminalSettingsGet(Fd, NULL, &Tios.c_cflag, &Tios.c_iflag, &Tios.c_lflag, &Tios.c_oflag);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = TerminalSettingsSet(Fd, ControlArray, Tios.c_cflag, Tios.c_iflag, Tios.c_lflag, Tios.c_oflag);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsSetControlFlags(int Fd, tcflag_t ControlFlags)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the control flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
ControlFlags - Supplies the new control flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
Result = TerminalSettingsGet(Fd, Tios.c_cc, NULL, &Tios.c_iflag, &Tios.c_lflag, &Tios.c_oflag);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = TerminalSettingsSet(Fd, Tios.c_cc, ControlFlags, Tios.c_iflag, Tios.c_lflag, Tios.c_oflag);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsSetInputFlags(int Fd, tcflag_t InputFlags)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the input flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
InputFlags - Supplies the new input flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
Result = TerminalSettingsGet(Fd, Tios.c_cc, &Tios.c_cflag, NULL, &Tios.c_lflag, &Tios.c_oflag);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = TerminalSettingsSet(Fd, Tios.c_cc, Tios.c_cflag, InputFlags, Tios.c_lflag, Tios.c_oflag);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsSetLocalFlags(int Fd, tcflag_t LocalFlags)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the local flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
LocalFlags - Supplies the new local flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
Result = TerminalSettingsGet(Fd, Tios.c_cc, &Tios.c_cflag, &Tios.c_iflag, NULL, &Tios.c_oflag);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = TerminalSettingsSet(Fd, Tios.c_cc, Tios.c_cflag, Tios.c_iflag, LocalFlags, Tios.c_oflag);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int TerminalSettingsSetOutputFlags(int Fd, tcflag_t OutputFlags)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will use termios to set the output flags for the FD.
|
|
|
|
Arguments:
|
|
|
|
Fd - Supplies the FD.
|
|
|
|
OutputFlags - Supplies the new output flags.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
int Result;
|
|
struct termios Tios = {0};
|
|
|
|
Result = TerminalSettingsGet(Fd, Tios.c_cc, &Tios.c_cflag, &Tios.c_iflag, &Tios.c_lflag, NULL);
|
|
if (Result < 0)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Result = TerminalSettingsSet(Fd, Tios.c_cc, Tios.c_cflag, Tios.c_iflag, Tios.c_lflag, OutputFlags);
|
|
|
|
ErrorExit:
|
|
return Result;
|
|
}
|
|
|
|
int WriteReadFdCommon(int WriteFd, size_t WriteSizes[], size_t NumWriteSizes, int ReadFd, size_t ReadSizes[], size_t NumReadSizes)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs write operation of given sizes to the WriteFd and
|
|
reads from the ReadFd for the specified sizes. Each write is expected to
|
|
succeed for the given size and each read is expected to succeed for the
|
|
given size. This routine will also validate that the data read aligns up
|
|
with the data written. All the writes will be performed before any of the
|
|
reads.
|
|
|
|
Arguments:
|
|
|
|
WriteFd - Supplies the FD on which the write operation should be called.
|
|
|
|
WriteSizes - Supplies the array of sizes for the write operation..
|
|
|
|
NumWriteSizes - Supplies the number of elements in the write size array.
|
|
|
|
ReadFd - Supplies the FD on which the read operation should be called.
|
|
|
|
ReadSizes - Supplies the array of sizes for the read operation..
|
|
|
|
NumReadSizes - Supplies the number of elements in the read size array.
|
|
|
|
Return Value:
|
|
|
|
0 on success, error code on failure.
|
|
--*/
|
|
|
|
{
|
|
|
|
char* AccumulatedReadBuffer;
|
|
char* AccumulatedWriteBuffer;
|
|
ssize_t BytesReadWrite;
|
|
size_t Itr;
|
|
size_t Offset;
|
|
char* ReadBuffer;
|
|
int Result;
|
|
size_t TotalReadSize;
|
|
size_t TotalWriteSize;
|
|
char* WriteBuffer;
|
|
|
|
AccumulatedReadBuffer = NULL;
|
|
AccumulatedWriteBuffer = NULL;
|
|
ReadBuffer = NULL;
|
|
WriteBuffer = NULL;
|
|
TotalWriteSize = 0;
|
|
TotalReadSize = 0;
|
|
|
|
//
|
|
// Calculate the size of total number of bytes to be written and read.
|
|
//
|
|
|
|
for (Itr = 0; Itr < NumWriteSizes; Itr += 1)
|
|
{
|
|
TotalWriteSize += WriteSizes[Itr];
|
|
}
|
|
|
|
for (Itr = 0; Itr < NumReadSizes; Itr += 1)
|
|
{
|
|
TotalReadSize += ReadSizes[Itr];
|
|
}
|
|
|
|
//
|
|
// Allocate memory to hold the data for the accumulated writes and reads.
|
|
//
|
|
|
|
AccumulatedWriteBuffer = calloc(TotalWriteSize, 1);
|
|
if (AccumulatedWriteBuffer == NULL)
|
|
{
|
|
LxtLogError("Failed to allocate memory for Write Buffer");
|
|
Result = LXT_RESULT_FAILURE;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
AccumulatedReadBuffer = calloc(TotalReadSize, 1);
|
|
if (AccumulatedReadBuffer == NULL)
|
|
{
|
|
LxtLogError("Failed to allocate memory for Read Buffer");
|
|
Result = LXT_RESULT_FAILURE;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// First perform all the writes.
|
|
//
|
|
|
|
Offset = 0;
|
|
for (Itr = 0; Itr < NumWriteSizes; Itr += 1)
|
|
{
|
|
WriteBuffer = calloc(WriteSizes[Itr], 1);
|
|
if (WriteBuffer == NULL)
|
|
{
|
|
Result = ENOMEM;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
LxtCheckErrno(GetRandomMessage(WriteBuffer, WriteSizes[Itr], FALSE));
|
|
LxtCheckErrno(BytesReadWrite = write(WriteFd, WriteBuffer, WriteSizes[Itr]));
|
|
|
|
LxtCheckFnResults("write", BytesReadWrite, (ssize_t)WriteSizes[Itr]);
|
|
memcpy(&AccumulatedWriteBuffer[Offset], WriteBuffer, WriteSizes[Itr]);
|
|
|
|
Offset += WriteSizes[Itr];
|
|
free(WriteBuffer);
|
|
WriteBuffer = NULL;
|
|
}
|
|
|
|
//
|
|
// On Ubuntu16 pty processing is asynchronous so pause a second to give the
|
|
// writes time to be processed.
|
|
//
|
|
|
|
sleep(1);
|
|
|
|
//
|
|
// Now read the data previously written.
|
|
//
|
|
|
|
Offset = 0;
|
|
for (Itr = 0; Itr < NumReadSizes; Itr += 1)
|
|
{
|
|
ReadBuffer = calloc(ReadSizes[Itr], 1);
|
|
if (ReadBuffer == NULL)
|
|
{
|
|
Result = ENOMEM;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
LxtCheckErrno(BytesReadWrite = read(ReadFd, ReadBuffer, ReadSizes[Itr]));
|
|
|
|
LxtCheckFnResults("read", BytesReadWrite, (ssize_t)ReadSizes[Itr]);
|
|
memcpy(&AccumulatedReadBuffer[Offset], ReadBuffer, ReadSizes[Itr]);
|
|
Offset += ReadSizes[Itr];
|
|
free(ReadBuffer);
|
|
ReadBuffer = NULL;
|
|
}
|
|
|
|
//
|
|
// Data read should align up with the previously written data.
|
|
//
|
|
|
|
if (memcmp(AccumulatedWriteBuffer, AccumulatedReadBuffer, min(TotalWriteSize, TotalReadSize)) != 0)
|
|
{
|
|
|
|
LxtLogError(
|
|
"Data read from FD:%d does not match what was "
|
|
"written by FD:%d.",
|
|
ReadFd,
|
|
WriteFd);
|
|
|
|
Result = -1;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
/*
|
|
LxtLogInfo("Data Written: ");
|
|
DumpBuffer(AccumulatedWriteBuffer, TotalWriteSize);
|
|
LxtLogInfo("Data Read: ");
|
|
DumpBuffer(AccumulatedReadBuffer, TotalReadSize);
|
|
*/
|
|
|
|
ErrorExit:
|
|
|
|
if (AccumulatedReadBuffer != NULL)
|
|
{
|
|
free(AccumulatedReadBuffer);
|
|
}
|
|
|
|
if (AccumulatedWriteBuffer != NULL)
|
|
{
|
|
free(AccumulatedWriteBuffer);
|
|
}
|
|
|
|
if (ReadBuffer != NULL)
|
|
{
|
|
free(ReadBuffer);
|
|
}
|
|
|
|
if (WriteBuffer != NULL)
|
|
{
|
|
free(WriteBuffer);
|
|
}
|
|
|
|
return Result;
|
|
}
|