/* * © Copyright 1976, 2003 Hewlett-Packard Development Company, L.P. * * Confidential computer software. Valid license from HP and/or its * subsidiaries required for possession, use, or copying. * * Consistent with FAR 12.211 and 12.212, Commercial Computer Software, * Computer Software Documentation, and Technical Data for Commercial * Items are licensed to the U.S. Government under vendor's standard * commercial license. * * Neither HP nor any of its subsidiaries shall be liable for technical * or editorial errors or omissions contained herein. The information * in this document is provided "as is" without warranty of any kind * and is subject to change without notice. The warranties for HP * products are set forth in the express limited warranty statements * accompanying such products. Nothing herein should be construed as * constituting an additional warranty. */ /* * HISTORY * $Log: chess_method.c,v $ * * 2001/09/24 Sheldon Bishov V5.3-00 * Use OpenVMS value for OID in trap, vs. UNIX, avoid confusion. * * 1999/10/06 Sheldon Bishov V5.1-02 * Disable FALLOFFEND for whole module, all routines handled * correctly. Beware if add new routine. * * 1999/09/17 Sheldon Bishov V5.1-01 * Handle informationals, e.g. INTRINSICINT. * * 1999/05/28 Sheldon Bishov V5.1-00 * Added sample code to fail on commit, to test UNDO (based on sample * from E. Abis) * * Revision 1.1.9.3 1999/03/29 Sheldon Bishov V2.0-01 * Added sample code to return nulls (non-RFC behavior) * * Revision 1.1.9.2 1997/09/19 15:33:18 Mark_Ellison * Fixed core on undo processing (QAR 55148). Also reordered check for * resources in gameEntry_set and moveEntry_set. Please note the comment * above gameEntry_set has been updated. * [1997/09/16 14:35:54 Mark_Ellison] * * Revision 1.1.2.5 1995/10/25 13:47:51 Michael_Daniele * Pass 6 not 0 as value of 'generic' arg to esnmp_trap(). * [1995/10/25 13:45:29 Michael_Daniele] * * Revision 1.1.2.4 1995/06/30 17:00:50 David_Keeney * Fix compile warning on type passed to bzero, line 310. * [1995/06/30 14:35:46 David_Keeney] * * Cleaned up GETNEXT's * [1995/04/25 22:50:32 David_Keeney] * * Initial Checkin * [1995/04/24 15:58:15 David_Keeney] * * Revision 1.1.2.3 1995/06/29 19:33:26 Michael_Daniele * Cleaned up comments. * [1995/06/27 17:35:19 Michael_Daniele] * * Revision 1.1.2.2 1995/04/26 16:01:22 David_Keeney * Cleaned up GETNEXT's * [1995/04/25 22:50:32 David_Keeney] * * Initial Checkin * [1995/04/24 15:58:15 David_Keeney] * * $EndLog$ */ #ifndef __VMS #pragma ident "@(#)$RCSfile: chess_method.c,v $ $Revision: 1.1.9.2 $ (DEC) $Date: 1997/09/19 15:33:18 $" #else #pragma module chess_method "V5.3-00" #pragma message save /* V5.1-02 */ #pragma message disable FALLOFFEND #endif /* ************************************************************************ * * * The method routines and internal support code for the HP Tru64 UNIX * * eSNMP example sub-agent. * * * * Author: Mike Daniele * * Date: April, 1995 * * * * The chess MIB represents a repository of chess games. * * A game is simply a collection of moves. * * A move is an ascii string containing algebraic or descriptive * * chess notation. * * * * This program does not 'play' chess, it simply provides SNMP access * * to a database of chess moves, organized into games. * * * * The MIB is structured as follows: * * * * General Information: * * chessProductID - the objectID of this software * * chessMaxGames - max number of games we'll maintain * * chessNumGames - current number of games we have * * * * GameTable - each game is represented as an entry * * gameIndex - unique identifier of this game * * gameDescr - description (players, date, tournament, etc) * * gameNumMoves - number of moves in this game so far * * gameStatus - one of { complete, underway, delete } * * * * MoveTable - each move pair (white/black) of each game is * * an entry, indexed by {gameIndex, moveIndex} * * moveIndex - id of move within a game * * moveByWhite - white player's move * * moveByBlack - black player's move * * * * SNMP applications may retrieve any MIB object. * * SNMP applications may create or delete gameEntries using Sets. * * SNMP applications may add moves or delete the last move from an * * an existing game using Sets. * * * * Traps are sent whenever a good move is accessed. * * * ************************************************************************ */ #include #include "esnmp.h" /* eSNMP definitions and prototypes */ #include "chess_tbl.h" /* chess subtree object table */ /************************************************************************ | Internal data structures. ************************************************************************/ #define MOVE_SIZE 16 #define DESCR_SIZE 64 #define MAX_MOVES 1024 #define MAX_GAMES 10 typedef struct _move { struct _move *next; struct _move *prev; unsigned char w[MOVE_SIZE]; /* white move */ unsigned char b[MOVE_SIZE]; /* black move */ int index; /* Move number */ int sts; /* In use/free */ } Move; typedef struct _game { Move *moves; /* ptr to linked list of move structures */ Move *lastmove; /* ptr to last on linked list */ unsigned char descr[DESCR_SIZE]; /* description */ int nummoves; /* current number of moves */ int index; /* its gameIndex */ int sts; /* current status */ } Game; /************************************************************************ | Data declarations. ************************************************************************/ /* * We have a static table of available moves. * and one for available games. Each game vies * for move resources with the others. */ #define NULL 0L static Move move_tbl[MAX_MOVES] = { /* Load our default games */ /* Byrne-Fischer, US Championship 1963 */ { NULL, NULL, "1. d4", "1...Nf6", 1, 1}, { NULL, NULL, "2. c4", "2...g6", 2, 1}, { NULL, NULL, "3. g3", "3...c6", 3, 1}, { NULL, NULL, "4. Bg2", "4...d5", 4, 1}, { NULL, NULL, "5. cd", "5...cd", 5, 1}, { NULL, NULL, "6. Nc3", "6...Bg7", 6, 1}, { NULL, NULL, "7. e3", "7...O-O", 7, 1}, { NULL, NULL, "8. Nge2", "8...Nc6", 8, 1}, { NULL, NULL, "9. O-O", "9...b6", 9, 1}, { NULL, NULL, "10. b3", "10...Ba6", 10, 1}, { NULL, NULL, "11. Ba3", "11...Re8", 11, 1}, { NULL, NULL, "12. Qd2", "12...e5", 12, 1}, { NULL, NULL, "13. de", "13...Ne5", 13, 1}, { NULL, NULL, "14. fRd1?", "14...Nd3!", 14, 1}, { NULL, NULL, "15. Qc2", "15...Nxf2!", 15, 1}, { NULL, NULL, "16. Kxf2", "16...Ng4+", 16, 1}, { NULL, NULL, "17. Kg1", "17...Nxe3", 17, 1}, { NULL, NULL, "18. Qd2", "18...Nxg2!", 18, 1}, { NULL, NULL, "19. Kxg2", "19...d4!", 19, 1}, { NULL, NULL, "20. Nxd4", "20...Bb7+", 20, 1}, { NULL, NULL, "21. Kf1", "21...Qd7!", 21, 1}, { NULL, NULL, "resigns", "0-1", 22, 1}, /* Anderssen - Lange, Breslau 1859 */ { NULL, NULL, "1. e4", "1...e5", 1, 1}, { NULL, NULL, "2. Nf3", "2...Nc6", 2, 1}, { NULL, NULL, "3. Bb5", "3...Nd4", 3, 1}, { NULL, NULL, "4. Nxd4", "4...exd4", 4, 1}, { NULL, NULL, "5. Bc4", "5...Nf6", 5, 1}, { NULL, NULL, "6. e5", "6...d5", 6, 1}, { NULL, NULL, "7. Bb3", "7...Bg4", 7, 1}, { NULL, NULL, "8. f3", "8...Ne4", 8, 1}, { NULL, NULL, "9. O-O", "9...d3!", 9, 1}, { NULL, NULL, "10. fxg4", "10...Bc5+", 10, 1}, { NULL, NULL, "11. Kh1", "11...Ng3+!", 11, 1}, { NULL, NULL, "12. hxg3", "12...Qg5!", 12, 1}, { NULL, NULL, "13. Rf5", "13...h5!", 13, 1}, { NULL, NULL, "14. gh", "14...Qxf5", 14, 1}, { NULL, NULL, "15. g4", "15...Rxh5+!!", 15, 1}, { NULL, NULL, "16. gxh5", "16...Qe4!", 16, 1}, { NULL, NULL, "17. Qf3", "17...Qh4+", 17, 1}, { NULL, NULL, "18. Qh3", "18...Qe1+", 18, 1}, { NULL, NULL, "resigns", "0-1", 19, 1}, /* Stienitz - Von Bardeleben, Hastings 1895 */ { NULL, NULL, "1. e4", "1...e5", 1, 1}, { NULL, NULL, "2. Nf3", "2...Nc6", 2, 1}, { NULL, NULL, "3. Bc4", "3...Bc5", 3, 1}, { NULL, NULL, "4. c3", "4...Nf6", 4, 1}, { NULL, NULL, "5. d4", "5...ed", 5, 1}, { NULL, NULL, "6. cd", "6...Bb4+", 6, 1}, { NULL, NULL, "7. Nc3", "7...d5", 7, 1}, { NULL, NULL, "8. ed", "8...Nxd5", 8, 1}, { NULL, NULL, "9. O-O", "9...Be6", 9, 1}, { NULL, NULL, "10. Bg5", "10...Be7", 10, 1}, { NULL, NULL, "11. Bxd5", "11...Bxd5", 11, 1}, { NULL, NULL, "12. Nxd5", "12...Qxd5", 12, 1}, { NULL, NULL, "13. Bxe7", "13...Nxe7", 13, 1}, { NULL, NULL, "14. Re1", "14...f6", 14, 1}, { NULL, NULL, "15. Qe2", "15...Qd7", 15, 1}, { NULL, NULL, "16. aRc1", "16...c6?", 16, 1}, { NULL, NULL, "17. d5!!", "17...cd", 17, 1}, { NULL, NULL, "18. Nd4", "18...Kf7", 18, 1}, { NULL, NULL, "19. Ne6", "19...hRc8", 19, 1}, { NULL, NULL, "20. Qg4!", "20...g6", 20, 1}, { NULL, NULL, "21. Ng5+", "21...Ke8", 21, 1}, { NULL, NULL, "22. Rxe7+!", "22...Kf8", 22, 1}, { NULL, NULL, "23. Rf7+", "23...Kg8", 23, 1}, { NULL, NULL, "24. Rg7+!", "24...Kh8", 24, 1}, { NULL, NULL, "25. Rxh7+", "25...resigns", 25, 1}, { NULL, NULL, "1-0", "1-0", 26, 1}, /* Daniele - LeBlanc Postal US 84VC110 */ { NULL, NULL, "1. d4", "1...d5", 1, 1}, { NULL, NULL, "2. c4", "2...Nf6", 2, 1}, { NULL, NULL, "3. Nc3", "3...c6", 3, 1}, { NULL, NULL, "4. Nf3", "4...g6", 4, 1}, { NULL, NULL, "5. Bf4", "5...Bg7", 5, 1}, { NULL, NULL, "6. h3", "6...O-O", 6, 1}, { NULL, NULL, "7. e3", "7...dc", 7, 1}, { NULL, NULL, "8. Bxc4", "8...b5", 8, 1}, { NULL, NULL, "9. Bd3", "9...Nd5", 9, 1}, { NULL, NULL, "10. Nxd5", "10...cd", 10, 1}, { NULL, NULL, "11. O-O", "11...b4?", 11, 1}, { NULL, NULL, "12. Qb3", "12...Na6", 12, 1}, { NULL, NULL, "13. fRc1", "13...Bb7", 13, 1}, { NULL, NULL, "14. Bxa6", "14...Bxa6", 14, 1}, { NULL, NULL, "15. Qxb4", "15...e6", 15, 1}, { NULL, NULL, "16. Rc6", "16...Bd3", 16, 1}, { NULL, NULL, "17. aRc1", "17...a5", 17, 1}, { NULL, NULL, "18. Qb7", "18...a4", 18, 1}, { NULL, NULL, "19. Bd6!", "19...Re8", 19, 1}, { NULL, NULL, "20. Ne5", "20..Bxe5", 20, 1}, { NULL, NULL, "21. de", "21...Bf5", 21, 1}, { NULL, NULL, "22. Rc7!", "22...Rb8", 22, 1}, { NULL, NULL, "23. Qc6", "23...Rxb2", 23, 1}, { NULL, NULL, "24. Qxe8+!", "24...Qxe8", 24, 1}, { NULL, NULL, "25. Rc8", "25...Qxc8", 25, 1}, { NULL, NULL, "26. Rxc8+", "26...Kg7", 26, 1}, { NULL, NULL, "27. Bf8+", "27...resigns", 27, 1}, { NULL, NULL, "1-0", "1-0", 28, 1} }; static Game game_tbl[MAX_GAMES] = { { &move_tbl[0], &move_tbl[21], "Byrne-Fischer, US Championship 1963", 22, 1, D_gameStatus_complete }, { &move_tbl[22], &move_tbl[40], "Anderssen - Lange, Breslau 1859", 19, 2, D_gameStatus_complete}, { &move_tbl[41], &move_tbl[66], "Stienitz - Von Bardeleben, Hastings 1895", 26, 3, D_gameStatus_complete}, { &move_tbl[67], &move_tbl[94], "Daniele - LeBlanc, U.S. Postal 84VC110", 28, 4, D_gameStatus_complete} }; static int num_games = 4; #ifndef __VMS /* * For our productID we're using the one registered for HP Tru64 Unix. */ static char *prod_oid_str = "1.3.6.1.3.4.36.2.15.2"; static unsigned int prod_id_elem[10] = { 1,3,6,1,3,4,36,2,15,2 }; static OID prod_oid = { 10, prod_id_elem }; #else /* V5.3-00 ** Use OpenVMS value */ static char *prod_oid_str = "1.3.6.1.3.4.36.2.15.22.1"; static unsigned int prod_id_elem[11] = { 1,3,6,1,3,4,36,2,15,22,1 }; static OID prod_oid = { 11, prod_id_elem }; #endif /* * Data for generating the goodMove! trap. */ static VARBIND trap_vb[2]; /* * Because we'll need to access the emitted object table directly, * pick it up here. */ static OBJECT *ot; /******************************************************************* | | Support Routines: | | This section contains private functions used to support the | method routines for the chess sub-agent. | | chess_initialize() - Called from main(), set up static data | (initial games & moves, trap VARBINDs, etc) | | find_game() - Handle SNMP lexicographical semantics and identify | a Game. | | load_game() - Copy the contents of a Game into a gameEntry_type. | | find_move() - Handle SNMP lex semantics and identify a Move. | | load_move() - Copy the contents of a Move into a moveEntry_type. | | gen_trap() - Generate a goodMove! trap. | *******************************************************************/ /******************************************************************* | | Finish initializing the game and move tables. | | Initialize the list of VARBINDs for use later in generating | traps. | | Point our OBJECT ptr to the subtree's object table. | NOTE: It is not (always) possible to do this as part of | declaration because the subtree itself is initialized in | its declaration.) | *******************************************************************/ int chess_initialize(void) { int i; ot = chess_subtree.object_tbl; /* Fix up the next/prev ptrs in the move tbl. */ for (i = 1; i < (22+19+26+28); i++) { move_tbl[i].next = &move_tbl[i+1]; move_tbl[i].prev = &move_tbl[i-1]; } move_tbl[0].next = &move_tbl[1]; move_tbl[21].next = NULL; move_tbl[22].prev = NULL; move_tbl[40].next = NULL; move_tbl[41].prev = NULL; move_tbl[66].next = NULL; move_tbl[67].prev = NULL; move_tbl[94].next = NULL; bzero((char *)trap_vb, sizeof(trap_vb)); trap_vb[0].next = &trap_vb[1]; return(ESNMP_MTHD_noError); } /******************************************************************* | | Locate a game_tbl element based on the passed index and | request type. | | This routine implements the SNMP Get and GetNext/Bulk search | semantics. For ESNMP_ACT_GET we need to find an entry whose | index is = to the requested index. For GET_NEXT/BULK we need | to find the entry with the lowest index greater than the | requested index. The table entries are initialized in ascending | order of indexes, but are not guaranteed to stay that way. So | search the entire table. Indexes are guaranteed to be between | 1 and MAX_GAMES inclusive. | | (In our game_tbl array, and element of the array is valid | if its sts field is non-0.) | ******************************************************************/ static Game *find_game( int gi, int r ) { int i, j; #if (__DECC_VER >= 60200000) #pragma message save /* V5.1-01 #ifdef __VMS */ #pragma message disable INTCONSTSIGN unsigned int s = 0xffffffff; #pragma message restore #else unsigned int s = 0xffffffff; #endif for (i = 0, j = -1; i < MAX_GAMES; i++) { if (game_tbl[i].sts) { if (r == ESNMP_ACT_GET) { if (game_tbl[i].index == gi) return(&game_tbl[i]); } else if ((game_tbl[i].index > gi) && (game_tbl[i].index < s)) { s = game_tbl[i].index; j = i; } } } if (r == ESNMP_ACT_GET) return(NULL); else if (j != -1) return(&game_tbl[j]); else return(NULL); } /******************************************************************* | | Find an unused game tbl entry. | ******************************************************************/ static Game *find_game_slot() { int i; for (i = 0; i < MAX_GAMES; i++) { if (!game_tbl[i].sts) return(&game_tbl[i]); } return(NULL); } /******************************************************************* | | Copy a Game into a gameEntry_type. | | NOTE: This routine does not allocate memory, it simply | points to the same memory used by the Game. | ******************************************************************/ static void load_game( gameEntry_type *ge, Game *g ) { ge->gameIndex = g->index; ge->gameNumMoves = g->nummoves; ge->gameStatus = g->sts; ge->gameDescr.ptr = g->descr; #pragma message save /* V5.1-01 #ifdef __VMS */ #pragma message disable INTRINSICINT ge->gameDescr.len = strlen(g->descr); #pragma message restore } /******************************************************************* | | Locate a move_tbl element based on the passed indexes and | request type. | | This routine implements the SNMP Get and GetNext/Bulk search | semantics. | | The move table is indexed by the combination (gameIndex, moveIndex). | So moveByWhite.2.23 means White's 23rd move in game 2. | | For a successful GET action, both game and move index must match | exactly. | | For NEXT/BULK, the logic is a bit more involved. If requested for | moveByWhite.g.m, we check (in this order) for: | | g.n, where n is the smallest move in game g > m | | h.l, where h is the smallest game number > g and l is | the lowest move number in game h. | | In a nutshell, if asked for game 2's 23 move, but it doesn't have | 23 moves, return the first move in game 3. If there is no game | 3, return the first move of game 4, etc. Fail only if there is | no game (with moves) with a higher index than 2. | | We maintain the list of moves for a game in order of ascending | moveIndex, so we only need to search a game's move lists until we | have a hit. | ******************************************************************/ static Move *find_move( int gi, int mi, int r, Game **gp ) { int srch = mi; Game *g; Move *m; g = find_game(gi, ESNMP_ACT_GET); if (r == ESNMP_ACT_GET) { if ((!g) || (g->nummoves < srch)) return(NULL); } else { if ((!g) || (g->nummoves <= srch)) { g = find_game(gi, r); srch = 0; } if (!g) return(NULL); if (g->nummoves <= srch) return(find_move(g->index+1, 0, r, gp)); } for (m = g->moves; m; m = m->next) { if (((r == ESNMP_ACT_GET) && (m->index == srch)) || ((r != ESNMP_ACT_GET) && (m->index > srch))) { *gp = g; return(m); } } return(NULL); } /******************************************************************* | | Copy a Move into a moveEntry_type. | | NOTE: This routine does not allocate memory, it simply | points to the same memory used by the Move. | ******************************************************************/ static void load_move( moveEntry_type *me, Move *m, Game *g ) { me->gameIndex = g->index; me->moveIndex = m->index; me->moveByWhite.ptr = m->w; me->moveByWhite.len = strlen(m->w); me->moveByBlack.ptr = m->b; me->moveByBlack.len = strlen(m->b); } /******************************************************************* | | Find an unused move tbl entry. (A free entry has sts == 0). | ******************************************************************/ static Move *find_move_slot() { int i; for (i = 0; i < MAX_MOVES; i++) { if (!move_tbl[i].sts) return(&move_tbl[i]); } return(NULL); } /******************************************************************* | | Reclaim the move tbl entries that are currently being used | by a game tbl entry. We are passed the head of the move | list. Just walk the list and clear the front & back ptrs, | and set sts to 0. | ******************************************************************/ static void reclaim_move_slots( Move *head ) { Move *m = head, *f; while(m) { f = m->next; m->prev = m->next = NULL; m->sts = 0; m = f; } } /******************************************************************* | | Add a move to a game. We're passed the Move and Game ptrs. | We assume the Move already has its string data intact, and | simply needs to be inserted at the end of the move list | for this Game. | ******************************************************************/ static void add_move( Move *m, Game *g ) { m->prev = g->lastmove; m->next = NULL; m->index = ++(g->nummoves); m->sts = 1; if (g->lastmove) g->lastmove->next = m; g->lastmove = m; if (!g->moves) g->moves = m; } /******************************************************************* | | Remove the Move at the end of a Game's move list. | Adjust ptrs only. In particular, don't clear the string | data. (It wasn't malloc-ed to begin with, and this permits | easy "UNDOs". Clear both move ptrs, effectively putting this | Move back on the 'free list'. | ******************************************************************/ static void kill_move( Game *g ) { Move *m; if (g->lastmove) { m = g->lastmove->prev; if (m) m->next = NULL; g->lastmove->prev = g->lastmove->next = NULL; g->lastmove->sts = 0; if (g->moves == g->lastmove) g->moves = m; g->lastmove = m; --(g->nummoves); } } /******************************************************************* | | Generate the goodMove! trap. | A trap is generated by calling esnmp_trap(generic, specific, vb_list, sysoid). | where: | generic - an integer representing an architected trap type | specific - an integer representing an implementation-specific trap type | vb_list - a linked list of VARBINDs containing data to be included in the | trap | sysoid - An OID representing the sysObjectID of the sending entity | (If NULL, the value of MIB-II sysObjectID will be used.) | | eSNMP expects the correct vb_list when generic != 0, otherwise you can | do whatever you want. (coldStart, linkdown, etc are generic traps). | | Of course, for a trap receiver program to correctly interpret what you | send, there must exist a MIB specifying these details, and you must do | it correctly. But eSNMP has no knowledge of those details. | | NOTE that when sending enterprise-specific traps, you should specify | a value of 6 for 'generic'. This is the architected SNMP value | for enterprise-specific (as opposed to coldStart, etc). | | Our goodMove trap will contain 2 VarBinds, the value of gameDescr, and | the value of moveByWhite/Black. We set up the fields in the VarBinds | by using the o_xxx routines to load data, just like usual. What's | slightly different is that there is no OID at all in these varbinds | (whereas the one in the method struct always has the base oid of the | object pre-loaded). So we first have to copy the base oid from the | object table. | | NOTE WELL: When we load data into a METHOD's varbind, libesnmp handles virtual memory. | When we load data into our private varbinds, we have to handle | memory ourselves. | | The libesnmp routine free_varbind_data is useful for this. | It will | free the VARBIND.name (free data pointed to by name.ptr, | set name.length to 0) | clear the VARBIND.octet/oid (free data pointed to by ptr and | set appropriate len/size t 0) if the VARBIND.type field indicates | | and that's all. It does not alter any other VARBIND field, nor | does it free the VARBIND itself. | *******************************************************************/ static void gen_trap( OBJECT *moveobj, unsigned char *movedata, OBJECT *gameobj, unsigned char *gamedata) { o_string(&trap_vb[1], moveobj, movedata, strlen(movedata)); clone_oid(&trap_vb[0].name, &ot[I_gameDescr].oid); o_string(&trap_vb[0], gameobj, gamedata, strlen(gamedata)); clone_oid(&trap_vb[1].name, &moveobj->oid); esnmp_trap(6, 1, prod_oid_str, trap_vb); free_varbind_data(&trap_vb[0]); free_varbind_data(&trap_vb[1]); } /******************************************************************* | | Method Routines: | | This section contains the method routines for the 3 groups | within the chess MIB. These functions are pointed to by | elements of the object table defined in chess_tbl.c, and are | called by the libesnmp dispatcher to process SNMP requests | for MIB variables. | | _get functions handle Get, GetNext, and GetBulk operations. | _set functions handle Set, Commit, Undo, and Cleanup. | | chess_get - The general chess group. | | gameEntry_get/set - The game table. | | moveEntry_get/set - The move table. | *******************************************************************/ /******************************************************************* | SNMP Versions: | | The eSNMP method routine API is based on SNMPv2. All SNMPv2 | datatypes, protocol data units, and error codes are supported | in the API. | | The eSNMP error code definitions reflect architected SNMPv2 error | codes. (The actual values are not identical, the master agent | maps appropriately). | | The eSNMP master-agent is bilingual, meaning it accepts either | v1 or v2 requests. It maps returned v2 error codes into the | appropriate v1 code. On a v1 request, the libesnmp code | running in the sub-agents will ignore object table entries | whose datatype is only supported in SNMPv2 (64 bit counters, for instance). | *******************************************************************/ /******************************************************************* | | GET, GETNEXT, GETBULK: | | The "Get" method routine handles these three action codes. | The function should either | | o update the VARBIND's oid and data fields, and return noError | | o return the correct SNMP error code | | On a GetRequest (ESNMP_ACT_GET), the possible error codes are: | | noSuchObject - libesnmp returns this when the requested variable binding | does not match an entry in the object table. | | noSuchInstance - method routine returns this when the requested instance is | not available or does not exist. | | genErr - method routine returns this when a general processing | error occurs (e.g., out of memory) | | On a GetNextRequest (ESNMP_ACT_GETNEXT) or GetBulkRequest (ESNMP_ACT_GETBULK) | the possible error codes are: | | noSuchInstance - method routine returns this when the lexicographical successor to | the requested instance is not available or does not exist. | | When this happens libesnmp calls method routines for subsequent objects in the | table. If the table is exhausted libesnmp will return the v2 exception | endOfMibView. | | genErr - method routine returns this when a general processing | error occurs (e.g., out of memory) | *******************************************************************/ #ifdef NULL_TEST /******************************************************************* | V2.0-01 | o_null() | | Test routine | Returns null type for any variable. May result in non-RFC-compliant | behavior. | *******************************************************************/ int o_null(VARBIND *vb) { if (!vb) { ESNMP_LOG( WARNING, ( "o_null; Null varbind\n")); return ESNMP_MTHD_genErr; } else { vb->type = ESNMP_TYPE_NULL; return ESNMP_MTHD_noError; } } #endif /******************************************************************* | | The GET method routine for the (general) chess group. | | Since this group has only 3 simple scalar variables, | we've decided not to use the chess_type data structure. | We just use the static (or constant) data as is. | | On a GET, the only valid instance is '0'. | | On GETNEXT/GETBULK, there is no valid instance, so we can only | return successfully if instance length == 0. (If the requested | object is Foo, and all we have is Foo.0, a GETNEXT for Foo.anything | must be failed.) | | Note the assumption here that the libesnmp code 'promotes' a | requested object to the subtree's first object, if the requested | object lexi-preceeded it. For instance, if this MIB is the | lexi first one in the master's registry, and this request arrives: | | GetNext{ 0.0 } | | we will be called here with incoming == chessProductID, not 0.0. | *******************************************************************/ int chess_get( METHOD *method ) { VARBIND *vb = method->varbind; OID *incoming = &method->varbind->name; OBJECT *object = method->object; int arg = object->object_index; int instlen; unsigned int inst; /* * Step 1, validate the requested object instance. */ instlen = oid2instance(incoming, object, &inst, 1); if (method->action == ESNMP_ACT_GET) { if ((instlen != 1) || (inst)) return(ESNMP_MTHD_noSuchInstance); } else { if (instlen) return(ESNMP_MTHD_noSuchInstance); } /* * Step 2, copy the correct instance back into the method's * variable binding's name. * We do this now because we know we can return data, and * we know the only instance we can return is '0'. */ inst = 0; instance2oid(&vb->name, object, &inst, 1); /* * Step 3, find the data (which variable in the chess group * is arg indicating?) * * Then load the proper data into the method's varbind. * This is done by calling the libesnmp o_xxx routine corresponding * to the raw data we have. */ switch(arg) { case I_chessProductID: return(o_oid(vb, object, &prod_oid)); case I_chessMaxGames: #ifdef NULL_TEST return o_null(vb); /* V2.0-01 */ #else return(o_integer(vb, object, MAX_GAMES)); #endif case I_chessNumGames: return(o_integer(vb, object, num_games)); default: return(ESNMP_MTHD_noSuchObject); } } /******************************************************************* | | The GET method routine for the gameEntry group. | | gameEntries are indexed by a single integer, gameIndex. | So for GET, a valid instance must be of length 1, and exactly | match the index of a game in the game_tbl. | | For GETNEXT/BULK, the instance can be longer than 1, but we | return the game whose index is larger than the first instance | integer. | For example, if the request is GetNext{ gameDescr.2 }, or | GetNext{ gameDescr.2.3.4 }, in both cases we should return | gameDescr for the game with the smallest value of gameIndex that | is larger than 2. | | For NEXT/BULK, the instance can be null, in which case we search | for the gameEntry with the smallest index > 0 (since SNMP table | indexes start @ 1). | | For this group we keep a static structure around (that was defined | by the MIB compiler). This 'cached' data is loaded whenever a request | arrives for something different than what's currently cached. | If we have a cache hit, we load the method->varbind directly from | the cache. | | (Since our data is just hanging around in local memory, this really | doesn't buy us much. But other sub-agents might find this a very | useful optimization.) | | NOTE that when you do data caching in a method routine, you need to | check that BOTH serial number AND instance match. | | method->serial_num is unique during the processing of any request. | That is, new requests always cause serial_num to increment. But even | within the same SNMP request, 2 objects in your MIB could have been | requested with different instance information, for example | | GetRequest{ gameNumMoves.1, gameDescr.2 } | | so you need to check instance information as well as serial_num. | | This type of caching is useful whenever your sub-agent fetches 'rows' | of data at a time. | *******************************************************************/ int gameEntry_get( METHOD *method ) { VARBIND *vb = method->varbind; OID *incoming = &method->varbind->name; OBJECT *object = method->object; int arg = object->object_index; Game *g; int instlen; unsigned int inst; static gameEntry_type cache_entry = {0, {0, NULL}, 0, 0}; static int cache_serial_num = 0; /* * Step 1, validate/set up the instance. */ instlen = oid2instance(incoming, object, &inst, 1); if (method->action == ESNMP_ACT_GET) { if (instlen != 1) return(ESNMP_MTHD_noSuchInstance); } else if (instlen <= 0) inst = 0; /* * Step 2, find the correct gameEntry. */ g = find_game(inst, method->action); if (!g) return(ESNMP_MTHD_noSuchInstance); /* * Can we use the cached gameEntry_type structure? * If not, reload the cache. */ if ((cache_serial_num != method->serial_num) || (cache_entry.gameIndex != g->index)) { cache_serial_num = method->serial_num; load_game(&cache_entry, g); } /* * Step 3, load the instance back into the method's varbind. */ inst = cache_entry.gameIndex; instance2oid(&vb->name, object, &inst, 1); /* * Step 4, load the data into the method's varbind, * and return the correct status value. */ switch(arg) { case I_gameIndex: return(o_integer(vb, object, cache_entry.gameIndex)); case I_gameNumMoves: #ifdef NULL_TEST return o_null(vb); /* V2.0-01 */ #else return(o_integer(vb, object, cache_entry.gameNumMoves)); #endif case I_gameStatus: return(o_integer(vb, object, cache_entry.gameStatus)); case I_gameDescr: return(o_octet(vb, object, &cache_entry.gameDescr)); default: return(ESNMP_MTHD_noSuchObject); } } /*********************************************************************** | | The GET method routine for the moveEntry group. | | This also uses a cached data structure emitted by the MIB compiler. | | The semantics for accessing this table (which is indexed by 2 integers) | is implemented in the function find_move(). | | Note how gameIndex is included in the moveEntry_type data structure. | This is so that all information required by the method routine is in | 1 place. gameIndex is not a variable in the moveEntry group, so it's | value is not loaded back into the method->varbind using an o_xxx routine. | But gameIndex IS used to index the table, so it's value is used in | the inst[] array, and gets loaded back into method->varbind->name. | | When we validate the instance, we now need to check for a length of 2 | for GET. NEXT/BULK may once again permit an instance of any length. | If we don't get enough information in the passed instance OID we | make up 0's. | ***********************************************************************/ int moveEntry_get( METHOD *method ) { VARBIND *vb = method->varbind; OID *incoming = &method->varbind->name; OBJECT *object = method->object; int arg = object->object_index; int instlen, i; unsigned int inst[2]; Move *m; Game *g; static moveEntry_type cache_entry = { 0, {0,NULL}, {0,NULL} }; static int cache_serial_num = 0; /* * Step 1, do initial validation/set up of the instance. */ instlen = oid2instance(incoming, object, inst, 2); if (method->action == ESNMP_ACT_GET) { if (instlen != 2) return(ESNMP_MTHD_noSuchInstance); } else { if (instlen < 2) inst[1] = 0; if (instlen < 1) inst[0] = 0; } /* * Step 2, find the correct moveEntry. */ m = find_move(inst[0], inst[1], method->action, &g); if (!m) return(ESNMP_MTHD_noSuchInstance); /* * Can we use the cached moveEntry_type structure? * If not, reload the cache. */ if ((cache_serial_num != method->serial_num) || (cache_entry.gameIndex != g->index) || (cache_entry.moveIndex != m->index)) { cache_serial_num = method->serial_num; load_move(&cache_entry, m, g); } /* * Step 3, load the instance back into the method's varbind. */ inst[0] = cache_entry.gameIndex; inst[1] = cache_entry.moveIndex; instance2oid(&vb->name, object, inst, 2); /* * Step 4, if the variable we're returning is moveByWhite/Black, * check if it's a good move. In standard chess notation, a good * move is usually indicated by appending 1 or more exclamation points. * (For instance, Frank Marshall once played 23 ... Q-KN6!!!.) * So we simply scan for a '!' character. * * If we determine this is a good move, generate a goodMove trap. * * Step 5, load the data back into the method->varbind and * return the correct status. */ switch(arg) { case I_moveIndex: return(o_integer(vb, object, cache_entry.moveIndex)); case I_moveByWhite: if (strstr(cache_entry.moveByWhite.ptr, "!")) gen_trap(object, cache_entry.moveByWhite.ptr, &ot[I_gameDescr], g->descr); return(o_octet(vb, object, &cache_entry.moveByWhite)); case I_moveByBlack: if (strstr(cache_entry.moveByBlack.ptr, "!")) gen_trap(object, cache_entry.moveByBlack.ptr, &ot[I_gameDescr], g->descr); return(o_octet(vb, object, &cache_entry.moveByBlack)); case I_moveStatus: return(o_integer(vb, object, D_moveStatus_ok)); default: return(ESNMP_MTHD_noSuchObject); } } /*********************************************************************** | | Setting SNMP variables: | | SNMP setRequest PDUs may contain one or more variable bindings (varBinds). | | Each varBind consists of a MIB variable and an associated, or bound, | value. The list of varBinds contained in a single setRequest PDU may, | or may not, belong to the same MIB group, like the gameTable or the | moveTable of the chess MIB. When more than one varBind in a | setRequest PDU belongs to the same MIB group, each varBind may, or | may not, refer to the same instance. The instance of a MIB variable is | determined by the value of the OID(s) appended to the MIB name. In the | example of the gameTable, or the moveTable, the instance consists of | the value(s), comprising the row index, appended to the MIB name. | Therefore, varBinds in the same row of a table refer to the same instance. | MIB variables that are not part of a table have an instance of '.0'. | | For example: | | setRequest{sysDescr.0 = "foo", ifAdminStatus.1 = up, ifAdminStatus.2 = down} | | It is typical for setRequests to specify MIB variables that belong to | the same MIB group, sharing the same instance OIDs. For example: | | setRequest{ moveByWhite.4.5 = "5. Bg2", moveByBlack.4.5 = "5 ...c6" } | | In such cases it is often necessary to determine if the varBinds | "make sense together". | | The SNMPv2 protocol specifies that set operations must be implemented in | an 'atomic fashion'. This means, that either all varBinds in the | setRequest must successfully be set, or, if one (or more) varBind(s) | can not be successfully set, then, no varBind in the setRequest | shall be set. | | It is for this reason that eSNMP subagent methods need to handle 'set', | 'commit' and 'undo' processing. In the 'set' processing each varBind is | validated. In the 'commit' processing, each varBind value is placed into | the subagent's instrumented object for the corresponding MIB variable. | | Sometimes, an error is detected during 'commit' processing. In | distributed (extensible) SNMP agents, 'commit' processing errors can | arise when multiple sub-agents are involved in handling a given | setRequest. When a 'commit' processing error occurs, the set operation | is considered unsuccessful. At this point, the eSNMP master agent | directs each sub-agent involved in the handling of the setRequest | to undertake 'undo' processing. In the 'undo' processing, each of | the subagent's instrumented objects touched during 'commit' processing | is rolled back to the value it contained prior to the 'commit'. | | | For these reasons eSNMP subagent method routines handling SNMP setRequests | need to perform processing for the following directives: | | ESNMP_ACT_SET - Test the validity of the varBind (does the subagent | support setting this object? Does the instance make | sense? Does the value make sense? Are all required | varBinds present?) | | ESNMP_ACT_COMMIT - All the varBinds have passed the validity test of the | 'set' directive. Copy the varBind value into the | subagent's instrumented object for this MIB variable. | | ESNMP_ACT_UNDO - An error was reported during the 'commit' phase. | Restore the value of the affected instrumented | objects in the subagent. | | ESNMP_ACT_CLEANUP - Notification to the subagent that this setRequest is | complete. The subagent can now release any resources | allocated while processing this request. | | Overall processing is as follows: | | Each varBind is parsed and its corresponding entry in the object table | is located. A METHOD structure is allocated for each varBind. A METHOD | structure contains information that remains persistent between calls to | a method routine. One member of the METHOD structure points to a | ROW_CONTEXT structure. The method routine stores information within | the ROW_CONTEXT and METHOD structures that is useful in handling the | processing of the 'set', 'commit', 'undo' and 'cleanup' directives. | | MIB variables sharing the same instance, i.e. in the same conceptual row, | all point to the same ROW_CONTEXT structure. | | Each ROW_CONTEXT is loaded with the instance information for that | conceptual row. The ROW_CONTEXT members, context and save, are set | to NULL, and the state field is set to ESNMP_SET_UNKNOWN. | | For each varBind, a method routine is called. The method routine | is passed the METHOD structure for the varBind and a directive, or | action code, of ESNMP_ACT_SET. | | If all method routines return ESNMP_MTHD_noErr, a SINGLE method routine | (the last one called for the row) is called for each row, with | method->action == ESNMP_ACT_COMMIT. | | If any row reports failure, all rows that have been successfully | committe dare given an 'undo' directive. This is accomplished by | calling a single method routine for each row (the same one that was | called for 'commit'), with method->action == ESNMP_ACT_UNDO. | | Finally, each row is directed to 'cleanup'. The same single method | routine for each row is called with method->action == ESNMP_ACT_CLEANUP. | This occurs for every row, regardless of the results of previous processing. | | Each of the four processing directives is now discussed in greater detail | | ------------------------------------------------------------------------- | | ESNMP_ACT_SET: | | Each varBind's method routine is called during the SET phase, until | either all varBinds have been processed, or a method routine | returns an error status value. (This is the only phase during which | a method routine is called for each varBind.) For varBinds in | the same conceptual row, method->row points to a common ROW_CONTEXT. | | The method->flags bitmask will have the ESNMP_LAST_IN_ROW bit present if | this is the last varBind being called for this ROW_CONTEXT. This enables | the subagent to perform any final consistency checks since all related | varBinds for this conceptual row are now available for cross validation. | | The method routine's job, during 'set' processing, is to determine | if the setRequest will work, return the correct SNMP error code if | not, and prepare any context data it will need to update the | corresponding instrumented object when processing a subsequent | 'commit' directive. | | The SNMP protocol defines a set of error codes, intended to provide | as much information as possible to the application making the | setRequest. Also, the SNMP protocol implies the order of validity | checking for possible error conditions. The following ordered check | list, extracted from RFC1905, describes the error codes and conditions. | | NOTE: libesnmp and the master-agent ultimately determine | the proper error-status and error-index to use when generating | the response PDU. Specific processing required of the eSNMP | subagent method routine is called out after each corresponding | item of the check list. | | [1] If the variable binding's name specifies an existing or non- | existent variable to which this request is/would be denied access, | then the value of the Response-PDU's error-status field is set to | `noAccess', and the value of its error-index field is set to the | index of the failed variable binding. | | The master-agent detects this condition without disturbing the sub-agent. | Thus, the method routine need not detect this condition. | | [2] Otherwise, if there are no variables which share the same OBJECT | IDENTIFIER prefix as the variable binding's name, and which are | able to be created or modified no matter what new value is | specified, then the value of the Response-PDU's error-status field | is set to `notWritable', and the value of its error-index field | is set to the index of the failed variable binding. | | libesnmp detects this if no matching table entry exists, or if one exists | but has no Set method routine pointer (refer to the exclusion file). | Method routines may also detect and indicate this error, by returning a | value of ESNMP_MTHD_notWritable, when the implementation does not | support setting the variable. (also see item [9] with respect to | ESNMP_MTHD_notWritable.) | | [3] Otherwise, if the variable binding's value field specifies, | according to the ASN.1 language, a type which is inconsistent with | that required for all variables which share the same OBJECT | IDENTIFIER prefix as the variable binding's name, | then the value of the Response-PDU's error-status field is set to | `wrongType', and the value of its error-index field is set to the | index of the failed variable binding. | | libesnmp detects this condition (by matching the variable binding's type | against that of the object table entry) without disturbing the method | routine. Thus, the method routine need not detect this condition. | | [4] Otherwise, if the variable binding's value field specifies, | according to the ASN.1 language, a length which is inconsistent | with that required for all variables which share the same OBJECT | IDENTIFIER prefix as the variable binding's name, | then the value of the Response-PDU's error-status field is set to | `wrongLength', and the value of its error-index field is set to the | index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_wrongLength. | | [5] Otherwise, if the variable binding's value field contains an ASN.1 | encoding which is inconsistent with that field's ASN.1 tag, then | the value of the Response-PDU's error-status field is set to | `wrongEncoding', and the value of its error-index field is set to | the index of the failed variable binding. (Note that not all | implementation strategies will generate this error.) | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_wrongEncoding. | | [6] Otherwise, if the variable binding's value field specifies a value | which could under no circumstances be assigned to the variable, | then the value of the Response-PDU's error-status field is set to | `wrongValue', and the value of its error-index field is set to the | index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_wrongValue. | | [7] Otherwise, if the variable binding's name specifies a variable | which does not exist and could not ever be created (even though | some variables sharing the same OBJECT IDENTIFIER prefix might | under some circumstances be able to be created), then the value of | the Response-PDU's error-status field is set to `noCreation', and | the value of its error-index field is set to the index of the | failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_noCreation. | | [8] Otherwise, if the variable binding's name specifies a variable | which does not exist but can not be created under the present | circumstances (even though it could be created under other | circumstances), then the value of the Response-PDU's error-status | field is set to `inconsistentName', and the value of its error- | index field is set to the index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_inconsistentName. | | [9] Otherwise, if the variable binding's name specifies a variable | which exists but can not be modified no matter what new value is | specified, then the value of the Response-PDU's error-status field | is set to `notWritable', and the value of its error-index field is | set to the index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_notWritable. An example of this | condition is how the gameNumMoves variable is handled in the | gameEntry_set method routine. | | | [10] Otherwise, if the variable binding's value field specifies a value | that could under other circumstances be assigned to the variable, | but is presently inconsistent, then the value of the Response-PDU's | error-status field is set to `inconsistentValue', and the value of | its error-index field is set to the index of the failed variable | binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_inconsistentValue. | | [11] When, during the above steps, the assignment of the value specified | by the variable binding's value field to the specified variable requires | the allocation of a resource which is presently unavailable, then | the value of the Response-PDU's error-status field is set to | `resourceUnavailable', and the value of its error-index field is | set to the index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_resourceUnavailable. | | [12] If the processing of the variable binding fails for a reason other | than listed above, then the value of the Response-PDU's error- | status field is set to `genErr', and the value of its error-index | field is set to the index of the failed variable binding. | | The method routine must detect and indicate this error condition by | returning a value of ESNMP_MTHD_genErr. | | [13] Otherwise, the validation of the variable binding succeeds. | | At the end of the first phase, if the validation of all variable | bindings succeeded, then the value of the Response-PDU's error-status | field is set to `noError' and the value of its error-index field is | zero, and processing continues onto the next phase. | | At this point, the validity checking of the 'set' directive is complete. | Use the method->row->context field to hold any data the subagent will | require when processing a 'commit' directive. (A typical use would be | to store the address of one of the emitted _type structures, and | load that structure's fields with the values in method->vb->value>.) | | The row->state field is a bitmask indicating the processing state. | | The row->state and row->context fields are private to the method routine, | libesnmp ignores them. | | The method routine indicates the successful completion of the first phase, | ESNMP_ACT_SET, by returning a value of ESNMP_MTHD_noErr. | | | ESNMP_ACT_COMMIT: | | Even though several varBinds may be in a conceptual row, only | the last one (in order of the setRequest) is processed. So for all the | METHOD's that point to a common row, only the method routine for the last | varBind is called. | | This method routine must have available to it all necessary data and | context to perform the operation. It must also save a snapshot of | current data or whatever it needs to undo the set if directed to do so. | (row->save is intended to hold a pointer to whatever data is needed | to accomplish this. A typical use is to store the address of an | emitted _type structure that has been loaded with the current | data for the conceptual row.) | | row->save is also private to the method routine, libesnmp does not use it. | | If the set operation succeeds, return ESNMP_MTHD_noError. Otherwise | return a value of ESNMP_MTHD_commitFailed. | | If any errors were returned during the 'commit' processing, the subagent | is directed to perform 'undo' processing. If no errors were reported, | then the subagent is directed to perform 'cleanup' processing. | | NOTE: An 'undo' directive may be received even if the 'commit' | processing in the subagent method was successful. This | may occur if the setRequest contained varBinds spanning | subagent methods, and, some other subagent method returned | an error from 'commit' processing. | | ESNMP_ACT_UNDO: | | For each conceptual row that was successfully comitted, the same method | routine is called with method->action == ESNMP_ACT_UNDO. (ROW_CONTEXTs | that have not yet been directed to 'commit' are not directed to 'undo', | they are, instead, directed to 'cleanup'.) | | The subagent method routine should attempt to restore conditions to | what they were before it processing the 'commit' directive. (This | is typically done using the data pointed to by row->save.) | | If successful, return ESNMP_MTHD_noError, otherwise return | ESNMP_MTHD_undoFail. | | | ESNMP_ACT_CLEANUP: | | Regardless of what else has happened up to this point, one method routine | is called for each set of Method structures sharing the same ROW_CONTEXT. | The method routine called is the same one that was called with a | directive of 'set' and the ESNMP_LAST_IN_ROW bit present in the | method->flags bitmask. The subagent method routine is now directed to | perform 'cleanup' processing with method->action == ESNMP_ACT_CLEANUP. | | This directive indicates the completion of processing for the setRequest. | The subagent method routine should perform whatever cleanup is required | (for instance, freeing dynamically allocated memory that may have been | stored in row->context, etc.) | | Status values returned by the subagent method after 'cleanup' | processing are ignored by the master agent and libesnmp. | ***********************************************************************/ /*********************************************************************** | | The SET method routine for the gameEntry group. | | Our rules for setting games are: | Creation: | New games can be created. | gameDescr must be present and non-null. | gameIndex may be present, if so, its value must match | the instance in the ROW_CONTEXT. | (We would not accept SetRequest{GameIndex.5 = 6} | gameStatus may be present, if so must be = Underway | | Deletion: | Only gameStatus may be present, must = Delete | | Modification: | GameStatus may be present, if so must not = Delete | gameDescr may be present, may be any non-null value | | We use the emitted gameEntry_type structure to hold | data for set context (row->context) and to hold any "before" | data (row->save) if we're deleting or modifying. | ***********************************************************************/ int gameEntry_set( METHOD *method ) { OBJECT *object = method->object; VARBIND *vb = method->varbind; ROW_CONTEXT *row = method->row; int arg = object->object_index; unsigned int *inst = row->instance; int instlen = row->instance_len; gameEntry_type *cur = (gameEntry_type *)row->context; gameEntry_type *prev = (gameEntry_type *)row->save; static Game *g; switch (method->action) { case ESNMP_ACT_SET: /* * First check for non-writeable MIB objects. * In our example we do not support setting the value * of gameNumMoves (even tho the MIB spec defines it * with ACCESS read-write). This is an implementation-specific * variation that is "legal" SNMP. * * We want to return notWriteable before any other error, so check here. * NOTE: An alternative to performing this check in our code is to * use the -e option with snmpi. This tells snmpi to consult a * a file defining which MIB variables we exclude Get or Set * processing for. To turn off Set processing for gameNumMoves we * would have an entry in the exclusion file of the form * * gameNumMoves noSET */ if (arg == I_gameNumMoves) return(ESNMP_MTHD_notWritable); /* * If this is the first time this method routine has been called. * 1. Make a gameEntry_type and save it in row->context. * 2. Validate the instance, and determine if a game with * this index exists. If so, save the Game pointer in the * static variable g. (This SetRequest will be processed * to completion before any other SNMP requests are processed, so * we won't stomp this static data.) * We'll load it's data later. * 3. Save the row->instance in the context structure. */ if (!cur) { if (instlen != 1) return(ESNMP_MTHD_noCreation); cur = gameEntry_new(); if (!cur) return(ESNMP_MTHD_resourceUnavailable); row->context = cur; cur->gameIndex = *inst; g = find_game(*inst, ESNMP_ACT_GET); } /* * This section validates the value of each variable binding * in the SetRequest for this conceptual row. * * This is the only chance we get to look at this VARBIND, so * we need to leave enough information in the ROW_CONTEXT so * that later on we can figure out if we've been given a valid * request. * * If g != NULL, a game entry exists which matches * the instance info in this ROW_CONTEXT. So if g is null * we can't modify or delete, and if it's non-null we can't create. * The value of g is used to help validate values for variable * bindings that can be present for both create and modify operations. * * NOTE: * How a method routine validates variable bindings and checks for consistency * within SetRequest processing IS ENTIRELY PRIVATE. * Libesnmp does not use the row->state, row->context, or row->save fields. * * This example program uses the supplied support code to do * row and field state handling. This means: * * o the emitted gameEntry_type structure is used to store data about * the current SetRequest, and is pointed to by row->context. * o the mark[] array in this structure is used to store the * state of each variable binding we see. * o these states are referenced via the macros & defined values * in esnmp.h (MARK_CREATE, REQ_FOR_CREATE, etc.) * */ switch(arg) { case I_gameIndex: /* * May only be present on a row creation. * It's value must be = the row's instance. * (SetRequest{gameIndex.3 = 4} makes no sense). * We've already saved its value in cur->gameIndex. */ if (row->state & ~ESNMP_SET_CREATE) return(ESNMP_MTHD_inconsistentValue); if (g) return(ESNMP_MTHD_inconsistentValue); if (cur->gameIndex != vb->value.sl) return(ESNMP_MTHD_inconsistentValue); MARK_CREATE(&cur->gameIndex_mark); row->state = ESNMP_SET_CREATE; break; case I_gameDescr: /* * Must be present on creates, optional for modify, * not allowed on delete. If present must be non-null. * Can never be larger than DESCR_SIZE. * * Copy the vb data into cur->gameDescr. */ if ((vb->value.oct.len == 0) || (vb->value.oct.len >= DESCR_SIZE)) return(ESNMP_MTHD_wrongLength); if (row->state == ESNMP_SET_DELETE) return(ESNMP_MTHD_inconsistentName); if (clone_oct(&cur->gameDescr, &vb->value.oct) == NULL) return(ESNMP_MTHD_resourceUnavailable); MARK_CREATE(&cur->gameDescr_mark); MARK_MODIFY(&cur->gameDescr_mark); if (!row->state) row->state = (g)? ESNMP_SET_MODIFY : ESNMP_SET_CREATE; break; case I_gameStatus: /* * On Create, must be Underway. * On Modify, must be Underway or Complete. * On Delete, must be the only variable binding. */ switch(vb->value.sl) { case D_gameStatus_delete: if (row->state) /* We were not first. */ return(ESNMP_MTHD_inconsistentValue); cur->gameStatus = D_gameStatus_delete; MARK_DELETE(&cur->gameStatus_mark); row->state = ESNMP_SET_DELETE; break; case D_gameStatus_underway: if (row->state & ESNMP_SET_DELETE) return(ESNMP_MTHD_inconsistentValue); cur->gameStatus = D_gameStatus_underway; MARK_CREATE(&cur->gameStatus_mark); MARK_MODIFY(&cur->gameStatus_mark); row->state = (g)? ESNMP_SET_MODIFY : ESNMP_SET_CREATE; break; case D_gameStatus_complete: if (row->state & ~ESNMP_SET_MODIFY) return(ESNMP_MTHD_inconsistentValue); cur->gameStatus = D_gameStatus_complete; MARK_MODIFY(&cur->gameStatus_mark); row->state = ESNMP_SET_MODIFY; break; default: return(ESNMP_MTHD_wrongValue); } break; default: return(ESNMP_MTHD_genErr); } /* arg */ /* * Now that this variable binding has passed validation, * check if we're the last one for this row. If so, * perform the final consistency check. * * It's important to do this now, and not at the beginning * of the COMMIT phase, since errors discovered then would * cause all variable bindings to go thru COMMIT & UNDO needlessly. * * Additionally, for Create, check that we can in fact create * a new game (actually that we have an avialable slot in the * game_tbl). Clear this pointer since it's used in the subsequent * phases to mean an existing instance exists, which it does not! */ if (method->flags & ESNMP_LAST_IN_ROW) { switch(row->state) { case ESNMP_SET_CREATE: if ((REQ_FOR_CREATE(&cur->gameDescr_mark) && OPT_FOR_CREATE(&cur->gameIndex_mark) && OPT_FOR_CREATE(&cur->gameStatus_mark)) && (!g)) { g = find_game_slot(); if (g) { g = NULL; return(ESNMP_MTHD_noError); } else return(ESNMP_MTHD_resourceUnavailable); /* no move slots! */ } break; case ESNMP_SET_MODIFY: if ((REQ_FOR_MODIFY(&cur->gameDescr_mark) || REQ_FOR_MODIFY(&cur->gameStatus_mark)) && (g)) return(ESNMP_MTHD_noError); break; case ESNMP_SET_DELETE: if (g) return(ESNMP_MTHD_noError); break; } return(ESNMP_MTHD_inconsistentValue); } else return(ESNMP_MTHD_noError); break; /* ESNMP_ACT_SET */ case ESNMP_ACT_COMMIT: /* * All that SET phase processing worked, and we've been * called to COMMIT. * * 1. If an instance of this row exists, load its data now. * This is in case we need to undo whatever we're doing. * Saving this "undo" data should be done as close as possible * to when the COMMIT occurs, if the data in the existing row * instance is subject to change. (In our case it's not, but * we delayed loading the Game data until this point anyway.) * (In the case of creating a new game, the gameEntry_type in * row->context is sufficient to undo.) * * 2. Execute the COMMIT. Return either noError or commitFailed. * * Note that for a Delete, we can't actually dissolve this Game's * association with the move tbl, otherwise we'd never be able to * UNDO if required. So we leave the Moves and the Game.moves ptr * intact for now, and set the Game's sts to the special value -1. * If it's still == -1 in CLEANUP phase, we'll reclaim the Moves then. */ if (g) { prev = gameEntry_new(); if (prev) { load_game(prev, g); row->save = (void *)prev; } else return(ESNMP_MTHD_resourceUnavailable); } switch(row->state) { case ESNMP_SET_CREATE: g = find_game_slot(); if (!g) return(ESNMP_MTHD_commitFailed); g->index = cur->gameIndex; g->nummoves = 0; g->moves = NULL; g->lastmove = NULL; g->sts = D_gameStatus_underway; strncpy(g->descr, cur->gameDescr.ptr, cur->gameDescr.len); g->descr[cur->gameDescr.len] = '\0'; num_games++; return(ESNMP_MTHD_noError); break; case ESNMP_SET_MODIFY: if (REQ_FOR_MODIFY(&cur->gameDescr_mark)) { strncpy(g->descr, cur->gameDescr.ptr, cur->gameDescr.len); g->descr[cur->gameDescr.len] = '\0'; } if (REQ_FOR_MODIFY(&cur->gameStatus_mark)) g->sts = cur->gameStatus; return(ESNMP_MTHD_noError); break; case ESNMP_SET_DELETE: g->sts = -1; num_games--; return(ESNMP_MTHD_noError); default: return(ESNMP_MTHD_inconsistentName); } /* row->state */ break; /* ACT_COMMIT */ case ESNMP_ACT_UNDO: /* * A COMMIT failed somewhere after we had successfully completed our COMMIT. * Undo it. The static Game ptr g still points to the * game tbl entry we operated on. The gameEntry_type pointers * at this point are: * * cur = row->context contains the data we loaded when * validating the original request. * * prev = row->save contains the data that we loaded from * the game tbl entry BEFORE doing the set operation * (if one existed). * * So to UNDO, * * if we Created, just mark Game.sts as invalid. This frees * up the game tbl entry. There is no memory to free, and * there are no associated moves since games are created with * 0 moves. * * if we Modified, just copy the prev data back to the Game. * * if we Deleted, all we'd done was set sts = -1. Just copy * back the old status. */ switch(row->state) { case ESNMP_SET_CREATE: g->sts = 0; num_games--; break; case ESNMP_SET_MODIFY: strncpy(g->descr, prev->gameDescr.ptr, prev->gameDescr.len); g->descr[prev->gameDescr.len] = '\0'; g->sts = prev->gameStatus; break; case ESNMP_SET_DELETE: g->sts = prev->gameStatus; num_games++; break; } return(ESNMP_MTHD_noError); break; /* ACT_UNDO */ case ESNMP_ACT_CLEANUP: /* * Our main purpose here is to free up the data we created * for context and undo-ing. We use the routines that were * generated by snmpi for this. * * NOTE: These routines free variable length data pointed to * within the structure, as well as the structure itself. * (In this example, it frees gameDescr.ptr.) So be aware * of this when using the emitted structures and free routines. * * Since we malloced space for cur->gameDescr (via clone_oct), we'll * use the generated routine for that gameEntry_type. But we simply * re-used the g->descr pointer in prev->gameDescr (via load_game). * Freeing that pointer would corrupt the game tbl entry, so we * just free manually (we know there is no other dynamic memory * that needs reclaiming.) * * If the operation was a Delete, and we didn't UNDO, we * still have to free up the move tbl entries that were associated * with this game, and give it a real sts value. * * Finally, clear the static Game ptr so we're not confused next time. */ if (cur) gameEntry_free(cur); if (prev) free(prev); if (g) { if (g->sts == -1) { reclaim_move_slots(g->moves); g->sts = 0; } g = NULL; } return(ESNMP_MTHD_noError); break; /* ACT_CLEANUP */ default: /* * Bugcheck, this means the METHOD has been corrupted. */ abort(); } /* method->action */ } /* gameEntry_set */ /*********************************************************************** | | This is the SET method routine for the moveEntry group. | | Our rules for setting moves are: | | Creation: | new moveEntry rows may be created | the instance must be 1 greater than the current gameNumMoves | for the specified game (if it has 22 moves, you can create move 23) | moveByWhite must be present | moveByBlack may be present | both must be non-null | | creating a move causes gameNumMoves to be incremented | | Modification: | moveByWhite or moveByBlack may be modified | to non-null values | | Deletion | moveEntry rows may be deleted | only the last move in a game may be deleted | | deleting a move causes gameNumMoves to be decremented | | Since this table is indexed by [gameIndex, moveIndex] we must | receive 2 integers in the row->instance array. The first must match | an existing game. the second must either match an existing move | in that game, or be 1 larger than the number of moves in that | game (for a Create). | ***********************************************************************/ int moveEntry_set( METHOD *method ) { OBJECT *object = method->object; VARBIND *vb = method->varbind; ROW_CONTEXT *row = method->row; int arg = object->object_index; unsigned int *inst = row->instance; int instlen = row->instance_len; moveEntry_type *cur = (moveEntry_type *)row->context; moveEntry_type *prev = (moveEntry_type *)row->save; static Game *g; static Move *m; switch (method->action) { case ESNMP_ACT_SET: if (!cur) { if (instlen != 2) return(ESNMP_MTHD_noCreation); m = find_move(inst[0], inst[1], ESNMP_ACT_GET, &g); if (!m) { g = find_game(inst[0], ESNMP_ACT_GET); if (!g) return(ESNMP_MTHD_inconsistentName); if (g->nummoves + 1 != inst[1]) return(ESNMP_MTHD_inconsistentName); } /* * OK, if m exists it's a modify or delete. * If not, it's a create. The 2 indexes are initially valid. */ cur = moveEntry_new(); if (!cur) return(ESNMP_MTHD_resourceUnavailable); row->context = cur; cur->gameIndex = inst[0]; cur->moveIndex = inst[1]; } switch(arg) { case I_moveIndex: /* * May only be present on a row creation. * It's value must be = the row's instance. * (SetRequest{moveIndex.3 = 4} makes no sense). * We've already saved its value in cur->moveIndex. */ if (row->state & ~ESNMP_SET_CREATE) return(ESNMP_MTHD_inconsistentValue); if (m) return(ESNMP_MTHD_inconsistentValue); if (cur->moveIndex != vb->value.sl) return(ESNMP_MTHD_inconsistentValue); MARK_CREATE(&cur->moveIndex_mark); row->state = ESNMP_SET_CREATE; break; case I_moveByWhite: /* * Must be present on creates, optional for modify, * not allowed on delete. If present must be non-null. * Can never be larger than MOVE_SIZE. * * Copy the vb data into cur->moveDescr. */ if ((vb->value.oct.len == 0) || (vb->value.oct.len >= MOVE_SIZE)) return(ESNMP_MTHD_wrongLength); if (row->state == ESNMP_SET_DELETE) return(ESNMP_MTHD_inconsistentValue); if (clone_oct(&cur->moveByWhite, &vb->value.oct) == NULL) return(ESNMP_MTHD_resourceUnavailable); MARK_CREATE(&cur->moveByWhite_mark); MARK_MODIFY(&cur->moveByWhite_mark); if (!row->state) row->state = (m)? ESNMP_SET_MODIFY : ESNMP_SET_CREATE; break; case I_moveByBlack: /* * May be present on creates and modify, * not allowed on delete. If present must be non-null. * Can never be larger than MOVE_SIZE. * * Copy the vb data into cur->moveDescr. */ if ((vb->value.oct.len == 0) || (vb->value.oct.len >= MOVE_SIZE)) return(ESNMP_MTHD_wrongLength); if (row->state == ESNMP_SET_DELETE) return(ESNMP_MTHD_inconsistentValue); if (clone_oct(&cur->moveByBlack, &vb->value.oct) == NULL) return(ESNMP_MTHD_resourceUnavailable); MARK_CREATE(&cur->moveByBlack_mark); MARK_MODIFY(&cur->moveByBlack_mark); if (!row->state) row->state = (m)? ESNMP_SET_MODIFY : ESNMP_SET_CREATE; break; case I_moveStatus: /* * On Create, must be OK. * On Delete, must be the only variable binding. * Not allowed on Modify. */ switch(vb->value.sl) { case D_moveStatus_delete: if (row->state) /* We were not first varbind */ return(ESNMP_MTHD_inconsistentValue); if (!m) return(ESNMP_MTHD_inconsistentValue); if (g->lastmove != m) /* Not the last move */ return(ESNMP_MTHD_inconsistentName); /* hmmmm... */ cur->moveStatus = D_moveStatus_delete; MARK_DELETE(&cur->moveStatus_mark); row->state = ESNMP_SET_DELETE; break; case D_moveStatus_ok: if (row->state & ~ESNMP_SET_CREATE) return(ESNMP_MTHD_inconsistentValue); if (m) return(ESNMP_MTHD_inconsistentValue); cur->moveStatus = D_moveStatus_delete; MARK_CREATE(&cur->moveStatus_mark); row->state = ESNMP_SET_CREATE; break; default: return(ESNMP_MTHD_wrongValue); } break; } /* arg */ if (method->flags & ESNMP_LAST_IN_ROW) { switch(row->state) { case ESNMP_SET_CREATE: if ((REQ_FOR_CREATE(&cur->moveByWhite_mark) && OPT_FOR_CREATE(&cur->moveByBlack_mark) && OPT_FOR_CREATE(&cur->moveIndex_mark) && OPT_FOR_CREATE(&cur->moveStatus_mark)) && (!m)) { m = find_move_slot(); if (m) { m = NULL; return(ESNMP_MTHD_noError); } else return(ESNMP_MTHD_resourceUnavailable); /* no move slot! */ } break; case ESNMP_SET_MODIFY: if ((REQ_FOR_MODIFY(&cur->moveByWhite_mark) || REQ_FOR_MODIFY(&cur->moveByBlack_mark)) && (m)) return(ESNMP_MTHD_noError); break; case ESNMP_SET_DELETE: if (m) return(ESNMP_MTHD_noError); break; } return(ESNMP_MTHD_inconsistentValue); } else return(ESNMP_MTHD_noError); break; /* ESNMP_ACT_SET */ case ESNMP_ACT_COMMIT: #ifdef UNDO_TEST printf("Start of commit in moveEntry_set...\n"); /* V5.1-00 */ #endif if (m) { prev = moveEntry_new(); if (prev) { load_move(prev, m, g); row->save = (void *)prev; } else return(ESNMP_MTHD_resourceUnavailable); } switch(row->state) { case ESNMP_SET_CREATE: m = find_move_slot(); if (!m) return(ESNMP_MTHD_commitFailed); strncpy(m->w, cur->moveByWhite.ptr, cur->moveByWhite.len); m->w[cur->moveByWhite.len] = '\0'; if (REQ_FOR_MODIFY(&cur->moveByBlack_mark)) { strncpy(m->b, cur->moveByBlack.ptr, cur->moveByBlack.len); m->b[cur->moveByBlack.len] = '\0'; } add_move(m, g); return(ESNMP_MTHD_noError); break; case ESNMP_SET_MODIFY: if (REQ_FOR_MODIFY(&cur->moveByWhite_mark)) { strncpy(m->w, cur->moveByWhite.ptr, cur->moveByWhite.len); m->w[cur->moveByWhite.len] = '\0'; } if (REQ_FOR_MODIFY(&cur->moveByBlack_mark)) { strncpy(m->b, cur->moveByBlack.ptr, cur->moveByBlack.len); m->b[cur->moveByBlack.len] = '\0'; } return(ESNMP_MTHD_noError); break; case ESNMP_SET_DELETE: kill_move(g); #ifdef UNDO_TEST printf("Commit completing with failure in moveEntry_set.\n"); /* V5.1-00 */ return(ESNMP_MTHD_commitFailed); #else return(ESNMP_MTHD_noError); #endif break; default: return(ESNMP_MTHD_inconsistentName); } /* row->state */ break; /* ACT_COMMIT */ case ESNMP_ACT_UNDO: #ifdef UNDO_TEST printf("Executing UNDO in moveEntry_set.\n"); /* V5.1-00 */ #endif switch(row->state) { case ESNMP_SET_CREATE: kill_move(g); break; case ESNMP_SET_MODIFY: if (REQ_FOR_MODIFY(&cur->moveByWhite_mark)) { strncpy(m->w, prev->moveByWhite.ptr, prev->moveByWhite.len); m->w[prev->moveByWhite.len] = '\0'; } if (REQ_FOR_MODIFY(&cur->moveByBlack_mark)) { strncpy(m->b, prev->moveByBlack.ptr, prev->moveByBlack.len); m->b[prev->moveByBlack.len] = '\0'; } return(ESNMP_MTHD_noError); break; case ESNMP_SET_DELETE: add_move(m, g); break; } return(ESNMP_MTHD_noError); break; /* ACT_UNDO */ case ESNMP_ACT_CLEANUP: if (cur) moveEntry_free(cur); if (prev) free(prev); m = NULL; g = NULL; return(ESNMP_MTHD_noError); break; /* ACT_CLEANUP */ default: /* * Bugcheck, this means the METHOD has been corrupted. */ abort(); } /* method->action */ } /* moveEntry_set */ #ifdef __VMS #pragma message restore #endif