1 /* Copyright (c) 2003 Michael B. Allen <mba2000 ioplex.com>
2  *
3  * The MIT License
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21  * OTHER DEALINGS IN THE SOFTWARE.
22  */
23 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #include <errno.h>
29 #include <limits.h>
30 
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/ipc.h>
36 #include <sys/sem.h>
37 #include <sys/mman.h>
38 #include <sys/wait.h>
39 
40 #define FLAGS_SEMUNDO 1
41 #define IS_SEMUNDO(cmd) (((cmd)->flags & FLAGS_SEMUNDO) != 0)
42 
43 #define MAX_TRIES  3
44 #define MAX_SEMNUM 255
45 
46 #define CMD_OPEN     1
47 #define CMD_REMOVE   2
48 #define CMD_POST     3
49 #define CMD_POSTMULT 4
50 #define CMD_WAIT     5
51 #define CMD_TRYWAIT  6
52 #define CMD_SETVAL   7
53 #define CMD_GETVAL   8
54 
55 #define ERR(e,s) fprintf(stderr, "%d: %s: %s\n", __LINE__, (s), strerror(e))
56 
57 static const char *cmdnames[] = {
58 	NULL, "CMD_OPEN", "CMD_REMOVE", "CMD_POST", "CMD_POSTMULT",
59 	"CMD_WAIT", "CMD_TRYWAIT", "CMD_SETVAL", "CMD_GETVAL"
60 };
61 
62 static const char *cmdflags = " orpmwtsg";
63 
64 struct cmd {
65 	int cmd;
66 	int semid;
67 	int semnum;
68 	const char *path;
69 	int flags;
70 	int oflags;
71 	mode_t mode;
72 	int numsems;
73 	int value;
74 };
75 
76 void
cmd_print(struct cmd * cmd)77 cmd_print(struct cmd *cmd)
78 {
79 	char oflags[255], *o = oflags;
80 	*o = '\0';
81 	if ((cmd->oflags & O_CREAT))
82 		o += sprintf(o, "O_CREAT");
83 	if ((cmd->oflags & O_EXCL))
84 		o += sprintf(o, "|O_EXCL");
85 	fprintf(stderr,
86 			"%s: semid: %d semnum: %d path: %s flags: %s oflags: %s mode: %04o numsems: %d value: %d\n",
87 			cmdnames[cmd->cmd], cmd->semid, cmd->semnum, cmd->path ? cmd->path : "",
88 			IS_SEMUNDO(cmd) ? "FLAGS_SEMUNDO" : "", oflags, cmd->mode, cmd->numsems, cmd->value);
89 }
90 
91 
92 #if defined(_SEM_SEMUN_UNDEFINED) || \
93 		(defined(__digital__) && defined(__unix__)) || \
94 		defined(_HPUX_SOURCE)
95 union semun {
96 	int val;                  /* value for SETVAL */
97 	struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
98 	unsigned short *array;    /* array for GETALL, SETALL */
99 	/* Linux specific part: */
100 	struct seminfo *__buf;    /* buffer for IPC_INFO */
101 };
102 #endif
103 
104 /* Open or create a semaphore initializing it as necessary.
105  */
106 static int
semid_get(const char * name,int nsems,int oflags,mode_t mode,int value)107 semid_get(const char *name, int nsems, int oflags, mode_t mode, int value)
108 {
109 	key_t key;
110 	int max;
111 
112 	if (nsems > MAX_SEMNUM) {
113 		ERR(errno = ERANGE, "semid_get");
114 		return -1;
115 	}
116 	if ((key = ftok((char *)name, 1)) == (key_t)-1) {
117 		ERR(errno, "ftok");
118 		return -1;
119 	}
120 
121 	/* This following loop ensures that we know if the semaphore was created
122 	 * as opposed to just opened so that it can be initialized properly. We
123 	 * do this by alternating between oflags 0 and IPC_CREATE|IPC_EXCL until
124 	 * one succeeds.
125 	 */
126 	for (max = MAX_TRIES; max; max--) {
127 		int semid;
128 		union semun arg;
129 
130 		if ((oflags & O_EXCL) == 0) {
131 			if ((semid = semget(key, nsems, 0)) != -1) {
132 				struct semid_ds buf;
133 
134 				/* This inner try-loop ensures that the semaphore is initialized before
135 				 * we return even if the semaphore has been created with semget but not
136 				 * yet initialized with semctl. See Stevens' UNPv2 p274.
137 				 */
138 				arg.buf = &buf;
139 				for (max = MAX_TRIES; max; max--) {
140 					if (semctl(semid, 0, IPC_STAT, arg) == -1) {
141 						ERR(errno, "semctl");
142 						return -1;
143 					}
144 					if (buf.sem_otime != 0) {
145 						return semid;
146 					}
147 					sleep(1);
148 				}
149 
150 				ERR(errno = ETIMEDOUT, "semid_get");
151 				return -1;
152 			} else if (errno != ENOENT) {
153 				ERR(errno, "semget");
154 				return -1;
155 			}
156 		}
157 		if ((semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | (mode & 0777))) != -1) {
158 			struct sembuf initop;
159 
160 			if (nsems > 1) {
161 				unsigned short array[MAX_SEMNUM * sizeof(unsigned short)];
162 				int i;
163 
164 				arg.array = array;
165 				arg.array[0] = 0; /* leave the first one 0 to be set with semop */
166 				for (i = 1; i < nsems; i++) {
167 					arg.array[i] = value;
168 				}
169 				if (semctl(semid, 0, SETALL, arg) == -1) {
170 					ERR(errno, "semctl");
171 					semctl(semid, 0, IPC_RMID);
172 					return -1;
173 				}
174 			} else {
175 				arg.val = 0;
176 				if (semctl(semid, 0, SETVAL, arg) == -1) {
177 					ERR(errno, "semctl");
178 					semctl(semid, 0, IPC_RMID);
179 					return -1;
180 				}
181 			}
182                                  /* increment by value to set sem_otime nonzero */
183 			initop.sem_num = 0;
184 			initop.sem_op = value;
185 			initop.sem_flg = 0;
186 			if (semop(semid, &initop, 1) == -1) {
187 				ERR(errno, "semop");
188 				semctl(semid, 0, IPC_RMID);
189 				return -1;
190 			}
191 
192 			return semid;
193 		} else if ((oflags & O_EXCL) || errno != EEXIST) {
194 			ERR(errno, "semget");
195 			return -1;
196 		}
197 	}
198 
199 	ERR(errno = ETIMEDOUT, "semid_get");
200 	return -1;
201 }
202 static int
cmd_open(struct cmd * cmd)203 cmd_open(struct cmd *cmd)
204 {
205 	int semid, fd = 0;
206 	char oflags[255], *o = oflags;
207 
208 	if ((cmd->oflags & O_CREAT) && (fd = open(cmd->path, O_CREAT, cmd->mode)) == -1) {
209 		ERR(errno, "open");
210 		return -1;
211 	}
212 
213 	if ((semid = semid_get(cmd->path, cmd->numsems, cmd->oflags, cmd->mode, cmd->value)) == -1) {
214 		return -1;
215 	}
216 	*o = '\0';
217 	if ((cmd->oflags & O_CREAT))
218 		o += sprintf(o, "O_CREAT");
219 	if ((cmd->oflags & O_EXCL))
220 		o += sprintf(o, "|O_EXCL");
221 	fprintf(stderr, "%d = cmd_open(\"%s\", %d, %s, %04o, %d)\n",
222 			semid, cmd->path, cmd->numsems, oflags, cmd->mode, cmd->value);
223 
224 	if (fd) close(fd);
225 
226 	printf("%d", semid);
227 
228 	return 0;
229 }
230 static int
cmd_remove(struct cmd * cmd)231 cmd_remove(struct cmd *cmd)
232 {
233 	fprintf(stderr, "cmd_remove(%d)\n", cmd->semid);
234 	if (semctl(cmd->semid, 0, IPC_RMID) == -1) {
235 		ERR(errno, "semctl");
236 		return -1;
237 	}
238 
239 	return 0;
240 }
241 static int
cmd_wait(struct cmd * cmd)242 cmd_wait(struct cmd *cmd)
243 {
244 	struct sembuf wait;
245 
246 	wait.sem_num = cmd->semnum;
247 	wait.sem_op = -1;
248 	wait.sem_flg = IS_SEMUNDO(cmd) ? SEM_UNDO : 0;
249 
250 	fprintf(stderr, "cmd_wait(%d, %d, %s)\n",
251 			cmd->semid, cmd->semnum, IS_SEMUNDO(cmd) ? "SEM_UNDO" : "0");
252 	if (semop(cmd->semid, &wait, 1) == -1) {
253 		ERR(errno, "semop");
254 		return -1;
255 	}
256 
257 	return 0;
258 }
259 static int
cmd_trywait(struct cmd * cmd)260 cmd_trywait(struct cmd *cmd)
261 {
262 	struct sembuf wait;
263 
264 	wait.sem_num = cmd->semnum;
265 	wait.sem_op = -1;
266 	wait.sem_flg = IPC_NOWAIT | (IS_SEMUNDO(cmd) ? SEM_UNDO : 0);
267 
268 	fprintf(stderr, "cmd_wait(%d, %d, %s)\n",
269 			cmd->semid, cmd->semnum, IS_SEMUNDO(cmd) ? "IPC_NOWAIT|SEM_UNDO" : "IPC_NOWAIT");
270 	if (semop(cmd->semid, &wait, 1) == -1) {
271 		ERR(errno, "semop");
272 		return errno == EAGAIN ? 0 : -1;
273 	}
274 
275 	return 0;
276 }
277 static int
cmd_post(struct cmd * cmd)278 cmd_post(struct cmd *cmd)
279 {
280 	struct sembuf post;
281 
282 	post.sem_num = cmd->semnum;
283 	post.sem_op = 1;
284 	post.sem_flg = IS_SEMUNDO(cmd) ? SEM_UNDO : 0;
285 
286 	fprintf(stderr, "cmd_post(%d, %d, %s)\n",
287 			cmd->semid, cmd->semnum, IS_SEMUNDO(cmd) ? "SEM_UNDO" : "0");
288 	if (semop(cmd->semid, &post, 1) == -1) {
289 		ERR(errno, "semop");
290 		return -1;
291 	}
292 
293 	return 0;
294 }
295 static int
cmd_post_multiple(struct cmd * cmd)296 cmd_post_multiple(struct cmd *cmd)
297 {
298 	struct sembuf post;
299 	int count = cmd->value;
300 
301 	post.sem_num = cmd->semnum;
302 	post.sem_op = 1;
303 	post.sem_flg = IS_SEMUNDO(cmd) ? SEM_UNDO : 0;
304 
305 	fprintf(stderr, "cmd_post_multiple(%d, %d, %s, %d)\n",
306 			cmd->semid, cmd->semnum, IS_SEMUNDO(cmd) ? "SEM_UNDO" : "0", count);
307 	while (count--) {
308 		if (semop(cmd->semid, &post, 1) == -1) {
309 			ERR(errno, "semop");
310 			return -1;
311 		}
312 	}
313 
314 	return 0;
315 }
316 static int
cmd_getvalue(struct cmd * cmd)317 cmd_getvalue(struct cmd *cmd)
318 {
319 	int v;
320 
321 	if ((v = semctl(cmd->semid, cmd->semnum, GETVAL, 0)) == -1) {
322 		ERR(errno, "semctl");
323 		return -1;
324 	}
325 	fprintf(stderr, "%d = cmd_getvalue(%d, %d, GETVAL)\n", v, cmd->semid, cmd->semnum);
326 
327 	return 0;
328 }
329 static int
cmd_setvalue(struct cmd * cmd)330 cmd_setvalue(struct cmd *cmd)
331 {
332 	union semun arg;
333 
334 	arg.val = cmd->value;
335 	if (semctl(cmd->semid, cmd->semnum, SETVAL, arg) == -1) {
336 		ERR(errno,"semctl");
337 		return -1;
338 	}
339 	fprintf(stderr, "cmd_setvalue(%d, %d, SETVAL)\n", cmd->semid, cmd->semnum);
340 
341 	return 0;
342 }
343 
344 typedef int (*cmd_fn)(struct cmd *cmd);
345 cmd_fn cmd_functions[] = {
346 	NULL,
347 	cmd_open,
348 	cmd_remove,
349 	cmd_post,
350 	cmd_post_multiple,
351 	cmd_wait,
352 	cmd_trywait,
353 	cmd_setvalue,
354 	cmd_getvalue
355 };
356 
357 int
main(int argc,char * argv[])358 main(int argc, char *argv[])
359 {
360 	struct cmd cmd;
361 
362 	if (argc < 4) {
363 usage:
364 		fprintf(stderr, "usage: semc <path|semid and semnum> -o|r|p|m|w|t|g|s <cmdargs>\n"
365 				"     EXAMPLES:\n"
366 				"         open: semc /tmp/foo -o 1 1 777 250 99\n"
367                        /* argv: 0    1        2  3 4 5   6   7 */
368 				"       remove: semc 5441 I -r\n"
369 				"         post: semc 5441 3 -p\n"
370 				"post multiple: semc 5441 3 -m 10\n"
371 				"         wait: semc 5441 3 -w\n"
372 				"     try wait: semc 5441 3 -t\n"
373 				"          get: semc 5441 3 -g\n"
374 				"          set: semc 5441 3 -s 99\n");
375 		fputs("The -o command takes a path and command arguments O_CREAT and/or "
376 				"O_EXCL and/or 0, mode_t in octal, numsems, and the value with which "
377 				"the semaphore(s) should be initialize. All other commands take a semid "
378 				"and semnum but -r igores semnum.\n", stderr);
379 		return EXIT_FAILURE;
380 	}
381 
382 	memset(&cmd, 0, sizeof cmd);
383                                               /* Determine Command */
384 	if (strcmp(argv[2], "-o") == 0) {
385 		cmd.cmd = CMD_OPEN;
386 	} else if (*argv[3] == '-') {
387 		char *ch = strchr(cmdflags, argv[3][1]);
388 		if (!ch) {
389 			fprintf(stderr, "Invalid option: %s\n", argv[2]);
390 			goto usage;
391 		}
392 		cmd.cmd = ch - cmdflags;
393 	} else {
394 		fprintf(stderr, "Invalid options\n");
395 		goto usage;
396 	}
397                              /* Populate struct cmd with Arguments */
398 	switch (cmd.cmd) {
399 		case CMD_OPEN:
400 			if (argc < 8) {
401 				fprintf(stderr, "Too few arguments for command\n");
402 				goto usage;
403 			}
404 			cmd.path = argv[1];
405 			if (*argv[3] == '1') cmd.oflags |= O_CREAT;
406 			if (*argv[4] == '1') cmd.oflags |= O_EXCL;
407 			{
408 				unsigned long mode;
409 
410 				if ((mode = strtoul(argv[5], NULL, 8)) == ULONG_MAX) {
411 					ERR(errno, "strtoul");
412 					goto usage;
413 				}
414 				cmd.mode = mode;
415 			}
416 			if ((unsigned long)(cmd.numsems = strtoul(argv[6], NULL, 0)) == ULONG_MAX) {
417 				ERR(errno, "strtoul");
418 				goto usage;
419 			}
420 			if ((unsigned long)(cmd.value = strtoul(argv[7], NULL, 0)) == ULONG_MAX) {
421 				ERR(errno, "strtoul");
422 				goto usage;
423 			}
424 			break;
425 		case CMD_POSTMULT:
426 		case CMD_SETVAL:
427 			if ((unsigned long)(cmd.value = strtoul(argv[4], NULL, 0)) == ULONG_MAX) {
428 				ERR(errno, "strtoul");
429 				goto usage;
430 			}
431 		case CMD_REMOVE:
432 		case CMD_POST:
433 		case CMD_WAIT:
434 		case CMD_TRYWAIT:
435 		case CMD_GETVAL:
436 			if ((unsigned long)(cmd.semid = strtoul(argv[1], NULL, 0)) == ULONG_MAX ||
437 				(unsigned long)(cmd.semnum = strtoul(argv[2], NULL, 0)) == ULONG_MAX) {
438 				ERR(errno, "strtoul");
439 				goto usage;
440 			}
441 	}
442 
443 	cmd_print(&cmd);
444                                                /* Run the Command */
445 	if (cmd_functions[cmd.cmd](&cmd) == -1) {
446 		fprintf(stderr, "Command failed\n");
447 		return EXIT_FAILURE;
448 	}
449 
450 	return EXIT_SUCCESS;
451 }
452 
453