/* * Pinochle daemon. * * Expected usage "nc -server 'pinochled socket-path lock-path' ...", * or moral equivalent (eg, via inetd.conf). Other options: * * -log LOGFILE * Logs various things to LOGFILE. (Default: such logs * are thrown away.) * * socket-path and lock-path must be paths used to rendezvous. * lock-path must name a file, which either must exist or must be * creatable; it must be on a filesystem supporting flock locking. * socket-path must name an AF_LOCAL socket, or a path where one can * be created. * * This program operates as both the game-management daemon and the * front-end run, eg, by msd. The front-end just locks, starts the * backend daemon if necessary, and passes the connections off to the * backend. * * This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include "scm-rights.h" extern const char *__progname; typedef unsigned char CARD; #define CARD_SUIT(c) ((c)>>3) #define SUIT_C 0 #define SUIT_D 1 #define SUIT_H 2 #define SUIT_S 3 #define SUIT__N 4 #define CARD_RANK(c) ((c)&7) /* * Note that trick_winner() knows that order induced by numerical value * of rank equals card rank order, or, to put it another way, * CARD_RANK(c1) > CARD_RANK(c2) iff c1 beats c2, assuming they're in * the same suit. */ #define RANK_9 0 #define RANK_J 1 #define RANK_Q 2 #define RANK_K 3 #define RANK_T 4 #define RANK_A 5 #define RANK__N 6 #define CARD_MAKE(s,r) (((s) << 3) | (r)) #define CARD_COPIES 4 #define DECKSIZE (SUIT__N * RANK__N * CARD_COPIES) // must match pinochle.html's value #define HANDSIZE 18 // cards #define PROTO_RESET 0x01 #define PROTO_JUMP 0x02 #define PROTO_ANIM 0x03 #define PROTO_IMG 0x04 #define PROTO_DELAY 0x05 #define PROTO_RAISE 0x06 #define PROTO_LOWER 0x07 #define PROTO_ANIMEND 0x08 #define PROTO_STAGE 0x09 #define PROTO_SCORE 0x0a #define PROTO_PLAY 0x81 #define PROTO_LOC_STOCK 0x00 #define PROTO_LOC_TURNUP 0x01 #define PROTO_LOC_OWNHAND 0x02 #define PROTO_LOC_OPPHAND 0x03 #define PROTO_LOC_PLAY 0x04 #define PROTO_LOC_OWNTRICK 0x05 #define PROTO_LOC_OPPTRICK 0x06 #define PROTO_LOC_OWNMELD 0x07 #define PROTO_LOC_OPPMELD 0x08 #define PROTO_STAGE_DEAL 0x00 #define PROTO_STAGE_1_YOU_LEAD 0x01 #define PROTO_STAGE_1_OPP_LEAD 0x02 #define PROTO_STAGE_1_YOU_PLAY 0x03 #define PROTO_STAGE_1_OPP_PLAY 0x04 #define PROTO_STAGE_1_YOU_MELD 0x05 #define PROTO_STAGE_1_OPP_MELD 0x06 #define PROTO_STAGE_2_YOU_LEAD 0x07 #define PROTO_STAGE_2_OPP_LEAD 0x08 #define PROTO_STAGE_2_YOU_PLAY 0x09 #define PROTO_STAGE_2_OPP_PLAY 0x0a #define PROTO_STAGE_COUNT_UP 0x0b #define IMAGE_BACK 24 typedef struct client CLIENT; typedef struct game GAME; typedef struct gameplayer GAMEPLAYER; struct gameplayer { CLIENT *c; CARD hand[HANDSIZE]; int handsize; CARD trickpile[DECKSIZE]; int trickcards; int score; GAMEPLAYER *other; } ; struct game { GAME *link; int dealer; unsigned char deck[DECKSIZE]; int ndeck; unsigned char trumpup; GAMEPLAYER players[2]; unsigned int flags; #define GF_DEAD 0x00000001 unsigned char phase; #define GP_PHASE_1_LEAD 0 #define GP_PHASE_1_PLAY 1 #define GP_PHASE_1_MELD 2 #define GP_PHASE_1_DRAW 3 #define GP_PHASE_2_LEAD 4 #define GP_PHASE_2_PLAY 5 unsigned char turn; CARD trick_card_1; } ; struct client { CLIENT *link; int ifd; int ofd; AIO_OQ oq; int iid; int oid; GAME *game; int playerx; unsigned int flags; #define CF_DEAD 0x00000001 unsigned char *pbuf; int pba; int pbl; int pbw; void (*msg)(CLIENT *); } ; static const char *sock_path = 0; static const char *lock_path = 0; static const char *logfile = 0; static int logfd = -1; static struct timeval logtime; static struct stat logstat; static int mypid; static int lockfd = -1; static int meetfd; static CLIENT *clients; static GAME *games; static int killid; #define NTEXT 8 static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! sock_path) { sock_path = *av; } else if (! lock_path) { lock_path = *av; } else { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-log")) { WANTARG(); if (logfile) { fprintf(stderr,"%s: log file already specified\n",__progname); errs = 1; } logfile = av[skip]; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); } static void log(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void log(const char *fmt, ...) { char *s; int sl; char *p; int pl; va_list ap; struct timeval nowtv; time_t nowtt; struct tm *tm; struct iovec iov[2]; switch (logfd) { case -2: return; break; case -1: if (! logfile) { logfd = -2; return; } logfd = open(logfile,O_WRONLY|O_CREAT|O_APPEND,0666); if (logfd < 0) { fprintf(stderr,"%s: can't open logfile %s: %s\n",__progname,logfile,strerror(errno)); exit(1); } if (fstat(logfd,&logstat) < 0) { fprintf(stderr,"%s: can't fstat logfile %s: %s\n",__progname,logfile,strerror(errno)); exit(1); } gettimeofday(&nowtv,0); logtime = nowtv; break; default: gettimeofday(&nowtv,0); if (nowtv.tv_sec != logtime.tv_sec) { struct stat stb; if ( (stat(logfile,&stb) < 0) || (stb.st_dev != logstat.st_dev) || (stb.st_ino != logstat.st_ino) ) { int newfd; newfd = open(logfile,O_WRONLY|O_CREAT|O_APPEND,0666); if (newfd >= 0) { close(logfd); logfd = newfd; } } logtime = nowtv; } break; } va_start(ap,fmt); sl = vasprintf(&s,fmt,ap); va_end(ap); nowtt = nowtv.tv_sec; tm = localtime(&nowtt); pl = asprintf(&p,"%04d-%02d-%02d %02d:%02d:%02d.%02d [%d] ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(nowtv.tv_usec/10000), mypid ); iov[0].iov_base = p; iov[0].iov_len = pl; iov[1].iov_base = s; iov[1].iov_len = sl; writev(logfd,&iov[0],2); free(s); free(p); } static void set_nb(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void take_lock(void) { if (lockfd < 0) { lockfd = open(lock_path,O_RDWR|O_CREAT,0666); if (lockfd < 0) { fprintf(stderr,"%s: can't open lock path %s: %s\n",__progname,lock_path,strerror(errno)); exit(1); } } if (flock(lockfd,LOCK_EX) < 0) { fprintf(stderr,"%s: can't lock %s: %s\n",__progname,lock_path,strerror(errno)); exit(1); } } static void release_lock(void) { if (lockfd < 0) abort(); if (flock(lockfd,LOCK_UN) < 0) { fprintf(stderr,"%s: can't unlock %s: %s\n",__progname,lock_path,strerror(errno)); exit(1); } } static const char *card_text(CARD card) { static char rbufs[NTEXT][3]; static int hand = 0; hand = (hand ? hand : NTEXT) - 1; switch (CARD_RANK(card)) { case RANK_9: rbufs[hand][0] = '9'; break; case RANK_J: rbufs[hand][0] = 'J'; break; case RANK_Q: rbufs[hand][0] = 'Q'; break; case RANK_K: rbufs[hand][0] = 'K'; break; case RANK_T: rbufs[hand][0] = 'T'; break; case RANK_A: rbufs[hand][0] = 'A'; break; default: abort(); break; } switch (CARD_SUIT(card)) { case SUIT_C: rbufs[hand][1] = 'C'; break; case SUIT_D: rbufs[hand][1] = 'D'; break; case SUIT_H: rbufs[hand][1] = 'H'; break; case SUIT_S: rbufs[hand][1] = 'S'; break; default: abort(); break; } rbufs[hand][2] = '\0'; return(&rbufs[hand][0]); } static int image_for_card(CARD c) { int s; int r; s = CARD_SUIT(c); r = CARD_RANK(c); if ((s < 0) || (s >= SUIT__N) || (r < 0) || (r >= RANK__N)) abort(); return(r+(s*6)); } static int setup(void) { int s; int pl; int nb; struct sockaddr_un *sa; pid_t kid; take_lock(); pl = strlen(sock_path); nb = sizeof(*sa) - sizeof(sa->sun_path) + pl; sa = malloc(nb); sa->sun_len = nb; sa->sun_family = AF_LOCAL; bcopy(sock_path,&sa->sun_path[0],pl); s = socket(AF_LOCAL,SOCK_DGRAM,0); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } if (connect(s,(void *)sa,nb) < 0) { unlink(sock_path); close(s); s = socket(AF_LOCAL,SOCK_DGRAM,0); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } if (bind(s,(void *)sa,nb) < 0) { fprintf(stderr,"%s: bind %s: %s\n",__progname,sock_path,strerror(errno)); exit(1); } if (listen(s,10) < 0) { fprintf(stderr,"%s: listen %s: %s\n",__progname,sock_path,strerror(errno)); exit(1); } release_lock(); fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { meetfd = s; return(-1); } close(s); s = socket(AF_LOCAL,SOCK_DGRAM,0); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } if (connect(s,(void *)sa,nb) < 0) { fprintf(stderr,"%s: connect %s: %s\n",__progname,sock_path,strerror(errno)); exit(1); } } else { release_lock(); } return(s); } static void handoff(int s) { int fds[2]; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(2*sizeof(int))]; unsigned char content; content = 0; iov.iov_base = &content; iov.iov_len = 1; cmh.cmsg_len = CMLEN(2*sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&ctlbuf[0],sizeof(struct cmsghdr)); fds[0] = 0; fds[1] = 1; bcopy(&fds[0],&ctlbuf[CMSKIP(&cmh)],2*sizeof(int)); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *) &ctlbuf[0]; mh.msg_controllen = CMLEN(2*sizeof(int)); mh.msg_flags = 0; if (sendmsg(s,&mh,0) < 0) { fprintf(stderr,"%s: sendmsg to %s: %s\n",__progname,sock_path,strerror(errno)); exit(1); } } static int kill_dead_clients(void *arg __attribute__((__unused__))) { CLIENT *c; CLIENT **cp; GAME *g; GAME **gp; aio_remove_block(killid); killid = AIO_NOID; cp = &clients; while ((c = *cp)) { if (c->flags & CF_DEAD) { log("sweeping up dead client %p\n",(void *)c); close(c->ifd); close(c->ofd); aio_oq_flush(&c->oq); if (c->game) { log("clearing game %p player %d client\n",(void *)g,c->playerx); c->game->players[c->playerx].c = 0; if (! c->game->players[!c->playerx].c) { log("other player also clear, setting game dead\n"); c->game->flags |= GF_DEAD; } } *cp = c->link; free(c); } else { cp = &c->link; } } gp = &games; while ((g = *gp)) { if (g->flags & GF_DEAD) { log("sweeping up dead game %p\n",(void *)g); *gp = g->link; free(g); } else { gp = &g->link; } } if (! games) exit(0); return(AIO_BLOCK_LOOP); } static void kill_client(CLIENT *c) { aio_remove_poll(c->iid); aio_remove_poll(c->oid); c->flags |= CF_DEAD; if (killid == AIO_NOID) killid = aio_add_block(&kill_dead_clients,0); log("killing client %p\n",(void *)c); } static void proto_client(CLIENT *c, unsigned char opc, ...) #define PROTO_END 7401 // none #define PROTO_INT2 7402 // int #define PROTO_BYTE 7403 // int #define PROTO_STRING 7404 // const char *, int (if int is -ve, use strlen) #define OBUFSIZE 2000 { va_list ap; int key; int ival; const char *sval; unsigned char obuf[OBUFSIZE]; int no; int l; obuf[0] = opc; no = 1; va_start(ap,opc); while <"args"> (1) { key = va_arg(ap,int); switch (key) { case PROTO_END: break <"args">; case PROTO_INT2: ival = va_arg(ap,int); if ((ival < 0) || (ival > 65535)) abort(); if (no+2 > OBUFSIZE) abort(); obuf[no++] = ival >> 8; obuf[no++] = ival & 0xff; break; case PROTO_BYTE: ival = va_arg(ap,int); if ((ival < 0) || (ival >= 255)) abort(); if (no+1 > OBUFSIZE) abort(); obuf[no++] = ival; break; case PROTO_STRING: sval = va_arg(ap,const char *); ival = va_arg(ap,int); if (ival < 0) ival = strlen(sval); if ((ival < 0) || (ival >= 0x807f)) abort(); l = ((ival < 128) ? 1 : 2) + ival; if (no+l > OBUFSIZE) abort(); if (ival < 128) { obuf[no++] = ival; } else { obuf[no++] = 0x80 + ((ival-128) >> 8); obuf[no++] = (ival-128) & 0xff; } bcopy(sval,&obuf[no],ival); no += ival; break; } } /* if (logfd >= 0) { char *logstr; int i; logstr = malloc((no*3)+1); for (i=0;i %p:%s\n",(void *)c,logstr); free(logstr); } */ aio_oq_queue_copy(&c->oq,&obuf[0],no); } static void proto_both(GAME *g, unsigned char opc, ...) { va_list ap; int key; int val; unsigned char obuf[OBUFSIZE]; int no; obuf[0] = opc; no = 1; va_start(ap,opc); while <"args"> (1) { key = va_arg(ap,int); switch (key) { case PROTO_END: break <"args">; case PROTO_INT2: val = va_arg(ap,int); if ((val < 0) || (val > 65535)) abort(); if (no+2 > OBUFSIZE) abort(); obuf[no++] = val >> 8; obuf[no++] = val & 0xff; break; case PROTO_BYTE: val = va_arg(ap,int); if ((val < 0) || (val >= 255)) abort(); if (no+1 > OBUFSIZE) abort(); obuf[no++] = val; break; } } /* if (logfd >= 0) { char *logstr; int i; logstr = malloc((no*3)+1); for (i=0;i *:%s\n",(void *)c,logstr); free(logstr); } */ aio_oq_queue_copy(&g->players[0].c->oq,&obuf[0],no); aio_oq_queue_copy(&g->players[1].c->oq,&obuf[0],no); } static int trick_winner(CARD led, CARD played, CARD trump) { if (CARD_SUIT(led) == CARD_SUIT(played)) return(CARD_RANK(played)>CARD_RANK(led)); if (CARD_SUIT(led) == CARD_SUIT(trump)) return(0); if (CARD_SUIT(played) == CARD_SUIT(trump)) return(1); return(0); } static void addscore(GAMEPLAYER *gp, int n, const char *what) { GAME *g; g = gp->c->game; gp->score += n; proto_client(gp->c,PROTO_SCORE,PROTO_BYTE,0,PROTO_INT2,gp->score,PROTO_INT2,n,PROTO_STRING,what,-1,PROTO_END); proto_client(gp->other->c,PROTO_SCORE,PROTO_BYTE,1,PROTO_INT2,gp->score,PROTO_INT2,n,PROTO_STRING,what,-1,PROTO_END); } static void client_read_room(CLIENT *c, int n) { if (c->pba < n) { if (c->pbl < 1) { free(c->pbuf); c->pbuf = malloc(n); } else { c->pbuf = realloc(c->pbuf,n); } c->pba = n; } } static void client_new_read(CLIENT *); // forward static void client_msg_play(CLIENT *c) { int cardx; CARD card; int i; GAME *g; GAMEPLAYER *gp; if (c->pbl != 2) abort(); g = c->game; if (! g) { log("client %p sent PLAY without being in a game\n",(void *)c); kill_client(c); return; } switch (g->phase) { case GP_PHASE_1_LEAD: case GP_PHASE_1_PLAY: case GP_PHASE_2_LEAD: case GP_PHASE_2_PLAY: if (c->playerx != g->turn) { log("client %p sent PLAY without having the turn\n",(void *)c); kill_client(c); return; } break; default: log("client %p sent PLAY at non-play game phase\n",(void *)c); kill_client(c); return; break; } cardx = c->pbuf[1]; if (cardx >= DECKSIZE) { log("client %p sent PLAY with impossible card index %d\n",(void *)c,cardx); kill_client(c); return; } gp = &g->players[c->playerx]; do <"okay"> { for (i=gp->handsize-1;i>=0;i--) if (gp->hand[i] == cardx) break <"okay">; log("client %p tried to PLAY card %d not in hand\n",(void *)c,cardx); kill_client(c); return; } while (0); card = g->deck[cardx]; log("client %p plays %s\n",(void *)c,card_text(card)); switch (g->phase) { case GP_PHASE_1_LEAD: if ((CARD_RANK(card) == RANK_9) && (CARD_SUIT(card) == CARD_SUIT(g->deck[g->trumpup]))) { addscore(gp,1,"9 of trumps played"); } proto_both(g,PROTO_IMG,PROTO_BYTE,cardx,PROTO_BYTE,image_for_card(g->deck[cardx]),PROTO_END); proto_both(g,PROTO_ANIM,PROTO_BYTE,cardx,PROTO_BYTE,PROTO_LOC_PLAY,PROTO_BYTE,0,PROTO_END); g->trick_card_1 = cardx; g->phase = GP_PHASE_1_PLAY; g->turn = ! g->turn; proto_client(g->players[g->turn].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_YOU_PLAY,PROTO_END); proto_client(g->players[!g->turn].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_OPP_PLAY,PROTO_END); break; case GP_PHASE_1_PLAY: if ((CARD_RANK(card) == RANK_9) && (CARD_SUIT(card) == CARD_SUIT(g->deck[g->trumpup]))) { addscore(gp,1,"9 of trumps played"); } proto_both(g,PROTO_IMG,PROTO_BYTE,cardx,PROTO_BYTE,image_for_card(g->deck[cardx]),PROTO_END); proto_both(g,PROTO_ANIM,PROTO_BYTE,cardx,PROTO_BYTE,PROTO_LOC_PLAY,PROTO_BYTE,1,PROTO_END); g->phase = GP_PHASE_1_MELD; if (! trick_winner(g->deck[g->trick_card_1],g->deck[cardx],g->deck[g->trumpup])) g->turn = ! g->turn; proto_both(g,PROTO_DELAY,PROTO_INT2,1000,PROTO_END); proto_both(g,PROTO_IMG,PROTO_BYTE,cardx,PROTO_BYTE,IMAGE_BACK,PROTO_END); proto_both(g,PROTO_IMG,PROTO_BYTE,g->trick_card_1,PROTO_BYTE,IMAGE_BACK,PROTO_END); proto_client(g->players[g->turn].c,PROTO_ANIM,PROTO_BYTE,g->trick_card_1,PROTO_BYTE,PROTO_LOC_OWNTRICK,PROTO_BYTE,0,PROTO_END); proto_client(g->players[g->turn].c,PROTO_ANIM,PROTO_BYTE,cardx,PROTO_BYTE,PROTO_LOC_OWNTRICK,PROTO_BYTE,0,PROTO_END); proto_client(g->players[!g->turn].c,PROTO_ANIM,PROTO_BYTE,g->trick_card_1,PROTO_BYTE,PROTO_LOC_OPPTRICK,PROTO_BYTE,0,PROTO_END); proto_client(g->players[!g->turn].c,PROTO_ANIM,PROTO_BYTE,cardx,PROTO_BYTE,PROTO_LOC_OPPTRICK,PROTO_BYTE,0,PROTO_END); proto_client(g->players[g->turn].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_YOU_MELD,PROTO_END); proto_client(g->players[!g->turn].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_OPP_MELD,PROTO_END); break; } } static void client_msg_opc(CLIENT *c) { if (c->pbl != 1) abort(); log("client %p sent opcode %02x\n",(void *)c,c->pbuf[0]); switch (c->pbuf[0]) { case PROTO_PLAY: c->pbw = 2; c->msg = &client_msg_play; break; default: log("client %p opcode %02x unknown\n",(void *)c,c->pbuf[0]); kill_client(c); break; } } static void client_new_read(CLIENT *c) { c->pbl = 0; client_read_room(c,1); c->pbw = 1; c->msg = &client_msg_opc; } static void rd_client(void *cv) { CLIENT *c; unsigned char rbuf[1024]; int nr; int o; int n; c = cv; nr = read(c->ifd,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } log("read from client %p: %s\n",(void *)c,strerror(errno)); kill_client(c); return; } if (nr == 0) { log("read from client %p got EOF\n",(void *)c); kill_client(c); return; } o = 0; while (o < nr) { n = c->pbw - c->pbl; if (n < 1) abort(); if (n > nr-o) n = nr - o; bcopy(&rbuf[o],c->pbuf+c->pbl,n); c->pbl += n; o += n; if (c->pbl >= c->pbw) { (*c->msg)(c); if (c->flags & CF_DEAD) break; } } } static int wtest_client(void *cv) { return(aio_oq_nonempty(&((CLIENT *)cv)->oq)); } static void wr_client(void *cv) { CLIENT *c; int w; c = cv; w = aio_oq_writev(&c->oq,c->ofd,-1); switch (w) { case AIO_WRITEV_ERROR: log("client write: %s\n",strerror(errno)); kill_client(c); return; } if (w < 0) { log("libaio broke: aio_oq_writev returned %d\n",w); kill_client(c); return; } aio_oq_dropdata(&c->oq,w); } static void setup_client(void *arg __attribute__((__unused__))) { int fds[2]; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(2*sizeof(int))]; unsigned char connmsg[2]; int connmsglen; int nfd; CLIENT *c; iov.iov_base = &connmsg[0]; iov.iov_len = sizeof(connmsg); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *)&ctlbuf[0]; mh.msg_controllen = CMSPACE(2*sizeof(int)); mh.msg_flags = 0; connmsglen = recvmsg(meetfd,&mh,0); if (connmsglen < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } log("recvmsg on %s: %s\n",sock_path,strerror(errno)); exit(1); } if (mh.msg_controllen < sizeof(struct cmsghdr)) { log("recvmsg on %s: msg_controllen %d too small\n",sock_path,(int)mh.msg_controllen); return; } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_level != SOL_SOCKET) { log("recvmsg on %s: cmsg_level is %d, not SOL_SOCKET\n",sock_path,(int)cmh.cmsg_level); return; } if (cmh.cmsg_type != SCM_RIGHTS) { log("recvmsg on %s: cmsg_type is %d, not SCM_RIGHTS\n",sock_path,(int)cmh.cmsg_type); return; } nfd = (cmh.cmsg_len - CMSKIP(&cmh)) / sizeof(int); if (nfd < 1) { log("recvmsg on %s: received no fds (cmsg_len is %d)\n",sock_path,(int)cmh.cmsg_len); return; } if (nfd == 1) { log("recvmsg on %s: received only one fd (cmsg_len is %d)\n",sock_path,(int)cmh.cmsg_len); bcopy(&ctlbuf[CMSKIP(&cmh)],&fds[0],sizeof(int)); close(fds[0]); return; } if (nfd > 2) { int i; log("recvmsg on %s: received %d fds (cmsg_len is %d)\n",sock_path,nfd,(int)cmh.cmsg_len); for (i=nfd-1;i>=0;i--) { bcopy(&ctlbuf[CMSKIP(&cmh)+(i*sizeof(int))],&fds[0],sizeof(int)); close(fds[0]); } return; } bcopy(&ctlbuf[CMSKIP(&cmh)],&fds[0],2*sizeof(int)); set_nb(fds[0]); set_nb(fds[1]); c = malloc(sizeof(CLIENT)); c->ifd = fds[0]; c->ofd = fds[1]; aio_oq_init(&c->oq); c->iid = aio_add_poll(fds[0],&aio_rwtest_always,&aio_rwtest_never,&rd_client,0,c); c->oid = aio_add_poll(fds[1],&aio_rwtest_never,&wtest_client,0,&wr_client,c); c->game = 0; c->link = clients; c->flags = 0; c->pbuf = 0; c->pba = 0; c->pbl = 0; c->pbw = 0; client_new_read(c); clients = c; log("accepted client %p\n",(void *)c); } static unsigned int rnd(unsigned int n) { int seeded = 0; struct timeval tv; unsigned int h; int i; int j; if (! seeded) { gettimeofday(&tv,0); h = 0x12345678; for (i=sizeof(tv)-1;i>=0;i--) { h ^= ((unsigned char *)&tv)[i]; for (j=8;j>0;j--) h = (h >> 1) ^ ((h & 1) ? 0xedb88320 : 0); } srandom(h); seeded = 1; } return(random()%n); } static void init_gameplayer(GAMEPLAYER *p, GAME *g, CLIENT *c, int x) { p->c = c; c->game = g; c->playerx = x; p->handsize = 0; p->trickcards = 0; p->score = 0; p->other = &g->players[!x]; } static void game_setdealer(GAME *g) { g->dealer = rnd(2); } static void game_shuffle(GAME *g) { int i; int s; int r; CARD c; int j; int t; i = 0; for (s=SUIT__N-1;s>=0;s--) { for (r=RANK__N-1;r>=0;r--) { c = CARD_MAKE(s,r); for (j=CARD_COPIES;j>0;j--) g->deck[i++] = c; } } for (i=DECKSIZE-1;i>0;i--) { j = rnd(i+1); if (j != i) { t = g->deck[i]; g->deck[i] = g->deck[j]; g->deck[j] = t; } } g->ndeck = DECKSIZE; } static void deal_card_to(GAME *g, int to) { int cx; GAMEPLAYER *gp; int hx; if (g->ndeck < 1) abort(); cx = --g->ndeck; gp = &g->players[to]; hx = gp->handsize++; if ((hx < 0) || (hx >= HANDSIZE)) abort(); gp->hand[hx] = cx; proto_both(g,PROTO_RAISE,PROTO_BYTE,cx,PROTO_END); proto_client(g->players[to].c,PROTO_IMG,PROTO_BYTE,cx,PROTO_BYTE,image_for_card(g->deck[cx]),PROTO_END); proto_client(g->players[to].c,PROTO_ANIM,PROTO_BYTE,cx,PROTO_BYTE,PROTO_LOC_OWNHAND,PROTO_BYTE,hx,PROTO_END); proto_client(g->players[!to].c,PROTO_ANIM,PROTO_BYTE,cx,PROTO_BYTE,PROTO_LOC_OPPHAND,PROTO_BYTE,hx,PROTO_END); } static void turn_up_trump(GAME *g) { int cx; // Kludge to help debugging: always turn up a nine if we can { int i; CARD t; for (i=g->ndeck-2;i>=0;i--) { if (CARD_RANK(g->deck[i]) == RANK_9) { t = g->deck[g->ndeck-1]; g->deck[g->ndeck-1] = g->deck[i]; g->deck[i] = t; break; } } } // End kludge cx = --g->ndeck; g->trumpup = cx; proto_both(g,PROTO_LOWER,PROTO_BYTE,cx,PROTO_END); proto_both(g,PROTO_IMG,PROTO_BYTE,cx,PROTO_BYTE,image_for_card(g->deck[cx]),PROTO_END); proto_both(g,PROTO_JUMP,PROTO_BYTE,cx,PROTO_BYTE,PROTO_LOC_TURNUP,PROTO_BYTE,0,PROTO_END); if (CARD_RANK(g->deck[cx]) == RANK_9) addscore(&g->players[g->dealer],1,"9 turned up"); } static void game_delay(GAME *g, int ms) { if (ms < 0) { proto_both(g,PROTO_ANIMEND,PROTO_END); } else { proto_both(g,PROTO_DELAY,PROTO_INT2,ms,PROTO_END); } } static void game_deal(GAME *g) { int i; proto_both(g,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_DEAL,PROTO_END); for (i=HANDSIZE;i>0;i--) { deal_card_to(g,!g->dealer); game_delay(g,-1); deal_card_to(g,g->dealer); game_delay(g,-1); } turn_up_trump(g); } static void start_game(CLIENT *a, CLIENT *b) { GAME *g; if (a->game || b->game) abort(); g = malloc(sizeof(GAME)); g->link = games; games = g; init_gameplayer(&g->players[0],g,a,0); init_gameplayer(&g->players[1],g,b,1); g->flags = 0; game_setdealer(g); proto_client(g->players[g->dealer].c,PROTO_RESET,PROTO_BYTE,1,PROTO_END); proto_client(g->players[!g->dealer].c,PROTO_RESET,PROTO_BYTE,0,PROTO_END); game_shuffle(g); game_deal(g); proto_client(g->players[g->dealer].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_OPP_LEAD,PROTO_END); proto_client(g->players[!g->dealer].c,PROTO_STAGE,PROTO_BYTE,PROTO_STAGE_1_YOU_LEAD,PROTO_END); g->phase = GP_PHASE_1_LEAD; g->turn = ! g->dealer; log("started game %p between %p and %p\n",(void *)g,(void *)a,(void *)b); } static int join_clients(void *arg __attribute__((__unused__))) { CLIENT *c; CLIENT *c1; c1 = 0; for (c=clients;c;c=c->link) { if (! c->game) { if (c1) { start_game(c,c1); return(AIO_BLOCK_LOOP); } c1 = c; } } return(AIO_BLOCK_NIL); } static void daemon_run(void) { clients = 0; games = 0; killid = AIO_NOID; mypid = getpid(); log("Daemon starting\n"); aio_poll_init(); aio_add_poll(meetfd,&aio_rwtest_always,&aio_rwtest_never,&setup_client,0,0); aio_add_block(&join_clients,0); aio_event_loop(); exit(1); } int main(int, char **); int main(int ac, char **av) { int s; handleargs(ac,av); s = setup(); if (s < 0) daemon_run(); handoff(s); return(0); }