/* * Copyright © 2005-2013 Red Hat, Inc. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions of the * GNU General Public License v.2. This program is distributed in the hope * that it will be useful, but WITHOUT ANY WARRANTY expressed or implied, * including the implied warranties of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat * trademarks that are incorporated in the source code or documentation are not * subject to the GNU General Public License and may only be used or replicated * with the express permission of Red Hat, Inc. * * Red Hat Author(s): Nathan Straz * Dean Jansa * * New packet format (modeled after SSH) * * uint32 packet size * uint32 sequence number * byte packet type * payload * - strings are sent as uint32 length + chars * */ #include #include #include #include #include #include #include #include "qarsh_packet.h" /* Prototypes */ void parse_qp_hello(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_returncode(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_ack(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_runcmd(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_cmdexit(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_setuser(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_kill(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_recvfile(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_sendfile(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_rstat(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_data(char *buf, int *buflen, struct qa_packet *qp); void parse_qp_data_allow(char *buf, int *buflen, struct qa_packet *qp); char *store_qp_hello(char *buf, struct qa_packet *qp); char *store_qp_returncode(char *buf, struct qa_packet *qp); char *store_qp_runcmd(char *buf, struct qa_packet *qp); char *store_qp_ack(char *buf, struct qa_packet *qp); char *store_qp_cmdexit(char *buf, struct qa_packet *qp); char *store_qp_setuser(char *buf, struct qa_packet *qp); char *store_qp_kill(char *buf, struct qa_packet *qp); char *store_qp_recvfile(char *buf, struct qa_packet *qp); char *store_qp_sendfile(char *buf, struct qa_packet *qp); char *store_qp_rstat(char *buf, struct qa_packet *qp); char *store_qp_data(char *buf, struct qa_packet *qp); char *store_qp_data_allow(char *buf, struct qa_packet *qp); void free_qp_hello(struct qa_packet *qp); void free_qp_returncode(struct qa_packet *qp); void free_qp_runcmd(struct qa_packet *qp); void free_qp_setuser(struct qa_packet *qp); void free_qp_recvfile(struct qa_packet *qp); void free_qp_sendfile(struct qa_packet *qp); void free_qp_rstat(struct qa_packet *qp); void free_qp_data(struct qa_packet *qp); void dump_qp_ack(struct qa_packet *qp); void dump_qp_runcmd(struct qa_packet *qp); void dump_qp_returncode(struct qa_packet *qp); void dump_qp_cmdexit(struct qa_packet *qp); void dump_qp_setuser(struct qa_packet *qp); void dump_qp_kill(struct qa_packet *qp); void dump_qp_recvfile(struct qa_packet *qp); void dump_qp_sendfile(struct qa_packet *qp); void dump_qp_rstat(struct qa_packet *qp); void dump_qp_data(struct qa_packet *qp); void dump_qp_data_allow(struct qa_packet *qp); struct packet_internals { char *pi_name; void (*pi_parse)(char *buf, int *buflen, struct qa_packet *qp); char *(*pi_store)(char *buf, struct qa_packet *qp); void (*pi_free)(struct qa_packet *qp); void (*pi_dump)(struct qa_packet *qp); } qa_pi[] = { { .pi_name = "", .pi_parse = NULL, .pi_store = NULL, .pi_free = NULL, .pi_dump = NULL }, { .pi_name = "hello", .pi_parse = parse_qp_hello, .pi_store = store_qp_hello, .pi_free = free_qp_hello }, { .pi_name = "returncode", .pi_parse = parse_qp_returncode, .pi_store = store_qp_returncode, .pi_free = free_qp_returncode, .pi_dump = dump_qp_returncode }, { .pi_name = "runcmd", .pi_parse = parse_qp_runcmd, .pi_store = store_qp_runcmd, .pi_free = free_qp_runcmd, .pi_dump = dump_qp_runcmd }, { .pi_name = "ack", .pi_parse = parse_qp_ack, .pi_store = store_qp_ack, .pi_free = NULL, .pi_dump = dump_qp_ack }, { .pi_name = "cmdexit", .pi_parse = parse_qp_cmdexit, .pi_store = store_qp_cmdexit, .pi_free = NULL, .pi_dump = dump_qp_cmdexit }, { .pi_name = "setuser", .pi_parse = parse_qp_setuser, .pi_store = store_qp_setuser, .pi_free = free_qp_setuser, .pi_dump = dump_qp_setuser }, { .pi_name = "kill", .pi_parse = parse_qp_kill, .pi_store = store_qp_kill, .pi_free = NULL, .pi_dump = dump_qp_kill }, { .pi_name = "recvfile", .pi_parse = parse_qp_recvfile, .pi_store = store_qp_recvfile, .pi_free = free_qp_recvfile, .pi_dump = dump_qp_recvfile }, { .pi_name = "sendfile", .pi_parse = parse_qp_sendfile, .pi_store = store_qp_sendfile, .pi_free = free_qp_sendfile, .pi_dump = dump_qp_sendfile }, { .pi_name = "rstat", .pi_parse = parse_qp_rstat, .pi_store = store_qp_rstat, .pi_free = free_qp_rstat, .pi_dump = dump_qp_rstat }, { .pi_name = "data", .pi_parse = parse_qp_data, .pi_store = store_qp_data, .pi_free = free_qp_data, .pi_dump = dump_qp_data }, { .pi_name = "dallow", .pi_parse = parse_qp_data_allow, .pi_store = store_qp_data_allow, .pi_free = NULL, .pi_dump = dump_qp_data_allow } }; #define QP_TYPES (sizeof qa_pi / sizeof *qa_pi) #define QP_NAME(n) qa_pi[n].pi_name enum qa_packet_type parse_packet_type(char *s) { int i; for (i = 0; i < QP_TYPES; i++) { if (strcasecmp(s, QP_NAME(i)) == 0) { return (enum qa_packet_type)i; } } return QP_INVALID; } char * qp_packet_type(enum qa_packet_type t) { return QP_NAME(t); } /* * Packet parsing functions */ char * fetch_int(char *buf, int *buflen, int *out) { int i; if (*buflen < sizeof i) { fprintf(stderr, "Not enough data to unpack a uint32_t, %d\n", *buflen); return buf; } memcpy(&i, buf, sizeof i); *out = be32toh(i); *buflen -= sizeof i; return buf + sizeof i; } char * fetch_string(char *buf, int *buflen, char **out) { int slen; if (*buflen < sizeof slen) { fprintf(stderr, "Not enough data to unpack string length, %d\n", *buflen); *out = NULL; return buf; } buf = fetch_int(buf, buflen, &slen); if (slen > *buflen) { fprintf(stderr, "String is more than available data, %d > %d\n", slen, *buflen); *out = NULL; return buf; } *out = malloc(slen+1); memcpy(*out, buf, slen); (*out)[slen] = '\0'; *buflen -= slen; return buf+slen; } char * fetch_off_t(char *buf, int *buflen, off_t *out) { off_t i; if (*buflen < sizeof i) { fprintf(stderr, "Not enough data to unpack a off_t, %d\n", *buflen); return buf; } memcpy(&i, buf, sizeof i); *out = be64toh(i); *buflen -= sizeof i; return buf + sizeof i; } char * fetch_void(char *buf, int *buflen, int count, void *out) { memcpy(out, buf, count); *buflen -= count; return buf+count; } void parse_qp_hello(char *buf, int *buflen, struct qa_packet *qp) { fetch_string(buf, buflen, &(qp->qp_hello.qp_greeting)); } void parse_qp_returncode(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_int(buf, buflen, &(qp->qp_returncode.qp_rc)); buf = fetch_int(buf, buflen, &(qp->qp_returncode.qp_errno)); buf = fetch_string(buf, buflen, &(qp->qp_returncode.qp_strerror)); } void parse_qp_runcmd(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_string(buf, buflen, &(qp->qp_runcmd.qp_cmdline)); } void parse_qp_ack(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_int(buf, buflen, (int *)&(qp->qp_ack.qp_ack_type)); buf = fetch_int(buf, buflen, &(qp->qp_ack.qp_ack_seq)); } void parse_qp_cmdexit(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_int(buf, buflen, &(qp->qp_cmdexit.qp_pid)); buf = fetch_int(buf, buflen, &(qp->qp_cmdexit.qp_status)); } void parse_qp_setuser(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_string(buf, buflen, &(qp->qp_setuser.qp_user)); if (*buflen) buf = fetch_string(buf, buflen, &(qp->qp_setuser.qp_group)); } void parse_qp_kill(char *buf, int *buflen, struct qa_packet *qp) { fetch_int(buf, buflen, &(qp->qp_kill.qp_sig)); } void parse_qp_recvfile(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_string(buf, buflen, &(qp->qp_recvfile.qp_path)); buf = fetch_off_t(buf, buflen, &(qp->qp_recvfile.qp_count)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_recvfile.qp_mode)); } void parse_qp_sendfile(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_string(buf, buflen, &(qp->qp_sendfile.qp_path)); } void parse_qp_rstat(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_string(buf, buflen, &(qp->qp_rstat.qp_path)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_rstat.qp_st_mode)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_rstat.qp_st_uid)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_rstat.qp_st_gid)); buf = fetch_off_t(buf, buflen, &(qp->qp_rstat.qp_st_size)); } void parse_qp_data(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_int(buf, buflen, (int *)&(qp->qp_data.qp_remfd)); buf = fetch_off_t(buf, buflen, &(qp->qp_data.qp_offset)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_data.qp_count)); if (qp->qp_data.qp_count > *buflen) { fprintf(stderr, "Blob is larger than rest of packet, %d > %d\n", qp->qp_data.qp_count, *buflen); free(qp); } else { qp->qp_data.qp_blob = malloc(qp->qp_data.qp_count); qp->qp_data.free_blob = 1; buf = fetch_void(buf, buflen, qp->qp_data.qp_count, qp->qp_data.qp_blob); } } void parse_qp_data_allow(char *buf, int *buflen, struct qa_packet *qp) { buf = fetch_int(buf, buflen, (int *)&(qp->qp_dallow.qp_remfd)); buf = fetch_int(buf, buflen, (int *)&(qp->qp_dallow.qp_count)); } struct qa_packet * parse_packet(char *buf, int buflen) { struct qa_packet *qp = NULL; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); buf = fetch_int(buf, &buflen, &(qp->qp_seq)); buf = fetch_int(buf, &buflen, (int *)&(qp->qp_type)); if (qa_pi[qp->qp_type].pi_parse) qa_pi[qp->qp_type].pi_parse(buf, &buflen, qp); else { fprintf(stderr, "Packet type %d not implemented yet\n", qp->qp_type); free(qp); qp = NULL; } if (buflen) { fprintf(stderr, "%d remaining bytes of data in packet\n", buflen); } return qp; } /* * Packet serialization functions * */ static char * store_int(char *buf, int i) { i = htobe32(i); memcpy(buf, &i, sizeof i); return buf + sizeof i; } static char * store_string(char *buf, char *s) { char *cp; cp = store_int(buf, strlen(s)); memcpy(cp, s, strlen(s)); return cp+strlen(s); } static char * store_off_t(char *buf, off_t o) { o = htobe64(o); memcpy(buf, &o, sizeof o); return buf + sizeof o; } static char * store_void(char *buf, int count, void *d) { char *cp; cp = store_int(buf, count); memcpy(cp, d, count); return cp+count; } char * store_qp_hello(char *buf, struct qa_packet *qp) { return store_string(buf, qp->qp_hello.qp_greeting); } char * store_qp_returncode(char *buf, struct qa_packet *qp) { buf = store_int(buf, qp->qp_returncode.qp_rc); buf = store_int(buf, qp->qp_returncode.qp_errno); buf = store_string(buf, qp->qp_returncode.qp_strerror); return buf; } char * store_qp_runcmd(char *buf, struct qa_packet *qp) { buf = store_string(buf, qp->qp_runcmd.qp_cmdline); return buf; } char * store_qp_ack(char *buf, struct qa_packet *qp) { buf = store_int(buf, qp->qp_ack.qp_ack_type); buf = store_int(buf, qp->qp_ack.qp_ack_seq); return buf; } char * store_qp_cmdexit(char *buf, struct qa_packet *qp) { buf = store_int(buf, qp->qp_cmdexit.qp_pid); buf = store_int(buf, qp->qp_cmdexit.qp_status); return buf; } char * store_qp_setuser(char *buf, struct qa_packet *qp) { buf = store_string(buf, qp->qp_setuser.qp_user); if (qp->qp_setuser.qp_group) { buf = store_string(buf, qp->qp_setuser.qp_group); } return buf; } char * store_qp_kill(char *buf, struct qa_packet *qp) { return store_int(buf, qp->qp_kill.qp_sig); } char * store_qp_recvfile(char *buf, struct qa_packet *qp) { buf = store_string(buf, qp->qp_recvfile.qp_path); buf = store_off_t(buf, qp->qp_recvfile.qp_count); buf = store_int(buf, qp->qp_recvfile.qp_mode); return buf; } char * store_qp_sendfile(char *buf, struct qa_packet *qp) { buf = store_string(buf, qp->qp_sendfile.qp_path); return buf; } char * store_qp_rstat(char *buf, struct qa_packet *qp) { buf = store_string(buf, qp->qp_rstat.qp_path); buf = store_int(buf, qp->qp_rstat.qp_st_mode); buf = store_int(buf, qp->qp_rstat.qp_st_uid); buf = store_int(buf, qp->qp_rstat.qp_st_gid); buf = store_off_t(buf, qp->qp_rstat.qp_st_size); return buf; } char * store_qp_data(char *buf, struct qa_packet *qp) { buf = store_int(buf, qp->qp_data.qp_remfd); buf = store_off_t(buf, qp->qp_data.qp_offset); buf = store_void(buf, qp->qp_data.qp_count, qp->qp_data.qp_blob); return buf; } char * store_qp_data_allow(char *buf, struct qa_packet *qp) { buf = store_int(buf, qp->qp_dallow.qp_remfd); buf = store_int(buf, qp->qp_dallow.qp_count); return buf; } int qptostr(struct qa_packet *qp, char *qpstr, int maxsize) { char *cp; if (qp->qp_type == QP_INVALID) return 0; cp = qpstr; cp = store_int(cp, qp->qp_seq); cp = store_int(cp, qp->qp_type); if (qa_pi[qp->qp_type].pi_store) { cp = qa_pi[qp->qp_type].pi_store(cp, qp); } return cp - qpstr; } /* * Packet construction functions */ struct qa_packet * make_qp_hello(char *greeting) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_HELLO; qp->qp_hello.qp_greeting = strdup(greeting); return qp; } struct qa_packet * make_qp_returncode(int rc, int eno, char *strerr) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_RETURNCODE; qp->qp_returncode.qp_rc = rc; qp->qp_returncode.qp_errno = eno; qp->qp_returncode.qp_strerror = strdup(strerr); return qp; } struct qa_packet * make_qp_ack(enum qa_packet_type t, int i) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_ACK; qp->qp_ack.qp_ack_type = t; qp->qp_ack.qp_ack_seq = i; return qp; } struct qa_packet * make_qp_runcmd(char *cmdline) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_RUNCMD; qp->qp_runcmd.qp_cmdline = strdup(cmdline); return qp; } struct qa_packet * make_qp_cmdexit(pid_t pid, int status) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_CMDEXIT; qp->qp_cmdexit.qp_pid = pid; qp->qp_cmdexit.qp_status = status; return qp; } struct qa_packet * make_qp_setuser(char *user, char *group) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_SETUSER; qp->qp_setuser.qp_user = strdup(user); if (group) qp->qp_setuser.qp_group = strdup(group); return qp; } struct qa_packet * make_qp_kill(int sig) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_KILL; qp->qp_kill.qp_sig = sig; return qp; } struct qa_packet * make_qp_recvfile(const char *path, off_t count, mode_t mode) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_RECVFILE; qp->qp_recvfile.qp_path = strdup(path); qp->qp_recvfile.qp_count = count; qp->qp_recvfile.qp_mode = mode; return qp; } struct qa_packet * make_qp_sendfile(const char *path) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_SENDFILE; qp->qp_sendfile.qp_path = strdup(path); return qp; } struct qa_packet * make_qp_rstat(const char *path, const struct stat *sb) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_RSTAT; qp->qp_rstat.qp_path = strdup(path); if (sb) { qp->qp_rstat.qp_st_mode = sb->st_mode; qp->qp_rstat.qp_st_uid = sb->st_uid; qp->qp_rstat.qp_st_gid = sb->st_gid; qp->qp_rstat.qp_st_size = sb->st_size; } return qp; } struct qa_packet * make_qp_data(const int remfd, const off_t offset, const int count, void *blob) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_DATA; qp->qp_data.qp_remfd = remfd; qp->qp_data.qp_offset = offset; qp->qp_data.qp_count = count; /* data packets are different, the blob is just a pointer which * must stick around until after the packet is sent. This is to * avoid multiple data copies */ qp->qp_data.free_blob = 0; qp->qp_data.qp_blob = blob; return qp; } struct qa_packet * make_qp_data_allow(const int remfd, const int count) { struct qa_packet *qp; qp = malloc(sizeof *qp); assert(qp); memset(qp, 0, sizeof *qp); qp->qp_type = QP_DALLOW; qp->qp_dallow.qp_remfd = remfd; qp->qp_dallow.qp_count = count; return qp; } /* * Packet deallocation functions */ #define condfree(x) if (x) { free(x); } void free_qp_hello(struct qa_packet *qp) { condfree(qp->qp_hello.qp_greeting); } void free_qp_returncode(struct qa_packet *qp) { condfree(qp->qp_returncode.qp_strerror); } void free_qp_runcmd(struct qa_packet *qp) { condfree(qp->qp_runcmd.qp_cmdline); } void free_qp_setuser(struct qa_packet *qp) { condfree(qp->qp_setuser.qp_user); condfree(qp->qp_setuser.qp_group); } void free_qp_recvfile(struct qa_packet *qp) { condfree(qp->qp_recvfile.qp_path); } void free_qp_sendfile(struct qa_packet *qp) { condfree(qp->qp_sendfile.qp_path); } void free_qp_rstat(struct qa_packet *qp) { condfree(qp->qp_rstat.qp_path); } void free_qp_data(struct qa_packet *qp) { if (qp->qp_data.free_blob) free(qp->qp_data.qp_blob); } void qpfree(struct qa_packet *qp) { if (qp) { if (qa_pi[qp->qp_type].pi_free) { qa_pi[qp->qp_type].pi_free(qp); } free(qp); } } #undef condfree /* * Packet printing functions * * For printing out packets for debugging purposes */ void dump_qp_ack(struct qa_packet *qp) { fprintf(stderr, "%s #%d", QP_NAME(qp->qp_ack.qp_ack_type), qp->qp_ack.qp_ack_seq); } void dump_qp_runcmd(struct qa_packet *qp) { fprintf(stderr, "cmdline: %s", qp->qp_runcmd.qp_cmdline); } void dump_qp_returncode(struct qa_packet *qp) { fprintf(stderr, "rc: %d", qp->qp_returncode.qp_rc); } void dump_qp_cmdexit(struct qa_packet *qp) { if (WIFEXITED(qp->qp_cmdexit.qp_status)) { fprintf(stderr, "exited: %d", WEXITSTATUS(qp->qp_cmdexit.qp_status)); } else if (WIFSIGNALED(qp->qp_cmdexit.qp_status)) { fprintf(stderr, "signaled: %d", WTERMSIG(qp->qp_cmdexit.qp_status)); } else { fprintf(stderr, "status: %d", qp->qp_cmdexit.qp_status); } } void dump_qp_setuser(struct qa_packet *qp) { fprintf(stderr, "user: %s group: %s", qp->qp_setuser.qp_user, qp->qp_setuser.qp_group); } void dump_qp_kill(struct qa_packet *qp) { fprintf(stderr, "sig: %d", qp->qp_kill.qp_sig); } void dump_qp_recvfile(struct qa_packet *qp) { fprintf(stderr, "path: %s mode: %o count: %lld", qp->qp_recvfile.qp_path, qp->qp_recvfile.qp_mode, (long long int)qp->qp_recvfile.qp_count); } void dump_qp_sendfile(struct qa_packet *qp) { fprintf(stderr, "path: %s", qp->qp_sendfile.qp_path); } void dump_qp_rstat(struct qa_packet *qp) { fprintf(stderr, "path: %s st_mode: %o st_uid: %d st_gid: %d st_size: %lld", qp->qp_rstat.qp_path, qp->qp_rstat.qp_st_mode, qp->qp_rstat.qp_st_uid, qp->qp_rstat.qp_st_gid, (long long int)qp->qp_rstat.qp_st_size); } void dump_qp_data(struct qa_packet *qp) { fprintf(stderr, "remfd: %d offset: %lld count: %d", qp->qp_data.qp_remfd, (long long int)qp->qp_data.qp_offset, qp->qp_data.qp_count); } void dump_qp_data_allow(struct qa_packet *qp) { fprintf(stderr, "remfd: %d count: %d", qp->qp_dallow.qp_remfd, qp->qp_dallow.qp_count); } void dump_qp(struct qa_packet *qp) { fprintf(stderr, "#%d %s ", qp->qp_seq, QP_NAME(qp->qp_type)); if (qa_pi[qp->qp_type].pi_dump) { qa_pi[qp->qp_type].pi_dump(qp); } fprintf(stderr, "\n"); fflush(stderr); }