1 /*-
2  * Copyright (c) 2006, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file EXAMPLES-LICENSE for license information.
5  *
6  * $Id$
7  */
8 
9 /*
10  * NOTE: This example is a simplified version of the rep_mgr.c
11  * example that can be found in the db/examples/c/ex_rep/mgr directory.
12  *
13  * This example is intended only as an aid in learning Replication Manager
14  * concepts.  It is not complete in that many features are not exercised
15  * in it, nor are many error conditions properly handled.
16  *
17  */
18 
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #ifdef _WIN32
23 #include <windows.h>
24 #define	sleep(s)		Sleep(1000 * (s))
25 #else /* !_WIN32 */
26 #include <unistd.h>
27 #endif
28 
29 #include <db.h>
30 
31 #ifdef _WIN32
32 extern int getopt(int, char * const *, const char *);
33 #endif
34 
35 #define	CACHESIZE (10 * 1024 * 1024)
36 #define	DATABASE "quote.db"
37 #define	SLEEPTIME 3
38 
39 typedef struct {
40     int is_master;
41 } APP_DATA;
42 
43 const char *progname = "ex_rep_gsg_repmgr";
44 
45 int create_env(const char *, DB_ENV **);
46 int env_init(DB_ENV *, const char *);
47 int doloop (DB_ENV *);
48 int print_stocks(DB *);
49 static void event_callback(DB_ENV *, u_int32_t, void *);
50 
51 /* Usage function */
52 static void
usage()53 usage()
54 {
55     fprintf(stderr, "usage: %s ", progname);
56     fprintf(stderr, "-h home -l|-L host:port\n");
57     fprintf(stderr, "\t\t[-r host:port][-p priority]\n");
58     fprintf(stderr, "where:\n");
59     fprintf(stderr, "\t-h identifies the environment home directory ");
60     fprintf(stderr, "(required).\n");
61     fprintf(stderr, "\t-l identifies the host and port used by this ");
62     fprintf(stderr, "site (required unless L is specified).\n");
63     fprintf(stderr, "\t-L identifies the local site as group creator. \n");
64     fprintf(stderr, "\t-r identifies another site participating in ");
65     fprintf(stderr, "this replication group\n");
66     fprintf(stderr, "\t-p identifies the election priority used by ");
67     fprintf(stderr, "this replica.\n");
68     exit(EXIT_FAILURE);
69 }
70 
71 int
main(int argc,char * argv[])72 main(int argc, char *argv[])
73 {
74     DB_ENV *dbenv;
75     DB_SITE *dbsite;
76     extern char *optarg;
77     const char *home;
78     char ch, *host, *last_colon, *portstr;
79     int local_is_set, ret, is_group_creator;
80     u_int16_t port;
81     /* Used to track whether this is a replica or a master. */
82     APP_DATA my_app_data;
83 
84     dbenv = NULL;
85     ret = local_is_set = is_group_creator = 0;
86     home = NULL;
87 
88     my_app_data.is_master = 0;  /* Assume that we start as a replica */
89 
90     if ((ret = create_env(progname, &dbenv)) != 0)
91 	goto err;
92 
93     /* Make APP_DATA available through the environment handle. */
94     dbenv->app_private = &my_app_data;
95 
96     /* Default priority is 100. */
97     dbenv->rep_set_priority(dbenv, 100);
98     /* Permanent messages require at least one ack. */
99     dbenv->repmgr_set_ack_policy(dbenv, DB_REPMGR_ACKS_ONE);
100     /* Give 500 microseconds to receive the ack. */
101     dbenv->rep_set_timeout(dbenv, DB_REP_ACK_TIMEOUT, 500);
102 
103     /* Collect the command line options. */
104     while ((ch = getopt(argc, argv, "h:l:L:p:r:")) != EOF)
105 	switch (ch) {
106 	case 'h':
107 	    home = optarg;
108 	    break;
109 	/* Set the host and port used by this environment. */
110 	case 'L':
111 	    is_group_creator = 1; /* FALLTHROUGH */
112 	case 'l':
113 	    host = optarg;
114 	    /*
115 	     * The final colon in host:port string is the
116 	     * boundary between the host and the port portions
117 	     * of the string.
118 	     */
119 	    if ((last_colon = strrchr(host, ':')) == NULL ) {
120 		fprintf(stderr, "Bad local host specification.\n");
121 		goto err;
122 	    }
123 	    /*
124 	     * Separate the host and port portions of the
125 	     * string for further processing.
126 	     */
127 	    portstr = last_colon + 1;
128 	    *last_colon = '\0';
129 	    port = (unsigned short)atoi(portstr);
130 	    if ((ret =
131 	      dbenv->repmgr_site(dbenv, host, port, &dbsite, 0)) != 0){
132 		fprintf(stderr, "Could not set local address %s:%d.\n",
133 		  host, port);
134 		goto err;
135 	    }
136 	    dbsite->set_config(dbsite, DB_LOCAL_SITE, 1);
137 	    if (is_group_creator)
138 		dbsite->set_config(dbsite, DB_GROUP_CREATOR, 1);
139 
140 	    if ((ret = dbsite->close(dbsite)) != 0) {
141 		dbenv->err(dbenv, ret, "DB_SITE->close");
142 		goto err;
143 	}
144 	    local_is_set = 1;
145 	    break;
146 	/* Set this replica's election priority. */
147 	case 'p':
148 	    dbenv->rep_set_priority(dbenv, atoi(optarg));
149 	    break;
150 	/* Identify another site in the replication group. */
151 	case 'r':
152 	    host = optarg;
153 	    /*
154 	     * The final colon in host:port string is the
155 	     * boundary between the host and the port portions
156 	     * of the string.
157 	     */
158 	    if ((last_colon = strrchr(host, ':')) == NULL ) {
159 		fprintf(stderr, "Bad remote host specification.\n");
160 		goto err;
161 	    }
162 	    /*
163 	     * Separate the host and port portions of the
164 	     * string for further processing.
165 	     */
166 	    portstr = last_colon + 1;
167 	    *last_colon = '\0';
168 	    port = (unsigned short)atoi(portstr);
169 	    if ((ret = dbenv->repmgr_site(dbenv, host, port, &dbsite, 0)) != 0) {
170 		dbenv->err(dbenv, ret, "DB_ENV->repmgr_site");
171 		goto err;
172 	    }
173 	    dbsite->set_config(dbsite, DB_BOOTSTRAP_HELPER, 1);
174 	    if ((ret = dbsite->close(dbsite)) != 0) {
175 		dbenv->err(dbenv, ret, "DB_SITE->close");
176 		goto err;
177 	    }
178 	    break;
179 	case '?':
180 	default:
181 	    usage();
182 	}
183 
184     /* Error check command line. */
185     if (home == NULL || !local_is_set)
186 	usage();
187 
188     if ((ret = env_init(dbenv, home)) != 0)
189 	goto err;
190 
191     if ((ret = dbenv->repmgr_start(dbenv, 3, DB_REP_ELECTION)) != 0)
192 	goto err;
193 
194     if ((ret = doloop(dbenv)) != 0) {
195 	dbenv->err(dbenv, ret, "Application failed");
196 	goto err;
197     }
198 
199 err: if (dbenv != NULL)
200 	(void)dbenv->close(dbenv, 0);
201 
202     return (ret);
203 
204 }
205 
206 /* Create and configure an environment handle. */
207 int
create_env(const char * progname,DB_ENV ** dbenvp)208 create_env(const char *progname, DB_ENV **dbenvp)
209 {
210     DB_ENV *dbenv;
211     int ret;
212 
213     if ((ret = db_env_create(&dbenv, 0)) != 0) {
214 	fprintf(stderr, "can't create env handle: %s\n",
215 	    db_strerror(ret));
216 	return (ret);
217     }
218 
219     dbenv->set_errfile(dbenv, stderr);
220     dbenv->set_errpfx(dbenv, progname);
221     (void)dbenv->set_event_notify(dbenv, event_callback);
222 
223     *dbenvp = dbenv;
224     return (0);
225 }
226 
227 /* Open and configure an environment. */
228 int
env_init(DB_ENV * dbenv,const char * home)229 env_init(DB_ENV *dbenv, const char *home)
230 {
231     u_int32_t flags;
232     int ret;
233 
234     (void)dbenv->set_cachesize(dbenv, 0, CACHESIZE, 0);
235     (void)dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1);
236 
237     flags = DB_CREATE |
238 	DB_INIT_LOCK |
239 	DB_INIT_LOG |
240 	DB_INIT_MPOOL |
241 	DB_INIT_REP |
242 	DB_INIT_TXN |
243 	DB_RECOVER |
244 	DB_THREAD;
245     if ((ret = dbenv->open(dbenv, home, flags, 0)) != 0)
246 	dbenv->err(dbenv, ret, "can't open environment");
247     return (ret);
248 }
249 
250 /*
251  * A callback used to determine whether the local environment is a
252  * replica or a master. This is called by the Replication Manager
253  * when the local environment changes state.
254  */
255 static void
event_callback(DB_ENV * dbenv,u_int32_t which,void * info)256 event_callback(DB_ENV *dbenv, u_int32_t which, void *info)
257 {
258     APP_DATA *app = dbenv->app_private;
259 
260     info = NULL;                /* Currently unused. */
261 
262     switch (which) {
263     case DB_EVENT_REP_MASTER:
264 	app->is_master = 1;
265 	break;
266 
267     case DB_EVENT_REP_CLIENT:
268 	app->is_master = 0;
269 	break;
270 
271     case DB_EVENT_REP_STARTUPDONE: /* FALLTHROUGH */
272     case DB_EVENT_REP_NEWMASTER:
273 	/* Ignore. */
274 	break;
275 
276     default:
277 	dbenv->errx(dbenv, "ignoring event %d", which);
278     }
279 }
280 
281 /*
282  * Provides the main data processing function for our application.
283  * This function provides a command line prompt to which the user
284  * can provide a ticker string and a stock price.  Once a value is
285  * entered to the application, the application writes the value to
286  * the database and then displays the entire database.
287  */
288 #define	   BUFSIZE 1024
289 
290 int
doloop(DB_ENV * dbenv)291 doloop(DB_ENV *dbenv)
292 {
293     DB *dbp;
294     APP_DATA *app_data;
295     DBT key, data;
296     char buf[BUFSIZE], *rbuf;
297     int ret;
298     u_int32_t flags;
299 
300     dbp = NULL;
301     ret = 0;
302     memset(&key, 0, sizeof(key));
303     memset(&data, 0, sizeof(data));
304     app_data = dbenv->app_private;
305 
306     for (;;) {
307 	if (dbp == NULL) {
308 	    if ((ret = db_create(&dbp, dbenv, 0)) != 0)
309 		return (ret);
310 
311 	    flags = DB_AUTO_COMMIT;
312 	    if (app_data->is_master)
313 		flags |= DB_CREATE;
314 	    if ((ret = dbp->open(dbp,
315 		NULL, DATABASE, NULL, DB_BTREE, flags, 0)) != 0) {
316 		if (ret == ENOENT) {
317 		    printf(
318 		      "No stock database yet available.\n");
319 		    if ((ret = dbp->close(dbp, 0)) != 0) {
320 			dbenv->err(dbenv, ret, "DB->close");
321 			goto err;
322 		    }
323 		    dbp = NULL;
324 		    sleep(SLEEPTIME);
325 		    continue;
326 		}
327 		dbenv->err(dbenv, ret, "DB->open");
328 		goto err;
329 	    }
330 	}
331 
332 	printf("QUOTESERVER%s> ",
333 	    app_data->is_master ? "" : " (read-only)");
334 	fflush(stdout);
335 
336 	if (fgets(buf, sizeof(buf), stdin) == NULL)
337 	    break;
338 	if (strtok(&buf[0], " \t\n") == NULL) {
339 	    switch ((ret = print_stocks(dbp))) {
340 	    case 0:
341 		continue;
342 	    case DB_REP_HANDLE_DEAD:
343 		(void)dbp->close(dbp, DB_NOSYNC);
344 		dbp = NULL;
345                 dbenv->errx(dbenv, "Got a dead replication handle");
346 		continue;
347 	    default:
348 		dbp->err(dbp, ret, "Error traversing data");
349 		goto err;
350 	    }
351 	}
352 	rbuf = strtok(NULL, " \t\n");
353 	if (rbuf == NULL || rbuf[0] == '\0') {
354 	    if (strncmp(buf, "exit", 4) == 0 ||
355 		strncmp(buf, "quit", 4) == 0)
356 		break;
357 	    dbenv->errx(dbenv, "Format: TICKER VALUE");
358 	    continue;
359 	}
360 
361 	if (!app_data->is_master) {
362 	    dbenv->errx(dbenv, "Can't update at client");
363 	    continue;
364 	}
365 
366 	key.data = buf;
367 	key.size = (u_int32_t)strlen(buf);
368 
369 	data.data = rbuf;
370 	data.size = (u_int32_t)strlen(rbuf);
371 
372 	if ((ret = dbp->put(dbp,
373 	    NULL, &key, &data, 0)) != 0) {
374 	    dbp->err(dbp, ret, "DB->put");
375 	    goto err;
376 	}
377     }
378 
379 err: if (dbp != NULL)
380 	(void)dbp->close(dbp, DB_NOSYNC);
381 
382     return (ret);
383 }
384 
385 /* Display all the stock quote information in the database. */
386 int
print_stocks(DB * dbp)387 print_stocks(DB *dbp)
388 {
389     DBC *dbc;
390     DBT key, data;
391 #define	   MAXKEYSIZE    10
392 #define	   MAXDATASIZE    20
393     char keybuf[MAXKEYSIZE + 1], databuf[MAXDATASIZE + 1];
394     int ret, t_ret;
395     u_int32_t keysize, datasize;
396 
397     if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0) {
398 	dbp->err(dbp, ret, "can't open cursor");
399 	return (ret);
400     }
401 
402     memset(&key, 0, sizeof(key));
403     memset(&data, 0, sizeof(data));
404 
405     printf("\tSymbol\tPrice\n");
406     printf("\t======\t=====\n");
407 
408     for (ret = dbc->c_get(dbc, &key, &data, DB_FIRST);
409 	ret == 0;
410 	ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) {
411 	keysize = key.size > MAXKEYSIZE ? MAXKEYSIZE : key.size;
412 	memcpy(keybuf, key.data, keysize);
413 	keybuf[keysize] = '\0';
414 
415 	datasize = data.size >= MAXDATASIZE ? MAXDATASIZE : data.size;
416 	memcpy(databuf, data.data, datasize);
417 	databuf[datasize] = '\0';
418 
419 	printf("\t%s\t%s\n", keybuf, databuf);
420     }
421     printf("\n");
422     fflush(stdout);
423 
424     if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
425 	ret = t_ret;
426 
427     switch (ret) {
428     case 0:
429     case DB_NOTFOUND:
430 	return (0);
431     case DB_LOCK_DEADLOCK:
432 	return (0);
433     default:
434 	return (ret);
435     }
436 }
437 
438