+/* folder_manager_client.c
+ *
+ * This code is Copyright (c) 2020, by the authors of nmh. See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+#include "h/mh.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "h/folder_manager_protocol.h"
+#include "h/managed_folders.h"
+#include "h/utils.h"
+#include "sbr/error.h"
+#include "sbr/getcpy.h"
+#include "sbr/m_getfld.h"
+#include "sbr/trimcpy.h"
+
+struct mh_folder_manager_client {
+ int skt;
+ FILE *readf, *writef;
+ svector_t response;
+ char *message_buf;
+ size_t message_buf_len;
+ mh_managed_folder *mmf;
+};
+
+struct mh_folder_manager_inc_context {
+ mh_managed_folder_inc_page page;
+ mh_folder_manager_client *mfmc;
+ mh_folder_manager_protocol_read_message_t rmt;
+};
+
+static char const OP_MAKE_DRAFT[] = "op:comp\n";
+static char const OP_FOLDER[] = "op:folder\n";
+static char const OP_MAKE_FOLDER[] = "op:make-folder\n";
+static char const OP_MARK_ADD[] = "op:mark-add\n";
+static char const OP_MARK_DEL[] = "op:mark-del\n";
+static char const OP_RMF[] = "op:rmf\n";
+static char const OP_RMM[] = "op:rmm\n";
+static char const OP_UPDATE_DRAFT[] = "op:comp-update\n";
+static char const OP_INC[] = "op:inc\n";
+static char const OP_PICK[] = "op:pick\n";
+static char const OP_REFILE_FILE[] = "op:refile-file\n";
+static char const OP_REFILE_MOVE[] = "op:refile-move\n";
+static char const KEY_FILE[] = "file:";
+static char const KEY_FOLDER[] = "folder:";
+static char const KEY_DEST[] = "dest:";
+static char const KEY_MSG[] = "msg:";
+static char const KEY_MSGS[] = "msgs:";
+static char const KEY_NEED_UNSEEN[] = "needunseen:";
+static char const KEY_SEQS[] = "seqs:";
+
+static bool
+read_message(mh_folder_manager_client *const mfmc);
+
+static bool
+write_message(mh_folder_manager_client *const mfmc, char const *const name1, ...) ENDNULL;
+
+static char *
+msgs_str(struct msgs_array const msgs);
+
+static char *
+seqs_str(struct svector const *const sequences);
+
+bool
+mh_folder_manager_connect(
+ mh_folder_manager_client **const result,
+ mh_managed_folder *const mmf)
+{
+ struct sockaddr_un sun = { 0 };
+ mh_folder_manager_client* mfmc;
+ // TODO hang this off the mmfs so we can free everything in one bang?
+ NEW(mfmc);
+ mfmc->skt = -1;
+ mfmc->readf = mfmc->writef = NULL;
+ mfmc->response = NULL;
+ mfmc->message_buf = NULL;
+ mfmc->message_buf_len = 0;
+ mfmc->mmf = mmf;
+ *result = mfmc;
+
+ char const *socket_path = mh_managed_folder_socket_path(mmf);
+ sun.sun_family = AF_UNIX;
+ // TODO ensure strlen(socket_path) < sizeof sun.sun_path at configuration load time
+ strncpy(sun.sun_path, socket_path, strlen(socket_path));
+ mfmc->skt = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (mfmc->skt == -1) {
+ // TODO return errors rather than abort
+ adios(socket_path, "socket");
+ }
+ // 1. Try to connect to running manager.
+ if (connect(mfmc->skt, (struct sockaddr*)&sun, sizeof sun) == -1) {
+ if (errno != ENOENT) {
+ // 2. If not ENOENT, unlink the stale socket name.
+ if (unlink(socket_path) < 0)
+ adios(socket_path, "unable to unlink");
+ }
+ // 3. make pipe
+ int fds[2];
+ if (pipe(fds) < 0)
+ adios ("pipe", "unable to");
+ // 4. fork
+ char const *path = mh_managed_folder_manager_path(mmf);
+ switch (fork()) {
+ case -1:
+ adios("fork", "unable to");
+ case 0:
+ // 5.child. folder-manager; prints to stdout if successfully listening
+ if (dup2(fds[1], 1) < 0)
+ adios("pipe to child stdout", "dup2");
+ if (execlp(path, "imap-folder-manager", mh_managed_folder_name(mmf), NULL) < 0)
+ adios(path, "unable to exec");
+ break;
+ default: {
+ if (close(fds[1]) < 0)
+ adios("pipe", "close");
+ // 5. parent read from pipe to child
+ char buf[1];
+ int status = read(fds[0], buf, 1);
+ // 6.parent if error, die
+ if (status < 0)
+ adios("folder manager", "unable to read from");
+ if (status < 1)
+ adios("folder manager", "read too small from");
+ // 7.parent else, time to connect again! this time, die if error
+ if (connect(mfmc->skt, (struct sockaddr*)&sun, sizeof sun) == -1)
+ adios(socket_path, "unable to connect");
+ break; }
+ }
+ }
+
+ mfmc->readf = fdopen(mfmc->skt, "r");
+ if (mfmc->readf == NULL) {
+ adios(socket_path, "fdopen for reading");
+ }
+
+ mfmc->writef = fdopen(mfmc->skt, "w");
+ if (mfmc->writef == NULL) {
+ adios(socket_path, "fdopen for writing");
+ }
+
+ return true;
+}
+
+bool
+mh_folder_manager_close(mh_folder_manager_client *const mfmc)
+{
+ bool success = true;
+ free(mfmc->message_buf);
+ if (fclose(mfmc->readf) < 0) {
+ perror("TODO fclose");
+ //set_err(mfmc->mmfs, TODO);
+ success = false;
+ }
+ free(mfmc);
+ return success;
+}
+
+int
+mh_folder_manager_create_draft(
+ mh_folder_manager_client *const mfmc,
+ char const *const draft_folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_MAKE_DRAFT,
+ KEY_FOLDER, draft_folder, "\n",
+ NULL)) {
+ return 0;
+ }
+ if (!read_message(mfmc)) {
+ return 0;
+ }
+ int msgnum = 0;
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ char const *const name = svector_read_at(mfmc->response, i++);
+ char *const value = svector_read_at(mfmc->response, i);
+ if (strcmp(name, "msg") == 0) {
+ errno = 0;
+ msgnum = strtol(value, NULL, 10);
+ if (errno != 0) {
+ fprintf(stderr, "TODO failed to convert msgnum from %s\n", value);
+ return 0;
+ }
+ if (msgnum == 0) {
+ fputs("TODO msgnum mustn't be 0\n", stderr);
+ return 0;
+ }
+ break;
+ }
+ }
+ if (msgnum == 0) {
+ // error - manager didn't return "msg" component
+ return 0;
+ }
+ return msgnum;
+}
+
+bool
+mh_folder_manager_update_draft(
+ mh_folder_manager_client *const mfmc,
+ char const *const draft_folder,
+ int const msgnum)
+{
+ char msgnum_buf[32];
+ sprintf(msgnum_buf, "%d\n", msgnum);
+ if (!write_message(
+ mfmc,
+ OP_UPDATE_DRAFT,
+ KEY_FOLDER, draft_folder, "\n",
+ KEY_MSG, msgnum_buf, /* msgnum_buf has final newline */
+ NULL)) {
+ return false;
+ }
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_make_folder(
+ mh_folder_manager_client *const mfmc,
+ char const *const folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_MAKE_FOLDER,
+ KEY_FOLDER, folder, "\n",
+ NULL)) {
+ return false;
+ }
+ if (!read_message(mfmc)) {
+ return false;
+ }
+ int status = NOTOK;
+ char const *error = NULL;
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ char const *key = svector_read_at(mfmc->response, i++);
+ char const *val = svector_read_at(mfmc->response, i);
+ if (strcmp(key, "status") == 0) {
+ if (strcmp(val, "OK") == 0) {
+ status = OK;
+ }
+ } else if (strcmp(key, "error") == 0) {
+ error = val;
+ }
+ }
+ if (status == OK) {
+ if (error != NULL) {
+ // TODO don't spam stderr
+ fprintf(stderr, "folder manager said OK but also gave error message: %s\n", error);
+ mh_managed_folder_set_err(mfmc->mmf, "folder manager said OK but also gave error message");
+ return false;
+ }
+ return true;
+ }
+ if (error == NULL) {
+ mh_managed_folder_set_err(mfmc->mmf, "unspecified error from folder manager");
+ } else {
+ mh_managed_folder_set_err(mfmc->mmf, error);
+ }
+ return false;
+}
+
+static mh_folder_manager_inc_context *
+new_inc_page(mh_folder_manager_client *const mfmc)
+{
+ mh_folder_manager_inc_context *result;
+ NEW(result);
+ result->page.done = false;
+ result->page.folder = NULL;
+ result->mfmc = mfmc;
+ result->rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ return result;
+}
+
+mh_folder_manager_inc_context *
+mh_folder_manager_inc(mh_folder_manager_client *const mfmc, char const *const folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_INC,
+ KEY_FOLDER, folder, "\n",
+ NULL)) {
+ return NULL;
+ }
+ return new_inc_page(mfmc);
+}
+
+mh_managed_folder_inc_page *
+mh_folder_manager_inc_next(mh_folder_manager_inc_context *const mfinc)
+{
+ if (mfinc->page.done) {
+ fprintf(stderr, "TODO can't call after done!\n");
+ return NULL;
+ }
+
+ free_svector_and_elements(&mfinc->mfmc->response);
+ mfinc->mfmc->response = svector_create(16);
+
+ if (!mh_folder_manager_protocol_read_message(mfinc->mfmc->response, &mfinc->rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfinc->mfmc->mmf, "see above");
+ return NULL;
+ }
+
+ int status = OK;
+ char const *error = NULL;
+ for (size_t i = 0; i < svector_size(mfinc->mfmc->response); i++) {
+ char const *const name = svector_read_at(mfinc->mfmc->response, i++);
+ char *const value = svector_read_at(mfinc->mfmc->response, i);
+ if (strcmp(name, "status") == 0) {
+ if (strcmp(value, "OK") == 0) {
+ mfinc->page.done = true;
+ m_getfld_state_destroy(&mfinc->rmt.getfld_ctx);
+ } else if (strcmp(value, "NOTOK") == 0) {
+ status = NOTOK;
+ } else if (strcmp(value, "INC_FOLDER") != 0) {
+ inform("folder manager status=%s", value);
+ mh_managed_folder_set_err(mfinc->mfmc->mmf, "illegal folder manager status");
+ return NULL;
+ }
+ } else if (strcmp(name, "folder") == 0) {
+ free(mfinc->page.folder);
+ mfinc->page.folder = mh_xstrdup(value);
+ } else if (strcmp(name, "msg") == 0) {
+ errno = 0;
+ mfinc->page.msg = strtol(value, NULL, 10);
+ if (errno != 0) {
+ fprintf(stderr, "TODO failed to convert msgnum from %s\n", value);
+ return NULL;
+ }
+ } else if (strcmp(name, "unseen") == 0) {
+ mfinc->page.unseen = *value == '1';
+ } else if (strcmp(name, "error") == 0) {
+ error = value;
+ } else if (strcmp(name, "len") != 0) {
+ fprintf(stderr, "TODO unknown response component %s\n", name);
+ return NULL;
+ }
+ }
+
+ if (status == NOTOK) {
+ mh_managed_folder_set_err(mfinc->mfmc->mmf,
+ error == NULL ? "NOTOK status without error message" : error);
+ return NULL;
+ } else if (error != NULL) {
+ // TODO complain about error status:OK
+ }
+
+ return &mfinc->page;
+}
+
+bool
+mh_folder_manager_folder_read(mh_folder_manager_client *const mfmc, char const *const folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_FOLDER,
+ KEY_FOLDER, folder, "\n",
+ NULL)) {
+ return false;
+ }
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_refile_move(
+ mh_folder_manager_client *const mfmc,
+ char const *const src_folder,
+ struct msgs_array const msgs,
+ char const *const dst_folder)
+{
+ char *msgs_buf = msgs_str(msgs);
+ if (!write_message(
+ mfmc,
+ OP_REFILE_MOVE,
+ KEY_FOLDER, src_folder, "\n",
+ KEY_MSGS, msgs_buf, /* msgs_buf has final newline */
+ KEY_DEST, dst_folder, "\n",
+ NULL)) {
+ free(msgs_buf);
+ return false;
+ }
+ free(msgs_buf);
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_refile_file(
+ mh_folder_manager_client *const mfmc,
+ char const *const src_file,
+ char const *const dst_folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_REFILE_FILE,
+ KEY_FILE, src_file, "\n",
+ KEY_DEST, dst_folder, "\n",
+ NULL)) {
+ return false;
+ }
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_pick(
+ mh_folder_manager_client *const mfmc,
+ char const *const folder,
+ struct msgs_array const msgs,
+ int const need_unseen)
+{
+ if (need_unseen < 0 || need_unseen > 1) {
+ die("mh_folder_manager_pick need_unseen must be 0 or 1");
+ }
+ char *msgs_buf = msgs_str(msgs);
+ if (!write_message(
+ mfmc,
+ OP_PICK,
+ KEY_FOLDER, folder, "\n",
+ KEY_MSGS, msgs_buf, /* msgs_buf has final newline */
+ KEY_NEED_UNSEEN, (need_unseen ? "1\n" : "0\n"),
+ NULL)) {
+ free(msgs_buf);
+ return false;
+ }
+ free(msgs_buf);
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_rmf(mh_folder_manager_client *const mfmc, char const *const folder)
+{
+ if (!write_message(
+ mfmc,
+ OP_RMF,
+ KEY_FOLDER, folder, "\n",
+ NULL)) {
+ return false;
+ }
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_rmm(mh_folder_manager_client *const mfmc, char const *const folder, struct msgs_array const msgs)
+{
+ char *msgs_buf = msgs_str(msgs);
+ if (!write_message(
+ mfmc,
+ OP_RMM,
+ KEY_FOLDER, folder, "\n",
+ KEY_MSGS, msgs_buf, /* msgs_buf has final newline */
+ NULL)) {
+ free(msgs_buf);
+ return false;
+ }
+ free(msgs_buf);
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_mark_add(
+ mh_folder_manager_client *const mfmc,
+ char const *const folder,
+ struct msgs_array const msgs,
+ struct svector const *const sequences)
+{
+ char *msgs_buf = msgs_str(msgs);
+ char *seqs_buf = seqs_str(sequences);
+ if (!write_message(
+ mfmc,
+ OP_MARK_ADD,
+ KEY_FOLDER, folder, "\n",
+ KEY_MSGS, msgs_buf, /* msgs_buf has final newline */
+ KEY_SEQS, seqs_buf, /* seqs_buf has final newline */
+ NULL)) {
+ free(msgs_buf);
+ free(seqs_buf);
+ return false;
+ }
+ free(msgs_buf);
+ free(seqs_buf);
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+mh_folder_manager_mark_del(
+ mh_folder_manager_client *const mfmc,
+ char const *const folder,
+ struct msgs_array const msgs,
+ struct svector const *const sequences)
+{
+ char *msgs_buf = msgs_str(msgs);
+ char *seqs_buf = seqs_str(sequences);
+ if (!write_message(
+ mfmc,
+ OP_MARK_DEL,
+ KEY_FOLDER, folder, "\n",
+ KEY_MSGS, msgs_buf, /* msgs_buf has final newline */
+ KEY_SEQS, seqs_buf, /* seqs_buf has final newline */
+ NULL)) {
+ free(msgs_buf);
+ free(seqs_buf);
+ return false;
+ }
+ free(msgs_buf);
+ free(seqs_buf);
+
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ if (!mh_folder_manager_protocol_read_message(mfmc->response, &rmt)) {
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already wrote
+ * advise message to stderr. */
+ mh_managed_folder_set_err(mfmc->mmf, "see above");
+ return false;
+ }
+
+ for (size_t i = 0; i < svector_size(mfmc->response); i++) {
+ if (strcmp(svector_read_at(mfmc->response, i++), "status") == 0) {
+ if (strcmp(svector_read_at(mfmc->response, i), "OK") != 0) {
+ fprintf(stderr, "TODO handle error message\n");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+// TODO why is this not all inside mh_folder_manager_protocol_read_message
+static bool
+read_message(mh_folder_manager_client *const mfmc)
+{
+ free_svector_and_elements(&mfmc->response);
+ mfmc->response = svector_create(16);
+
+ mh_folder_manager_protocol_read_message_t rmt;
+ rmt.getfld_ctx = m_getfld_state_init(mfmc->readf);
+ const bool success = mh_folder_manager_protocol_read_message(mfmc->response, &rmt);
+ m_getfld_state_destroy(&rmt.getfld_ctx);
+ return success;
+}
+
+static void
+allocate_buffer(mh_folder_manager_client *const mfmc, size_t const len)
+{
+ if (mfmc->message_buf_len < len) {
+ if (mfmc->message_buf_len == 0)
+ mfmc->message_buf_len = 1024;
+ for (; mfmc->message_buf_len < len; mfmc->message_buf_len *= 2);
+ free(mfmc->message_buf);
+ mfmc->message_buf = mh_xmalloc(mfmc->message_buf_len);
+ }
+}
+
+static bool
+write_message(mh_folder_manager_client *const mfmc, char const *const name1, ...)
+{
+ // Iterate over parameters the first time adding up the buffer size.
+ va_list list;
+ size_t len = 0;
+ {
+ va_start(list, name1);
+ char const *cp = name1;
+ while (cp != NULL) {
+ // Recomputing lengths of all the names which we already knew.
+ // Not hard to fix that, but we're just going to replace this
+ // with calls into the Rust implementation anyway.
+ len += strlen(cp);
+ cp = va_arg(list, char const *);
+ }
+ va_end(list);
+ }
+
+ allocate_buffer(mfmc, len);
+
+ // Copy. (TODO good fit for writev(2) instead)
+ {
+ char *mp = mfmc->message_buf;
+ va_start(list, name1);
+ char const *cp = name1;
+ while (cp != NULL) {
+ mp = stpcpy(mp, cp);
+ cp = va_arg(list, char const *);
+ }
+ va_end(list);
+ }
+ return mh_folder_manager_protocol_write_message(mfmc->writef, mfmc->message_buf, len);
+}
+
+static char *
+msgs_str(struct msgs_array const msgs)
+{
+ // Start with the number of bytes we add: one space after each msg but the last, which gets LF.
+ size_t msgs_len = msgs.size;
+ for (int i = 0; i < msgs.size; i++) {
+ msgs_len += strlen(msgs.msgs[i]);
+ }
+ char *const result = mh_xmalloc(msgs_len);
+ char *mp = result;
+ for (int i = 0; i < msgs.size; i++) {
+ mp = stpcpy(mp, msgs.msgs[i]);
+ mp = stpcpy(mp, " ");
+ }
+ if (mp == result) {
+ stpcpy(result, "\n");
+ } else {
+ *(mp - 1) = '\n';
+ }
+ return result;
+}
+
+static char *
+seqs_str(struct svector const *const sequences)
+{
+ // Start with the number of bytes we add: one space after each seq but the last, which gets LF.
+ size_t seqs_len = svector_size(sequences);
+ for (size_t i = 0; i < svector_size(sequences); i++) {
+ seqs_len += strlen(svector_read_at(sequences, i));
+ }
+ char *const result = mh_xmalloc(seqs_len);
+ char *mp = result;
+ for (size_t i = 0; i < svector_size(sequences); i++) {
+ mp = stpcpy(mp, svector_read_at(sequences, i));
+ mp = stpcpy(mp, " ");
+ }
+ if (mp == result) {
+ stpcpy(result, "\n");
+ } else {
+ *(mp - 1) = '\n';
+ }
+ return result;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+/* copied from seq_read.c
+ * TODO put them back together
+ */
+
+#include "ssequal.h"
+#include "trimcpy.h"
+#include "m_atoi.h"
+#include "brkstring.h"
+#include "lock_file.h"
+
+/*
+ * static prototypes
+ */
+static int seq_init (struct msgs *, char *, char *);
+static void seq_private (struct msgs *);
+
+
+
+/*
+ * read folder's sequences file for public sequences
+ */
+
+typedef struct {
+ // outputs
+ bool done;
+ int failed_to_lock;
+ char name[NAMESZ], *value;
+
+ // inputs
+ struct msgs *mp;
+ int lockflag;
+
+ // internal state
+ char seqfile[PATH_MAX];
+ FILE *fp;
+ m_getfld_state_t gstate;
+ char valuebuf[NMH_BUFSIZ];
+} seq_public_state_t;
+static int
+seq_public_open(seq_public_state_t *const sps)
+{
+ /*
+ * If mh_seq == NULL or if *mh_seq == 0 (the user has defined
+ * the "mh-sequences" profile entry, but left it empty),
+ * then just return, and do not initialize any public sequences.
+ */
+ if (mh_seq == NULL || *mh_seq == 0) {
+ sps->done = true;
+ return OK;
+ }
+ sps->done = false;
+
+ /* get filename of sequence file */
+ snprintf(sps->seqfile, sizeof(sps->seqfile), "%s/%s", sps->mp->foldpath, mh_seq);
+ sps->fp = lkfopendata(sps->seqfile, sps->lockflag ? "r+" : "r", &sps->failed_to_lock);
+ if (sps->fp == NULL) {
+ sps->done = true;
+ return NOTOK;
+ }
+
+ /* Use m_getfld2 to scan sequence file */
+ sps->gstate = m_getfld_state_init(sps->fp);
+ return OK;
+}
+static void
+seq_public_next(seq_public_state_t *const sps)
+{
+ if (sps->done) {
+ adios(sps->seqfile, "attempt to keep reading after sequence file is done");
+ }
+ {
+ int state, fieldsz = sizeof sps->valuebuf;
+ switch (state = m_getfld2(&sps->gstate, sps->name, sps->valuebuf, &fieldsz)) {
+ case FLD:
+ case FLDPLUS:
+ if (state == FLDPLUS) {
+ char *cp = mh_xstrdup(sps->valuebuf);
+ while (state == FLDPLUS) {
+ fieldsz = sizeof sps->valuebuf;
+ state = m_getfld2(&sps->gstate, sps->name, sps->valuebuf, &fieldsz);
+ cp = add (sps->valuebuf, cp);
+ }
+ sps->value = trimcpy(cp);
+ free(cp);
+ } else {
+ sps->value = trimcpy(sps->valuebuf);
+ }
+ return;
+
+ case BODY:
+ lkfclosedata (sps->fp, sps->seqfile);
+ die("no blank lines are permitted in %s", sps->seqfile);
+ break;
+
+ case FILEEOF:
+ break;
+
+ default:
+ lkfclosedata (sps->fp, sps->seqfile);
+ die("%s is poorly formatted", sps->seqfile);
+ }
+ }
+ sps->done = true;
+ m_getfld_state_destroy (&sps->gstate);
+
+ if (sps->lockflag) {
+ sps->mp->seqhandle = sps->fp;
+ sps->mp->seqname = mh_xstrdup(sps->seqfile);
+ } else {
+ lkfclosedata (sps->fp, sps->seqfile);
+ }
+}
+
+
+/*
+ * Scan profile/context list for private sequences.
+ *
+ * We search the context list for all keys that look like
+ * "atr-seqname-folderpath", and add them as private sequences.
+ */
+
+static void
+seq_private (struct msgs *mp)
+{
+ int i, j, alen, plen;
+ char *cp;
+ struct node *np;
+
+ alen = LEN("atr-");
+ plen = strlen (mp->foldpath) + 1;
+
+ for (np = m_defs; np; np = np->n_next) {
+ if (ssequal ("atr-", np->n_name)
+ && (j = strlen (np->n_name) - plen) > alen
+ && *(np->n_name + j) == '-'
+ && strcmp (mp->foldpath, np->n_name + j + 1) == 0) {
+ cp = mh_xstrdup(np->n_name + alen);
+ *(cp + j - alen) = 0;
+ if ((i = seq_init (mp, cp, getcpy (np->n_field))) != -1)
+ make_seq_private (mp, i);
+ }
+ }
+}
+
+
+/*
+ * Split up the different message ranges at whitespace
+ */
+typedef struct {
+ bool done;
+ int start;
+ int end;
+ char *const *piece;
+ char *field;
+} parse_range_t;
+static void
+parse_range(parse_range_t *const state)
+{
+ if (state->done) {
+ die("attempt to keep reading after sequence range is done");
+ }
+ if (state->piece == NULL) {
+ state->piece = brkstring(state->field, " ", "\n");
+ state->done = *state->piece == NULL;
+ } else {
+ state->piece++;
+ }
+ if ((state->done = *state->piece == NULL)) {
+ return;
+ }
+ char *const dash_if_any = strchr(*state->piece, '-');
+ if (dash_if_any != NULL)
+ *dash_if_any = 0;
+ state->start = m_atoi (*state->piece);
+ if (state->start > 0) {
+ state->end = dash_if_any == NULL ? state->start : m_atoi(dash_if_any+1);
+ }
+}
+
+/*
+ * Add the name of sequence to the list of folder sequences.
+ * Then parse the list of message ranges for this
+ * sequence, and setup the various bit flags for each
+ * message in the sequence.
+ *
+ * Return internal index for the sequence if successful.
+ * Return -1 on error.
+ */
+
+static int
+seq_init (struct msgs *mp, char *name, char *field)
+{
+ unsigned int i;
+ int is_current;
+
+ /*
+ * Check if this is "cur" sequence,
+ * so we can do some special things.
+ */
+ is_current = !strcmp (current, name);
+
+ /*
+ * Search for this sequence name to see if we've seen
+ * it already. If we've seen this sequence before,
+ * then clear the bit for this sequence from all the
+ * messages in this folder.
+ */
+ for (i = 0; i < svector_size (mp->msgattrs); i++) {
+ if (!strcmp (svector_read_at (mp->msgattrs, i), name)) {
+ for (int j = mp->lowmsg; j <= mp->hghmsg; j++)
+ clear_sequence (mp, i, j);
+ break;
+ }
+ }
+
+ /*
+ * If we've already seen this sequence name, just free the
+ * name string. Else add it to the list of sequence names.
+ */
+ if (svector_read_at (mp->msgattrs, i)) {
+ free (name);
+ } else {
+ svector_push_back (mp->msgattrs, name);
+ }
+
+ /*
+ * Split up the different message ranges at whitespace
+ */
+ parse_range_t state;
+ state.done = false;
+ state.piece = NULL;
+ state.field = field;
+ for (parse_range(&state); !state.done; parse_range(&state)) {
+ if (state.start > 0) {
+ /*
+ * Keep mp->curmsg and "cur" sequence in synch. Unlike
+ * other sequences, this message doesn't need to exist.
+ * Think about the series of command (rmm; next) to
+ * understand why this can be the case. But if it does
+ * exist, we will still set the bit flag for it like
+ * other sequences.
+ */
+ if (is_current)
+ mp->curmsg = state.start;
+ /*
+ * We iterate through messages in this range
+ * and flip on bit for this sequence.
+ */
+ for (int j = state.start; j <= state.end; j++) {
+ if (j >= mp->lowmsg && j <= mp->hghmsg && does_exist(mp, j))
+ add_sequence (mp, i, j);
+ }
+ }
+ }
+
+ free (field); /* free string containing message ranges */
+ return i;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+/* copied from folder_read.c
+ * TODO put them back together
+
+ * The original folder_read uses readdir to find messages, but managed folders
+ * have a different source of truth known only to the folder manager.
+ * We never ask the folder manager for ALL messages. Instead, we pass it the
+ * user's message specifications and expect it to write all matching message
+ * numbers into the nmh-picked sequence.
+ */
+
+#include "m_maildir.h"
+
+/* We allocate the `mi' array 1024 elements at a time */
+#define NUMMSGS 1024
+
+/*
+ * 1) Create the folder/message structure
+ * 2) Read the directory (folder) and temporarily
+ * record the numbers of the messages we have seen.
+ * 3) Then allocate the array for message attributes and
+ * set the initial flags for all messages we've seen.
+ * 4) Read and initialize the sequence information.
+ */
+
+struct msgs *
+mh_managed_folder_read (char *in_name, int lockflag)
+{
+ struct msgs *mp;
+ struct bvector *v;
+ size_t i;
+ char *const name = m_mailpath (in_name);
+
+ /* Allocate the main structure for folder information */
+ NEW(mp);
+ clear_folder_flags (mp);
+ mp->foldpath = name;
+ mp->lowmsg = 0;
+ mp->hghmsg = 0;
+ mp->curmsg = 0;
+ mp->lowsel = 0;
+ mp->hghsel = 0;
+ mp->numsel = 0;
+ mp->nummsg = 0;
+ mp->seqhandle = NULL;
+ mp->seqname = NULL;
+
+ if (access (name, W_OK) == -1)
+ set_readonly (mp);
+
+ mp->msgattrs = svector_create (0);
+
+ // inlined fromseq_read
+ /*
+ * Initialize the list of sequence names. Go ahead and
+ * add the "cur" sequence to the list of sequences.
+ */
+ svector_push_back (mp->msgattrs, getcpy (current));
+ make_all_public (mp); /* initially, make all public */
+
+ seq_public_state_t sps;
+ sps.mp = mp;
+ sps.lockflag = lockflag;
+ if (seq_public_open(&sps) != OK) {
+ if (sps.failed_to_lock) {
+ advise (sps.seqfile, "failed to lock");
+ // TODO leaks!
+ return NULL;
+ }
+ perror("TODO is errno even useful or did lkfopendata clobber it?");
+ adios(sps.seqfile, "TODO pre-existing bug was to continue on all other failures");
+ // TODO leaks!
+ return NULL;
+ }
+
+ {
+ /*
+ * Allocate a temporary place to record the
+ * name of the messages in this folder.
+ */
+ int len = NUMMSGS;
+ int *mi = mh_xmalloc ((size_t) (len * sizeof(*mi)));
+
+ seq_public_next(&sps);
+ // TODO make nmh-picked configurable. if user already configured Previous-Sequence, use that?
+ if (!sps.done && strcmp(sps.name, "nmh-picked") == 0) {
+ parse_range_t state;
+ state.done = false;
+ state.piece = NULL;
+ state.field = sps.value;
+ for (parse_range(&state); !state.done; parse_range(&state)) {
+ if (state.start <= 0) {
+ adios(sps.seqfile, "folder manager put invalid message number %d into `nmh-picked` sequence", state.start);
+ }
+ if (state.end < state.start) {
+ adios(sps.seqfile, "folder manager put invalid message range %d-%d into `nmh-picked` sequence", state.start, state.end);
+ }
+ for (int msgnum = state.start; msgnum <= state.end; msgnum++) {
+ /*
+ * Check if we need to allocate more
+ * temporary elements for message names.
+ */
+ if (mp->nummsg >= len) {
+ len += NUMMSGS;
+ mi = mh_xrealloc (mi, (size_t) (len * sizeof(*mi)));
+ }
+
+ /* Check if this is the first message we've seen */
+ if (mp->nummsg == 0) {
+ mp->lowmsg = msgnum;
+ mp->hghmsg = msgnum;
+ } else {
+ /* Check if this is it the highest or lowest we've seen? */
+ if (msgnum < mp->lowmsg)
+ mp->lowmsg = msgnum;
+ if (msgnum > mp->hghmsg)
+ mp->hghmsg = msgnum;
+ }
+
+ /*
+ * Now increment count, and record message
+ * number in a temporary place for now.
+ */
+ mi[mp->nummsg++] = msgnum;
+
+ /* TODO Original readdir-based version would set this when
+ * encountering a file type it didn't know about. But this is
+ * only used by `folder -r` to determine whether this folder
+ * has subfolders to process.
+ * Instead we'll have `folder -r` ask the folder manager to
+ * enumerate subfolders. */
+ //set_other_files (mp);
+ }
+ }
+
+ free(sps.value);
+ seq_public_next(&sps);
+ }
+
+ mp->lowoff = max (mp->lowmsg, 1);
+
+ /* Go ahead and allocate space for 100 additional messages. */
+ mp->hghoff = mp->hghmsg + 100;
+
+ /* for testing, allocate minimal necessary space */
+ /* mp->hghoff = max (mp->hghmsg, 1); */
+
+ /*
+ * If for some reason hghoff < lowoff (like we got an integer overflow)
+ * the complain about this now.
+ */
+
+ if (mp->hghoff < mp->lowoff) {
+ die("Internal failure: high message limit < low message "
+ "limit; possible overflow?");
+ }
+
+ /*
+ * Allocate space for status of each message.
+ */
+ mp->num_msgstats = MSGSTATNUM (mp->lowoff, mp->hghoff);
+ mp->msgstats = mh_xmalloc (MSGSTATSIZE(mp));
+ for (i = 0, v = mp->msgstats; i < mp->num_msgstats; ++i, ++v) {
+ bvector_init(v);
+ }
+
+ /*
+ * Scan through the array of messages we've seen and
+ * setup the initial flags for those messages in the
+ * newly allocated mp->msgstats area.
+ */
+ for (int msgnum = 0; msgnum < mp->nummsg; msgnum++) {
+ set_exists (mp, mi[msgnum]);
+ }
+
+ free (mi); /* We don't need this anymore */
+ }
+
+ /*
+ * Read and initialize the sequence information.
+ */
+ // inlined seq_read
+/*
+ * Get the sequence information for this folder from
+ * .mh_sequences (or equivalent specified in .mh_profile)
+ * or context file (for private sequences).
+ */
+
+
+ /* If folder is empty, don't scan for sequence information */
+ if (mp->nummsg == 0) {
+ return mp;
+ }
+
+ /* Initialize the public sequences */
+ for (; !sps.done; seq_public_next(&sps)) {
+ seq_init(mp, mh_xstrdup(sps.name), sps.value);
+ }
+
+ /* Initialize the private sequences */
+ seq_private (mp);
+
+ return mp;
+}