          <<< NOTED::DISK$NOTES7:[NOTES$LIBRARY_7OF4]HACKERS.NOTE;1 >>>
                               -< ** Hackers ** >-
================================================================================
Note 1609.6                     OPCOM Messages ?                          6 of 6
SSDEVO::BLAYLOCK "If at first you doubt,doubt aga" 1027 lines  14-APR-1995 12:47
       -< It could use some ansi-fication/update for DECC > V5.5 of VMS >-
--------------------------------------------------------------------------------
#module operator "V01.01"
/*

Abstract:

        This module provides an example of an image
        that intercepts OPCOM messages.  It provides
        examples of creating a psuedoterminal device,
        enabling an operator on that device, and recieving
        the OPCOM brodcast messages to that device.
        Examples of $QIO, LIB$STOP and the message facility,
        kernel code, use of the VAX C builtin functions,
        $GETDVI, asynchronous RMS  and the use of a timer
        and SYS$FLUSH to flush the user output buffer.

Author:

        Kenneth Blaylock        18-Nov-1991

Modification History:

        Kenneth G. Blaylock     15-May-1991

        Add a conditional compilation flag to allow
        the non-kernel portion to run on Alpha.
        Also added support and a call to disable
        the operator terminal.

        Kenneth G. Blaylock     19-Dec-1991

        Add code (kernel) to allow for broadcasts to be
        received by this application that are sent to
        a username; in particular, the username that
        issued the RUN command.

        NOTE: PROGRAM USES THE VAX C BUILTIN READ_GPR TO
        OBTAIN THE USERS PCB ADDRESS.  THIS MUST BE
        THE FIRST INSTRUCTION IN THE KERNEL ROUTINE
        SO AS NOT TO KILL THE SYSTEM.  OTHERWISE
        THE CODE MUST BE CHANGED TO USE CTL$GL_PCB
        AS A POINTER TO THE PCB.  THE PCB 
        CONSTANTS ARE CORRECT FOR V5.5 OF VMS ONLY.

*/


/*

        Include files:

*/

#include <chfdef.h>                     /* Condition handling structures */
#include <dcdef.h>                      /* Device class and types */
#include <descrip.h>                    /* VMS string descriptors */
#include <dvidef.h>                     /* $GETDVI item codes */
#include <iodef.h>                      /* I/O routine function codes */
#include <opcdef.h>                     /* OPCOM defintions */
#include <msgdef.h>                     /* message variables */
#include <rms.h>                        /* RMS definitions */
#include <ssdef.h>                      /* system service status
definitions.*/
#include <stsdef.h>                     /* status message definitions */
#include <string.h>                     /* RTL string routines */
#include <ttdef.h>                      /* basic terminal characteristics
*/
#include <tt2def.h>                     /* extended terminal
characteristics */

#include "src$:operatormsg.h"           /* user defined message
definitions */

/* #include <pcbdef.h> */               /* Process header definitions */

#define PCB$T_TERMINAL (68)             /* Offset to PCB pointer */
#define PCB$S_TERMINAL (8)              /* Size of PCB field */

/*

        User defined structures.

*/


struct IOSB {                   /* For mailbox device interaction */
        short   status;
        short   byte_count;
        int     PID;
        };

struct MBXMSG {                 /* Format of user message in mailbox */
        short msg$w_type;
        short msg$w_unit;
        char msg$t_controller[16];
        short msg$w_length;
        char    msg$t_message;
        };

static struct INFO {            /* User storage of buffer and IOSB */

        struct IOSB iosb;
        char *user_buffer;
        int set_ast : 1;
        int filler : 31;

        } User_Info;

static short PT_Channel, MB_Channel;

static char device_name_string[64];
static $DESCRIPTOR(PT_Name, device_name_string);

static $DESCRIPTOR(MB_Name, "OPCOM_RECEIPT");
static $DESCRIPTOR(Flush_Time_D, "0000 00:00:15.00");

static int Flush_Time[2];

#define FILE_NAME "SYS$OUTPUT"
#define DEFAULT_NAME "INTERCEPTED.OPCOM_MESSAGES"
#define MAXMSG  1024
#define BUFQUO  (8*MAXMSG)
#define USER_BUF_SIZE (MAXMSG)

static struct FAB Output_FAB;
static struct RAB Output_RAB;
static struct NAM Output_NAM;

static char Output_RSS[NAM$C_MAXRSS];




main()
/*

        This is the main entry point to the module.  Control
        Routines are called from here.

Inputs:

        None

Implicit Inputs:

        None

Outputs:

        None

Implicit Outputs:

        None

Returns:

        SS$_NORMAL.  All other errors are signaled.

*/
{
int wake_handler(), kernel_set_terminal(),
        cmkrnl_args[2] = { 1, &PT_Name } ;

void create_terminal(), set_characteristics(), setup_rms(),
        enable_broadcast_trapping(), enable_operator(),
        wait_for_messages(), delete_terminal();

        VAXC$ESTABLISH(wake_handler);

        create_terminal();

        set_characteristics();

        setup_rms();

        sys$cmkrnl(kernel_set_terminal, cmkrnl_args); /* Ignore errors */

        enable_broadcast_trapping();

        enable_operator( 1 );

        wait_for_messages();

        enable_operator( 0 );

        delete_terminal();

        return SS$_NORMAL;

}
        

void create_terminal()
/*

        This routine creates a psuedo terminal.  The channel
        of the terminal and its name are made available to
        the rest of the routines.

Inputs:

        None

Implicit Inputs:

        None

Outputs:

        None

Implicit Outputs:

        PT_Channel:     A word to recieve the channel of the device.
        PT_Name:        A descriptor of where the device name string is
                        stored

Returns:

        None - All errors are signaled.

*/
{
int status, inadr[2], pages = 8 * 512;
short iosb[4];
struct {
        short buflen, itmcod;
        void *bufadr;
        void *retlen;
        } itmlst[] = {  /* Change from FULLDEVNAM to DEVNAM to eliminate
node */
        { 64, DVI$_DEVNAM, PT_Name.dsc$a_pointer, &PT_Name.dsc$w_length },
        {  0, 0, 0, 0 }
        };

/*

        Create some virtual address space for use by the
        psuedo terminal for buffer space.  Allocate the
        maximum required for the device plus 2 guard pages.

*/

        status = lib$get_vm_page( &pages, &inadr[0] );
        if (!(status & 1)) lib$stop(OPERATOR$_GETVMFAIL, 0, status);

        inadr[0] += (512);
        inadr[1] = inadr[0] + (6 * 512);
/*
        Create the terminal device.
*/
        status = ptd$create(&PT_Channel, 0, 0, 0,
                        0, 0, 0, inadr);

        if (!(status&1)) lib$stop(OPERATOR$_CRETERMERR, 0, status);
/*
        Get its full device name.
*/
        status = sys$getdviw(1, PT_Channel, 0, itmlst, iosb, 0, 0, 0);
        if (status&1) status = iosb[0];
        if (!(status&1)) lib$stop(OPERATOR$_GETDVIERR, 0, status);
}


void set_characteristics()
/*

        This routine takes the psuedo terminal and sets up
        the various characteristics of the device.  This
        means disabling displayed broadcasts messages, 
        creating a mailbox, enabling a broadcast mailbox.

Inputs:

        None

Implicit Inputs:

        PT_Channel      The channel of the psuedo terminal device
        Flush_Time_D    String of the Between flushes of the output file
        Flush_Time      VMS Quadword date time        
        MB_Name         Name of the mailbox logical
        
Outputs:

        None

Implicit Outputs:

        MB_Channel      Channel Number of the mailbox

Returns:

        None - Errors are signalled.

*/
{

int status;
short temp_channel, iosb[4];
void flush_routine();
struct {
        char class, type;
        short page_width;
        int characteristics : 24;
        int page_length : 8;
        int extended_characteristics;

        int filler[2];

        } dev_buffer = { DC$_TERM, DT$_VD, 80, 
                TT$M_ESCAPE|TT$M_LOWER|TT$M_NOBRDCST|TT$M_NOTYPEAHD,
                24,
                TT2$M_ANSICRT|TT2$M_BRDCSTMBX, 0, 0 };
/*
        Set up the timer binary time
*/
        status = sys$bintim(&Flush_Time_D, Flush_Time);
        if (!(status&1)) lib$stop(OPERATOR$_BINTIM, 0, status);

        status = sys$setimr(0, Flush_Time, flush_routine, &Output_RAB, 0);
        if (!(status&1)) lib$stop(OPERATOR$_SETIMR, 0, status);
/*
        Create the user mailbox.  Protection on the device
        is s:rwed, o:rwed, g: w: 
*/        
        status = sys$crembx(0, &MB_Channel, MAXMSG, BUFQUO, 0xff00,
                                0, &MB_Name);
        if (!(status&1)) lib$stop(OPERATOR$_CREMAILBOX, 0, status);
/*
        Associate the terminal with the mailbox.

        Don't deassign the superfluous channel.
        So the mailbox association will not go away.
*/
        status = sys$assign(&PT_Name, &temp_channel, 0, &MB_Name);
        if (!(status&1)) lib$stop(OPERATOR$_ATTMAILBOX, 0, status);
/*
        Set device characteristics.
*/
        status = sys$qiow(1, temp_channel, IO$_SETMODE, iosb, 0, 0,
                        &dev_buffer, sizeof(dev_buffer), 0, 0, 0, 0);

        if (status&1) status = iosb[0];
        if (!(status&1)) lib$stop(OPERATOR$_SETMODE, 0, status);

}


void enable_broadcast_trapping()
/*

        The device is enabled with a Read AST is on
        the mailbox to get the broadcasts that are sent.

Inputs:

        None

Implicit Inputs:

        MB_Channel      Channel of the broadcast mailbox.
        User_Info:      I/O Status Block and pointer to buffer;

Outputs:

        None

Implicit Outputs:

        None

Returns:

        None - Errors are signaled.

*/
{
void message_ast();
int status;

        if (User_Info.user_buffer == (void *) 0) 
            User_Info.user_buffer = malloc(USER_BUF_SIZE);

        User_Info.set_ast = 0;

        status = sys$qiow(1, MB_Channel, IO$_SETMODE|IO$M_WRTATTN, 
                        &User_Info.iosb,
                        0, 0, 
                        message_ast, &User_Info, 0, 0, 0, 0);

        if (status&1) status = User_Info.iosb.status;

        if (!(status&1)) lib$stop(OPERATOR$_QIOREQUEST, 0, status);


}


void enable_operator( flag )
/*

        This routine enables the psuedo terminal as an operator.

Inputs:

        flag:           1 meaning to enable / 0 meaning to disable

Implicit Inputs:

        PT_Channel:     Channel of the psuedo terminal device.
        PT_Name:        String name of the device.

Outputs:

        None

Implicit Outputs:

        None

Returns:

        None - Errors are signaled.

*/
int flag;

{
int status, i;
char *cp, *cp2;
short iosb[4];
struct OPC opcom_message;
struct dsc$descriptor_s OPC_D = 
        { sizeof(opcom_message), DSC$K_DTYPE_T, DSC$K_CLASS_S,
&opcom_message};

struct {
        short buflen, itmcod;
        void *bufadr;
        void *retlen;
        } itmlst[] = {
                {  4, DVI$_UNIT, &opcom_message.opc$w_ms_ounit, 0 },
                {  0, 0, 0, 0 }
                };

        memset(&opcom_message, 0, sizeof(opcom_message));

        opcom_message.opc$b_ms_type = OPC$_RQ_TERME;
        opcom_message.opc$b_ms_enab = flag;
        opcom_message.opc$l_ms_mask = OPC$M_NM_CENTRL | OPC$M_NM_SECURITY;

        status = sys$getdviw(1, PT_Channel, 0, itmlst, iosb, 0, 0, 0);
        if (status&1) status = iosb[0];
        if (!(status&1)) lib$stop(OPERATOR$_GETDVIERR, 0, status);
/*

    Set the device name in the message.  We only want the _ddc portion
    (of _ddcu:) and not the unit portion.  The -2 compensates for the 
    zero based array and the ":" in the device name.

*/
        for (i = PT_Name.dsc$w_length-2, cp = PT_Name.dsc$a_pointer; i>=0;
i--)
                if ((cp[i] < '0') || (cp[i] > '9')) break;

        cp2 = &opcom_message.opc$t_ms_oname;
        *cp2 = i + 1;
        memcpy((cp2+1), cp, *cp2);

        status = sys$sndopr(&OPC_D, 0);

        if (!(status&1)) lib$stop(OPERATOR$_ENABLEOPR, 0, status);

}


void wait_for_messages()
/*

        Here we hibernate until we are awoken.  All other routine
        are called at AST level.

Inputs:

        None

Implicit Inputs:

        None

Outputs:

        None

Implicit Outputs:

        None

Returns:

        Nothing.

*/
{


        sys$hiber();

}


void delete_terminal()
/*

        When the image is awoken, we delete the
        terminal

Inputs:

        None

Implicit Inputs:

        PT_Channel

Outputs:

        None

Implicit Outputs:

        None

Returns:

        Nothing Errors in this routine are ignored.

*/
{

        ptd$delete(PT_Channel);

}

void message_ast(Info)
/*

        This routine is entered when there is
        a message to read on the mailbox.  We read that
        message and call a routine to write out that
        message.  This routine must not exit unless
        an outstanding read is posted or a write attention
        AST has been posted.

Inputs:

        Info: Pointer to a structure with the I/O Status and buffer

Implicit Inputs:

        MB_Channel: Channel to the mailbox

Outputs:

        None

Implicit Outputs:

        None

Returns:

*/

struct INFO *Info;

{
int status;
struct MBXMSG *msg;
void write_message();

        msg = (struct MBXMSG *) Info->user_buffer;

        do {
                status = sys$qiow(2, MB_Channel, IO$_READVBLK|IO$M_NOW, 
                        &Info->iosb, 0, 0, 
                        Info->user_buffer, MAXMSG, 0, 0, 0, 0);

                if (status&1) status = Info->iosb.status;

                if ( (!(status&1)) && (status != SS$_ENDOFFILE) )
                        lib$stop(OPERATOR$_QIOREQUEST, 0, status);

                if (status == SS$_ENDOFFILE) {
                
                        status = sys$qiow(1, MB_Channel, 
                                IO$_SETMODE|IO$M_WRTATTN, 
                                &Info->iosb,
                                0, 0, 
                                message_ast, Info, 0, 0, 0, 0);

                        if (status&1) status = Info->iosb.status;
        
                        if (!(status&1)) lib$stop(OPERATOR$_QIOREQUEST, 0,
status);

                        break;
                }
        if (msg->msg$w_type == MSG$_TRMBRDCST)
                write_message(&msg->msg$t_message, msg->msg$w_length);
        } while (status&1);

}


void write_message(message, message_size)
/*

        This routine does the actual writing of a message
        to the file.

Inputs:

        message:        Pointer to a buffer with the message
        message_size:   Size of the message passed to us.

Implicit Inputs:

        Output_RAB:    RMS RAB block of connected stream

Outputs:

        None

Implicit Outputs:

        None

Returns:

        None - Errors Are handled via AST routine.
*/

char *message;
short message_size;

{
int status;
void put_error();

        Output_RAB.rab$l_rop &= (~RAB$M_ASY);   /* Clear the ASY bit */
        status = sys$wait(&Output_RAB);         /* Wait for the last I/O */

        Output_RAB.rab$l_rbf = message;         /* Set up the record
buffer */
        Output_RAB.rab$w_rsz = message_size;    /* And the message size */

        Output_RAB.rab$l_rop |= RAB$M_ASY;      /* Set the ASY bit */

        sys$put(&Output_RAB, put_error, 0);
        
}


void setup_rms()
/*

        This module initializes the structures and opens
        and connects to the FILE_NAME (SYS$OUTPUT) file.  Messages
        are written to that file.

Inputs:

        None

Implicit Inputs:

        FILE_NAME       Defined Symbol with the file name to open
        DEFAULT_NAME    Defined Symbol with the Default file name
        Output_FAB      RMS FAB Block
        Output_RAB      RMS RAB Block
        Output_NAM      RMS NAM Block
        Output_RSS      The Resultant String block

Outputs:

        None

Implicit Outputs:

        None

*/
{

struct FAB *fab;
struct RAB *rab;
struct NAM *nam;
int status, name_size;
struct dsc$descriptor_s DSC = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 };
char *name;

/*
        Set up local pointers to the structure.
*/
        fab = &Output_FAB;
        rab = &Output_RAB;
        nam = &Output_NAM;
/*
        Initialize the structure
*/
        *fab = cc$rms_fab;
        *rab = cc$rms_rab;
        *nam = cc$rms_nam;
/*
        set up the FAB portions
*/
        fab->fab$l_fna = FILE_NAME;
        fab->fab$b_fns = strlen(FILE_NAME);
        fab->fab$l_dna = DEFAULT_NAME;
        fab->fab$b_dns = strlen(DEFAULT_NAME);
        fab->fab$l_fop = FAB$M_CIF | FAB$M_TEF;
        fab->fab$b_fac = FAB$M_PUT;
        fab->fab$b_shr = FAB$M_GET | FAB$M_UPI;
        fab->fab$b_rfm = FAB$C_VAR;
        fab->fab$b_rat = FAB$M_CR;
        fab->fab$l_alq = 100;
        fab->fab$w_deq = 100;
        fab->fab$l_nam = nam;
/*
        Set up the RAB portions
*/
        rab->rab$l_fab = fab;
        rab->rab$l_rop = RAB$M_EOF;
        rab->rab$b_rac = RAB$C_SEQ;
/*
        Set up the NAM
*/
        nam->nam$l_rsa = Output_RSS;
        nam->nam$b_rss = NAM$C_MAXRSS;
/*
        Create the file (open if already there)
*/
        status = sys$create(fab);

        name_size = (nam->nam$b_rsl !=0) ? 
                     nam->nam$b_rsl : fab->fab$b_fns;

        name = (nam->nam$b_rsl !=0) ? 
                     nam->nam$l_rsa : fab->fab$l_fna;

        DSC.dsc$w_length = name_size;
        DSC.dsc$a_pointer = name;

        if (!(status&1)) {
                lib$stop(OPERATOR$_OPENOUT, 1, &DSC,
                                status, fab->fab$l_stv);
                }

        status = sys$connect(rab);
        if (!(status&1)) {
                lib$stop(OPERATOR$_CONNECTERR, 1, &DSC, 
                                status, rab->rab$l_stv);
                }

}


void flush_routine(rab)
/*

        This routine  performs an RMS flush of the current
        output file. It resets the timer for the next 
        go around.

Inputs:

        rab:    Address of an RMS rab for the output

Implicit Inputs:

        Flush_Time:     VMS Quadword of the flush interval

Outputs:

        None

Implicit Outputs:

        None

returns:

        Nothing
*/
struct RAB *rab;
{
int status;

        if (rab->rab$w_isi != 0)        /* If not connected, don't flush */
                sys$flush(rab);

        status = sys$setimr(0, Flush_Time, flush_routine, rab, 0);
        if (!(status&1)) lib$stop(OPERATOR$_SETIMR, 0, status);

}


int wake_handler(sigargs, mechargs)
/*

        This routine is established as an exception handler.
        It $WAKEs the process, closes the output file and returns
        SS$_RESIGNAL;

Inputs:

        sigargs         Address of the user signal arguments
        mechargs        Address of the mechanism array

Implicit Inputs:

        Output_FAB:     FAB block

Outputs:

        None

Implicit Outputs:

        None

returns:

        SS$_RESIGNAL

*/
struct chf$signal_array *sigargs;
struct chf$mech_array *mechargs;
{

        sys$wake(0, 0);
/*
        Close the output file before allowing errors to
        be written there.  Those should be in SYS$ERROR
*/
        sys$close(&Output_FAB);

        return SS$_RESIGNAL;

}


void put_error(rab)
/*

        This routine is called when the RMS SYS$PUT operation
        is going to signal an error.

Inputs:

        rab:            Pointer to a RMS RAB structure

Implict Inputs:

        None

Outputs:

        None

Implicit Outputs:

        None

Returns

        None - error is signaled.

*/
struct RAB *rab;
{

struct FAB *fab;
struct NAM *nam;
struct dsc$descriptor_s DSC = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 };

        fab = rab->rab$l_fab;
        nam = fab->fab$l_nam;
        DSC.dsc$w_length = nam->nam$b_rsl;
        DSC.dsc$a_pointer = nam->nam$l_rsa;
        lib$stop(OPERATOR$_PUTERROR, 1, &DSC,
                rab->rab$l_sts, rab->rab$l_stv);

}


int kernel_set_terminal(Device_Name)
struct dsc$descriptor_s *Device_Name;

/*

        This is a kernel routine that takes the name of the
        users terminal and places it into the JIB$T_TERMINAL
        field so as to be able to receive broadcasts sent
        to a user.  Requires CMKRNL privs.

Inputs:

        Device_Name:    Pointer to a string descriptor with
                        the users terminal name.

Implicit Inputs:

        General Purpose Register 4 (R4) : address of the
                        users PCB.

Outputs:

        None

Implicit Output:

        The users JIB$T_TERMINAL field is modified.

returns:

        Success.

        NOTE: PROGRAM USES THE VAX C BUILTIN READ_GPR TO
        OBTAIN THE USERS PCB ADDRESS.  THIS MUST BE
        THE FIRST INSTRUCTION IN THE KERNEL ROUTINE
        SO AS NOT TO KILL THE SYSTEM.  OTHERWISE
        THE CODE MUST BE CHANGED TO USE CTL$GL_PCB
        AS A POINTER TO THE PCB.  THE PCB 
        CONSTANTS ARE CORRECT FOR V5.5 OF VMS ONLY.
*/
{
#pragma builtins

char *ptr;

    ptr = _READ_GPR(4);

    ptr = ptr + PCB$T_TERMINAL;     /* Set offset to terminal field */

/* Value cannot exceed field */

    *ptr = ((Device_Name->dsc$w_length-1) & ((PCB$S_TERMINAL - 1)));

/*
        We must compensate for the leading "_" in the device
        name.  Move the pointer up one and the size of the
        string down one.  The potential ":" at the end will be
        properly truncated due to the output field size.
*/

    _MOVC5((short) *ptr, (Device_Name->dsc$a_pointer+1), 
                ' ',                    /* Pad with space */
                (short) (PCB$S_TERMINAL - 1), ptr + 1);

    return 1;
}

/* operatormsg.h */
/*************************************************************************
*******************************************************/
/* Created 19-DEC-1991 18:03:13 by VAX SDL V3.2-12     Source: 19-DEC-1991
18:03:11 KGB$ROOT:[C]OPERATORMSG.SDL;8 */
/*************************************************************************
*******************************************************/
 
/*** MODULE $OPERATORDEF ***/
/*                                                                        
 */
/* This SDL File Generated by VAX-11 Message V04-00 on 19-DEC-1991
18:03:11.51 */
/*                                                                        
 */
*/
#define OPERATOR$_FACILITY 2040
#define OPERATOR$_CRETERMERR 133726220
#define OPERATOR$_BINTIM 133726228
#define OPERATOR$_SETIMR 133726236
#define OPERATOR$_CREMAILBOX 133726244
#define OPERATOR$_ATTMAILBOX 133726252
#define OPERATOR$_SETMODE 133726260
#define OPERATOR$_QIOREQUEST 133726268
#define OPERATOR$_GETDVIERR 133726276
#define OPERATOR$_ENABLEOPR 133726284
#define OPERATOR$_GETVMFAIL 133726292
#define OPERATOR$_OPENOUT 133726300
#define OPERATOR$_CONNECTERR 133726308
#define OPERATOR$_PUTERROR 133726316
*/

/*

        .title  OPERATOR OPCOM Reciever message definitions
        .ident  'X01.00'
        .facility       operator, 2040/system

        .severity       fatal

        CRETERMERR      <Unable to create pseudo terminal
device>/fao_count=0
        BINTIM          <$BINTIM System Service failure>/fao_count=0
        SETIMR          <$SETIMR System Service failure>/fao_count=0
        CREMAILBOX      <$CREMBX System Service failure>/fao_count=0
        ATTMAILBOX      <$ASSIGN System Service failure>/fao_count=0
        SETMODE         <$QIO SETMODE System Service failure>/fao_count=0
        QIOREQUEST      <$QIO System Service failure>/fao_count=0
        GETDVIERR       <$GETDVI System Service failure>/fao_count=0
        ENABLEOPR       <$SNDOPR System Service failure>/fao_count=0
        GETVMFAIL       <failure to allocate virtual memory>/fao_count=0
        OPENOUT         <error $OPENing file !AS>/fao_count=1
        CONNECTERR      <error $CONNECTing to file !AS>/fao_count=1
        PUTERROR        <error $PUTing to file !AS>/fao_count=1

        .END


*/

/* operatormsg.msg 
*/
