+
+/* Send a ready formatted status line via assuan. */
+void
+send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
+{
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ if (strchr (args, '\n'))
+ log_error ("error: LF detected in status line - not sending\n");
+ else
+ assuan_write_status (ctx, keyword, args);
+}
+
+
+/* Helper to send the clients a status change notification. */
+static void
+send_client_notifications (void)
+{
+ struct {
+ pid_t pid;
+#ifdef HAVE_W32_SYSTEM
+ HANDLE handle;
+#else
+ int signo;
+#endif
+ } killed[50];
+ int killidx = 0;
+ int kidx;
+ struct server_local_s *sl;
+
+ for (sl=session_list; sl; sl = sl->next_session)
+ {
+ if (sl->event_signal && sl->assuan_ctx)
+ {
+ pid_t pid = assuan_get_pid (sl->assuan_ctx);
+#ifdef HAVE_W32_SYSTEM
+ HANDLE handle = (void *)sl->event_signal;
+
+ for (kidx=0; kidx < killidx; kidx++)
+ if (killed[kidx].pid == pid
+ && killed[kidx].handle == handle)
+ break;
+ if (kidx < killidx)
+ log_info ("event %lx (%p) already triggered for client %d\n",
+ sl->event_signal, handle, (int)pid);
+ else
+ {
+ log_info ("triggering event %lx (%p) for client %d\n",
+ sl->event_signal, handle, (int)pid);
+ if (!SetEvent (handle))
+ log_error ("SetEvent(%lx) failed: %s\n",
+ sl->event_signal, w32_strerror (-1));
+ if (killidx < DIM (killed))
+ {
+ killed[killidx].pid = pid;
+ killed[killidx].handle = handle;
+ killidx++;
+ }
+ }
+#else /*!HAVE_W32_SYSTEM*/
+ int signo = sl->event_signal;
+
+ if (pid != (pid_t)(-1) && pid && signo > 0)
+ {
+ for (kidx=0; kidx < killidx; kidx++)
+ if (killed[kidx].pid == pid
+ && killed[kidx].signo == signo)
+ break;
+ if (kidx < killidx)
+ log_info ("signal %d already sent to client %d\n",
+ signo, (int)pid);
+ else
+ {
+ log_info ("sending signal %d to client %d\n",
+ signo, (int)pid);
+ kill (pid, signo);
+ if (killidx < DIM (killed))
+ {
+ killed[killidx].pid = pid;
+ killed[killidx].signo = signo;
+ killidx++;
+ }
+ }
+ }
+#endif /*!HAVE_W32_SYSTEM*/
+ }
+ }
+}
+
+
+
+/* This is the core of scd_update_reader_status_file but the caller
+ needs to take care of the locking. */
+static void
+update_reader_status_file (int set_card_removed_flag)
+{
+ int idx;
+ unsigned int status, changed;
+
+ /* Make sure that the reader has been opened. Like get_reader_slot,
+ this part of the code assumes that there is only one reader. */
+ if (!slot_table[0].valid)
+ (void)get_reader_slot ();
+
+ /* Note, that we only try to get the status, because it does not
+ make sense to wait here for a operation to complete. If we are
+ busy working with a card, delays in the status file update should
+ be acceptable. */
+ for (idx=0; idx < DIM(slot_table); idx++)
+ {
+ struct slot_status_s *ss = slot_table + idx;
+ struct server_local_s *sl;
+ int sw_apdu;
+
+ if (!ss->valid || ss->slot == -1)
+ continue; /* Not valid or reader not yet open. */
+
+ sw_apdu = apdu_get_status (ss->slot, 0, &status, &changed);
+ if (sw_apdu == SW_HOST_NO_READER)
+ {
+ /* Most likely the _reader_ has been unplugged. */
+ apdu_close_reader(ss->slot);
+ ss->valid = 0;
+ status = 0;
+ changed = ss->changed;
+ }
+ else if (sw_apdu)
+ {
+ /* Get status failed. Ignore that. */
+ continue;
+ }
+
+ if (!ss->any || ss->status != status || ss->changed != changed )
+ {
+ char *fname;
+ char templ[50];
+ FILE *fp;
+
+ log_info ("updating slot %d status: 0x%04X->0x%04X (%u->%u)\n",
+ ss->slot, ss->status, status, ss->changed, changed);
+ ss->status = status;
+ ss->changed = changed;
+
+ /* FIXME: Should this be IDX instead of ss->slot? This
+ depends on how client sessions will associate the reader
+ status with their session. */
+ snprintf (templ, sizeof templ, "reader_%d.status", ss->slot);
+ fname = make_filename (opt.homedir, templ, NULL );
+ fp = fopen (fname, "w");
+ if (fp)
+ {
+ fprintf (fp, "%s\n",
+ (status & 1)? "USABLE":
+ (status & 4)? "ACTIVE":
+ (status & 2)? "PRESENT": "NOCARD");
+ fclose (fp);
+ }
+ xfree (fname);
+
+ /* If a status script is executable, run it. */
+ {
+ const char *args[9], *envs[2];
+ char numbuf1[30], numbuf2[30], numbuf3[30];
+ char *homestr, *envstr;
+ gpg_error_t err;
+
+ homestr = make_filename (opt.homedir, NULL);
+ if (estream_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
+ log_error ("out of core while building environment\n");
+ else
+ {
+ envs[0] = envstr;
+ envs[1] = NULL;
+
+ sprintf (numbuf1, "%d", ss->slot);
+ sprintf (numbuf2, "0x%04X", ss->status);
+ sprintf (numbuf3, "0x%04X", status);
+ args[0] = "--reader-port";
+ args[1] = numbuf1;
+ args[2] = "--old-code";
+ args[3] = numbuf2;
+ args[4] = "--new-code";
+ args[5] = numbuf3;
+ args[6] = "--status";
+ args[7] = ((status & 1)? "USABLE":
+ (status & 4)? "ACTIVE":
+ (status & 2)? "PRESENT": "NOCARD");
+ args[8] = NULL;
+
+ fname = make_filename (opt.homedir, "scd-event", NULL);
+ err = gnupg_spawn_process_detached (fname, args, envs);
+ if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
+ log_error ("failed to run event handler `%s': %s\n",
+ fname, gpg_strerror (err));
+ xfree (fname);
+ xfree (envstr);
+ }
+ xfree (homestr);
+ }
+
+ /* Set the card removed flag for all current sessions. We
+ will set this on any card change because a reset or
+ SERIALNO request must be done in any case. */
+ if (ss->any && set_card_removed_flag)
+ update_card_removed (idx, 1);
+
+ ss->any = 1;
+
+ /* Send a signal to all clients who applied for it. */
+ send_client_notifications ();
+ }
+
+ /* Check whether a disconnect is pending. */
+ if (opt.card_timeout)
+ {
+ for (sl=session_list; sl; sl = sl->next_session)
+ if (!sl->disconnect_allowed)
+ break;
+ if (session_list && !sl)
+ {
+ /* FIXME: Use a real timeout. */
+ /* At least one connection and all allow a disconnect. */
+ log_info ("disconnecting card in slot %d\n", ss->slot);
+ apdu_disconnect (ss->slot);
+ }
+ }
+
+ }
+}
+
+/* This function is called by the ticker thread to check for changes
+ of the reader stati. It updates the reader status files and if
+ requested by the caller also send a signal to the caller. */
+void
+scd_update_reader_status_file (void)
+{
+ if (!pth_mutex_acquire (&status_file_update_lock, 1, NULL))
+ return; /* locked - give up. */
+ update_reader_status_file (1);
+ if (!pth_mutex_release (&status_file_update_lock))
+ log_error ("failed to release status_file_update lock\n");
+}