34
39
struct dm_hash_table *pvid_to_vgid;
36
41
struct dm_hash_table *vg;
42
pthread_mutex_t vg_lock_map;
37
43
pthread_mutex_t pvid_to_pvmeta;
38
44
pthread_mutex_t vgid_to_metadata;
39
45
pthread_mutex_t pvid_to_vgid;
48
pthread_mutex_t token_lock;
43
__attribute__ ((format(printf, 1, 2)))
44
static void debug(const char *fmt, ...) {
47
fprintf(stderr, "[D %lu] ", pthread_self());
48
vfprintf(stderr, fmt, ap);
53
static int debug_cft_line(const char *line, void *baton) {
54
fprintf(stderr, "| %s\n", line);
58
static void debug_cft(const char *id, struct dm_config_node *n) {
60
dm_config_write_node(n, &debug_cft_line, NULL);
51
static void destroy_metadata_hashes(lvmetad_state *s)
53
struct dm_hash_node *n = NULL;
55
n = dm_hash_get_first(s->vgid_to_metadata);
57
dm_config_destroy(dm_hash_get_data(s->vgid_to_metadata, n));
58
n = dm_hash_get_next(s->vgid_to_metadata, n);
61
n = dm_hash_get_first(s->pvid_to_pvmeta);
63
dm_config_destroy(dm_hash_get_data(s->pvid_to_pvmeta, n));
64
n = dm_hash_get_next(s->pvid_to_pvmeta, n);
66
dm_hash_destroy(s->pvid_to_pvmeta);
67
dm_hash_destroy(s->vgid_to_metadata);
68
dm_hash_destroy(s->vgid_to_vgname);
69
dm_hash_destroy(s->vgname_to_vgid);
71
n = dm_hash_get_first(s->device_to_pvid);
73
dm_free(dm_hash_get_data(s->device_to_pvid, n));
74
n = dm_hash_get_next(s->device_to_pvid, n);
77
dm_hash_destroy(s->device_to_pvid);
78
dm_hash_destroy(s->pvid_to_vgid);
81
static void create_metadata_hashes(lvmetad_state *s)
83
s->pvid_to_pvmeta = dm_hash_create(32);
84
s->device_to_pvid = dm_hash_create(32);
85
s->vgid_to_metadata = dm_hash_create(32);
86
s->vgid_to_vgname = dm_hash_create(32);
87
s->pvid_to_vgid = dm_hash_create(32);
88
s->vgname_to_vgid = dm_hash_create(32);
63
91
static void lock_pvid_to_pvmeta(lvmetad_state *s) {
108
152
static void unlock_vg(lvmetad_state *s, const char *id) {
109
153
pthread_mutex_t *vg;
111
// debug("unlock VG %s\n", id);
112
lock_vgid_to_metadata(s); /* someone might be changing the s->lock.vg structure right
113
* now, so avoid stepping on each other's toes */
155
DEBUGLOG(s, "unlocking VG %s", id);
156
/* Protect the s->lock.vg structure from concurrent access. */
157
pthread_mutex_lock(&s->lock.vg_lock_map);
114
158
if ((vg = dm_hash_lookup(s->lock.vg, id)))
115
159
pthread_mutex_unlock(vg);
116
unlock_vgid_to_metadata(s);
160
pthread_mutex_unlock(&s->lock.vg_lock_map);
119
163
static struct dm_config_node *pvs(struct dm_config_node *vg)
128
* TODO: This set_flag function is pretty generic and might make sense in a
129
* library here or there.
131
static int set_flag(struct dm_config_tree *cft, struct dm_config_node *parent,
132
const char *field, const char *flag, int want) {
133
struct dm_config_value *value = NULL, *pred = NULL;
134
struct dm_config_node *node = dm_config_find_node(parent->child, field);
135
struct dm_config_value *new;
140
while (value && value->type != DM_CFG_EMPTY_ARRAY && strcmp(value->v.str, flag)) {
151
if (value && !want) {
153
pred->next = value->next;
154
} else if (value == node->v && value->next) {
155
node->v = value->next;
157
node->v->type = DM_CFG_EMPTY_ARRAY;
161
if (!value && want) {
163
if (!(node = dm_config_create_node(cft, field)))
165
node->sib = parent->child;
166
if (!(node->v = dm_config_create_value(cft)))
168
node->v->type = DM_CFG_EMPTY_ARRAY;
169
node->parent = parent;
170
parent->child = node;
172
if (!(new = dm_config_create_value(cft))) {
173
/* FIXME error reporting */
176
new->type = DM_CFG_STRING;
185
static struct dm_config_node *make_config_node(struct dm_config_tree *cft,
187
struct dm_config_node *parent,
188
struct dm_config_node *pre_sib)
190
struct dm_config_node *cn;
192
if (!(cn = dm_config_create_node(cft, key)))
200
if (parent && parent->child && !pre_sib) { /* find the last one */
201
pre_sib = parent->child;
202
while (pre_sib && pre_sib->sib)
203
pre_sib = pre_sib->sib;
206
if (parent && !parent->child)
214
static struct dm_config_node *make_text_node(struct dm_config_tree *cft,
217
struct dm_config_node *parent,
218
struct dm_config_node *pre_sib)
220
struct dm_config_node *cn;
222
if (!(cn = make_config_node(cft, key, parent, pre_sib)) ||
223
!(cn->v = dm_config_create_value(cft)))
226
cn->v->type = DM_CFG_STRING;
227
cn->v->v.str = value;
232
static struct dm_config_node *make_int_node(struct dm_config_tree *cft,
235
struct dm_config_node *parent,
236
struct dm_config_node *pre_sib)
238
struct dm_config_node *cn;
240
if (!(cn = make_config_node(cft, key, parent, pre_sib)) ||
241
!(cn->v = dm_config_create_value(cft)))
244
cn->v->type = DM_CFG_INT;
250
171
static void filter_metadata(struct dm_config_node *vg) {
251
172
struct dm_config_node *pv = pvs(vg);
394
317
const char *pvid = daemon_request_str(r, "uuid", NULL);
395
318
int64_t devt = daemon_request_int(r, "device", 0);
396
response res = { .buffer = NULL };
397
320
struct dm_config_node *pv;
322
buffer_init( &res.buffer );
399
324
if (!pvid && !devt)
400
return daemon_reply_simple("failed", "reason = %s", "need PVID or device", NULL);
325
return reply_fail("need PVID or device");
402
327
if (!(res.cft = dm_config_create()))
403
return daemon_reply_simple("failed", "reason = %s", "out of memory", NULL);
328
return reply_fail("out of memory");
405
330
if (!(res.cft->root = make_text_node(res.cft, "response", "OK", NULL, NULL)))
406
return daemon_reply_simple("failed", "reason = %s", "out of memory", NULL);
331
return reply_fail("out of memory");
408
333
lock_pvid_to_pvmeta(s);
409
334
if (!pvid && devt)
410
335
pvid = dm_hash_lookup_binary(s->device_to_pvid, &devt, sizeof(devt));
413
debug("pv_lookup: could not find device %" PRIu64 "\n", devt);
338
WARN(s, "pv_lookup: could not find device %" PRIu64, devt);
414
339
unlock_pvid_to_pvmeta(s);
415
340
dm_config_destroy(res.cft);
416
return daemon_reply_simple("unknown", "reason = %s", "device not found", NULL);
341
return reply_unknown("device not found");
419
344
pv = make_pv_node(s, pvid, res.cft, NULL, res.cft->root);
421
346
unlock_pvid_to_pvmeta(s);
422
347
dm_config_destroy(res.cft);
423
return daemon_reply_simple("unknown", "reason = %s", "PV not found", NULL);
348
return reply_unknown("PV not found");
426
351
pv->key = "physical_volume";
751
692
filter_metadata(metadata); /* sanitize */
753
701
if (seq == haveseq) {
755
703
if (compare_config(metadata, old->root))
757
debug("Not updating metadata for %s at %d (%s)\n", _vgid, haveseq,
705
DEBUGLOG(s, "Not updating metadata for %s at %d (%s)", _vgid, haveseq,
758
706
retval ? "ok" : "MISMATCH");
760
debug_cft("OLD: ", old->root);
761
debug_cft("NEW: ", metadata);
708
DEBUGLOG_cft(s, "OLD: ", old->root);
709
DEBUGLOG_cft(s, "NEW: ", metadata);
766
714
if (seq < haveseq) {
767
debug("Refusing to update metadata for %s at %d to %d\n", _vgid, haveseq, seq);
715
DEBUGLOG(s, "Refusing to update metadata for %s (at %d) to %d", _vgid, haveseq, seq);
768
716
/* TODO: notify the client that their metadata is out of date? */
773
721
if (!(cft = dm_config_create()) ||
774
722
!(cft->root = dm_config_clone_node(cft, metadata, 0))) {
775
debug("Out of memory\n");
723
ERROR(s, "Out of memory");
779
727
vgid = dm_config_find_str(cft->root, "metadata/id", NULL);
781
729
if (!vgid || !name) {
782
debug("Name '%s' or uuid '%s' missing!\n", name, vgid);
730
DEBUGLOG(s, "Name '%s' or uuid '%s' missing!", name, vgid);
786
734
lock_pvid_to_vgid(s);
788
736
if (haveseq >= 0 && haveseq < seq) {
789
debug("Updating metadata for %s at %d to %d\n", _vgid, haveseq, seq);
737
INFO(s, "Updating metadata for %s at %d to %d", _vgid, haveseq, seq);
790
738
/* temporarily orphan all of our PVs */
791
739
remove_metadata(s, vgid, 1);
794
742
lock_vgid_to_metadata(s);
795
debug("Mapping %s to %s\n", vgid, name);
743
DEBUGLOG(s, "Mapping %s to %s", vgid, name);
797
745
retval = ((cfgname = dm_pool_strdup(dm_config_memory(cft), name)) &&
798
746
dm_hash_insert(s->vgid_to_metadata, vgid, cft) &&
814
765
const char *pvid = daemon_request_str(r, "uuid", NULL);
815
766
int64_t device = daemon_request_int(r, "device", 0);
816
767
struct dm_config_tree *pvmeta;
818
debug("pv_gone: %s / %" PRIu64 "\n", pvid, device);
770
DEBUGLOG(s, "pv_gone: %s / %" PRIu64, pvid, device);
820
772
lock_pvid_to_pvmeta(s);
821
773
if (!pvid && device > 0)
822
774
pvid = dm_hash_lookup_binary(s->device_to_pvid, &device, sizeof(device));
824
776
unlock_pvid_to_pvmeta(s);
825
return daemon_reply_simple("unknown", "reason = %s", "device not in cache", NULL);
777
return reply_unknown("device not in cache");
828
debug("pv_gone (updated): %s / %" PRIu64 "\n", pvid, device);
780
DEBUGLOG(s, "pv_gone (updated): %s / %" PRIu64, pvid, device);
830
782
pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, pvid);
783
pvid_old = dm_hash_lookup_binary(s->device_to_pvid, &device, sizeof(device));
831
784
dm_hash_remove_binary(s->device_to_pvid, &device, sizeof(device));
832
785
dm_hash_remove(s->pvid_to_pvmeta, pvid);
833
786
vg_remove_if_missing(s, dm_hash_lookup(s->pvid_to_vgid, pvid));
834
787
unlock_pvid_to_pvmeta(s);
837
793
dm_config_destroy(pvmeta);
838
794
return daemon_reply_simple("OK", NULL);
840
return daemon_reply_simple("unknown", "reason = %s", "PVID does not exist", NULL);
796
return reply_unknown("PVID does not exist");
799
static response pv_clear_all(lvmetad_state *s, request r)
801
DEBUGLOG(s, "pv_clear_all");
803
lock_pvid_to_pvmeta(s);
804
lock_vgid_to_metadata(s);
805
lock_pvid_to_vgid(s);
807
destroy_metadata_hashes(s);
808
create_metadata_hashes(s);
810
unlock_pvid_to_vgid(s);
811
unlock_vgid_to_metadata(s);
812
unlock_pvid_to_pvmeta(s);
814
return daemon_reply_simple("OK", NULL);
843
817
static response pv_found(lvmetad_state *s, request r)
848
822
const char *vgid = daemon_request_str(r, "metadata/id", NULL);
849
823
struct dm_config_node *pvmeta = dm_config_find_node(r.cft->root, "pvmeta");
851
struct dm_config_tree *cft, *pvmeta_old = NULL;
825
struct dm_config_tree *cft, *pvmeta_old_dev = NULL, *pvmeta_old_pvid = NULL;
853
827
const char *pvid_dup;
854
828
int complete = 0, orphan = 0;
829
int64_t seqno = -1, seqno_old = -1;
857
return daemon_reply_simple("failed", "reason = %s", "need PV UUID", NULL);
832
return reply_fail("need PV UUID");
859
return daemon_reply_simple("failed", "reason = %s", "need PV metadata", NULL);
834
return reply_fail("need PV metadata");
861
836
if (!dm_config_get_uint64(pvmeta, "pvmeta/device", &device))
862
return daemon_reply_simple("failed", "reason = %s", "need PV device number", NULL);
864
debug("pv_found %s, vgid = %s, device = %" PRIu64 "\n", pvid, vgid, device);
837
return reply_fail("need PV device number");
866
839
lock_pvid_to_pvmeta(s);
868
841
if ((old = dm_hash_lookup_binary(s->device_to_pvid, &device, sizeof(device)))) {
869
pvmeta_old = dm_hash_lookup(s->pvid_to_pvmeta, old);
842
pvmeta_old_dev = dm_hash_lookup(s->pvid_to_pvmeta, old);
870
843
dm_hash_remove(s->pvid_to_pvmeta, old);
845
pvmeta_old_pvid = dm_hash_lookup(s->pvid_to_pvmeta, pvid);
847
DEBUGLOG(s, "pv_found %s, vgid = %s, device = %" PRIu64 ", old = %s", pvid, vgid, device, old);
873
851
if (!(cft = dm_config_create()) ||
874
852
!(cft->root = dm_config_clone_node(cft, pvmeta, 0))) {
875
853
unlock_pvid_to_pvmeta(s);
876
return daemon_reply_simple("failed", "reason = %s", "out of memory", NULL);
854
return reply_fail("out of memory");
879
pvid_dup = dm_config_find_str(cft->root, "pvmeta/id", NULL);
857
pvid_dup = dm_strdup(pvid);
880
858
if (!dm_hash_insert(s->pvid_to_pvmeta, pvid, cft) ||
881
859
!dm_hash_insert_binary(s->device_to_pvid, &device, sizeof(device), (void*)pvid_dup)) {
882
860
unlock_pvid_to_pvmeta(s);
883
return daemon_reply_simple("failed", "reason = %s", "out of memory", NULL);
861
return reply_fail("out of memory");
886
dm_config_destroy(pvmeta_old);
864
dm_config_destroy(pvmeta_old_pvid);
865
if (pvmeta_old_dev && pvmeta_old_dev != pvmeta_old_pvid)
866
dm_config_destroy(pvmeta_old_dev);
888
868
unlock_pvid_to_pvmeta(s);
892
return daemon_reply_simple("failed", "reason = %s", "need VG UUID", NULL);
893
debug("obtained vgid = %s, vgname = %s\n", vgid, vgname);
872
return reply_fail("need VG UUID");
873
DEBUGLOG(s, "obtained vgid = %s, vgname = %s", vgid, vgname);
895
return daemon_reply_simple("failed", "reason = %s", "need VG name", NULL);
875
return reply_fail("need VG name");
896
876
if (daemon_request_int(r, "metadata/seqno", -1) < 0)
897
return daemon_reply_simple("failed", "reason = %s", "need VG seqno", NULL);
877
return reply_fail("need VG seqno");
899
if (!update_metadata(s, vgname, vgid, metadata))
900
return daemon_reply_simple("failed", "reason = %s",
901
"metadata update failed", NULL);
879
if (!update_metadata(s, vgname, vgid, metadata, &seqno_old))
880
return reply_fail("metadata update failed");
903
882
lock_pvid_to_vgid(s);
904
883
vgid = dm_hash_lookup(s->pvid_to_vgid, pvid);
909
if ((cft = lock_vg(s, vgid)))
888
if ((cft = lock_vg(s, vgid))) {
910
889
complete = update_pv_status(s, cft, cft->root, 0);
911
else if (!strcmp(vgid, "#orphan"))
890
seqno = dm_config_find_int(cft->root, "metadata/seqno", -1);
891
} else if (!strcmp(vgid, "#orphan"))
914
894
unlock_vg(s, vgid);
915
return daemon_reply_simple("failed", "reason = %s",
916
// FIXME provide meaningful-to-user error message
917
"internal treason!", NULL);
895
return reply_fail("non-orphan VG without metadata encountered");
919
897
unlock_vg(s, vgid);
933
913
const char *vgname = daemon_request_str(r, "vgname", NULL);
936
return daemon_reply_simple("failed", "reason = %s", "need VG UUID", NULL);
916
return reply_fail("need VG UUID");
938
return daemon_reply_simple("failed", "reason = %s", "need VG name", NULL);
918
return reply_fail("need VG name");
939
919
if (daemon_request_int(r, "metadata/seqno", -1) < 0)
940
return daemon_reply_simple("failed", "reason = %s", "need VG seqno", NULL);
920
return reply_fail("need VG seqno");
942
922
/* TODO defer metadata update here; add a separate vg_commit
943
923
* call; if client does not commit, die */
944
if (!update_metadata(s, vgname, vgid, metadata))
945
return daemon_reply_simple("failed", "reason = %s",
946
"metadata update failed", NULL);
924
if (!update_metadata(s, vgname, vgid, metadata, NULL))
925
return reply_fail("metadata update failed");
948
927
return daemon_reply_simple("OK", NULL);
964
943
return daemon_reply_simple("OK", NULL);
946
static void _dump_cft(struct buffer *buf, struct dm_hash_table *ht, const char *key_addr)
948
struct dm_hash_node *n = dm_hash_get_first(ht);
950
struct dm_config_tree *cft = dm_hash_get_data(ht, n);
951
const char *key_backup = cft->root->key;
952
cft->root->key = dm_config_find_str(cft->root, key_addr, "unknown");
953
dm_config_write_node(cft->root, buffer_line, buf);
954
cft->root->key = key_backup;
955
n = dm_hash_get_next(ht, n);
959
static void _dump_pairs(struct buffer *buf, struct dm_hash_table *ht, const char *name, int int_key)
962
struct dm_hash_node *n = dm_hash_get_first(ht);
964
buffer_append(buf, name);
965
buffer_append(buf, " {\n");
968
const char *key = dm_hash_get_key(ht, n),
969
*val = dm_hash_get_data(ht, n);
970
buffer_append(buf, " ");
972
dm_asprintf(&append, "%d = \"%s\"", *(int*)key, val);
974
dm_asprintf(&append, "%s = \"%s\"", key, val);
976
buffer_append(buf, append);
977
buffer_append(buf, "\n");
979
n = dm_hash_get_next(ht, n);
981
buffer_append(buf, "}\n");
984
static response dump(lvmetad_state *s)
987
struct buffer *b = &res.buffer;
991
/* Lock everything so that we get a consistent dump. */
993
lock_vgid_to_metadata(s);
994
lock_pvid_to_pvmeta(s);
995
lock_pvid_to_vgid(s);
997
buffer_append(b, "# VG METADATA\n\n");
998
_dump_cft(b, s->vgid_to_metadata, "metadata/id");
1000
buffer_append(b, "\n# PV METADATA\n\n");
1001
_dump_cft(b, s->pvid_to_pvmeta, "pvmeta/id");
1003
buffer_append(b, "\n# VGID to VGNAME mapping\n\n");
1004
_dump_pairs(b, s->vgid_to_vgname, "vgid_to_vgname", 0);
1006
buffer_append(b, "\n# VGNAME to VGID mapping\n\n");
1007
_dump_pairs(b, s->vgname_to_vgid, "vgname_to_vgid", 0);
1009
buffer_append(b, "\n# PVID to VGID mapping\n\n");
1010
_dump_pairs(b, s->pvid_to_vgid, "pvid_to_vgid", 0);
1012
buffer_append(b, "\n# DEVICE to PVID mapping\n\n");
1013
_dump_pairs(b, s->device_to_pvid, "device_to_pvid", 1);
1015
unlock_pvid_to_vgid(s);
1016
unlock_pvid_to_pvmeta(s);
1017
unlock_vgid_to_metadata(s);
967
1022
static response handler(daemon_state s, client_handle h, request r)
969
1024
lvmetad_state *state = s.private;
970
1025
const char *rq = daemon_request_str(r, "request", "NONE");
1026
const char *token = daemon_request_str(r, "token", "NONE");
1028
pthread_mutex_lock(&state->token_lock);
1029
if (!strcmp(rq, "token_update")) {
1030
strncpy(state->token, token, 128);
1031
state->token[127] = 0;
1032
pthread_mutex_unlock(&state->token_lock);
1033
return daemon_reply_simple("OK", NULL);
1036
if (strcmp(token, state->token) && strcmp(rq, "dump")) {
1037
pthread_mutex_unlock(&state->token_lock);
1038
return daemon_reply_simple("token_mismatch",
1039
"expected = %s", state->token,
1040
"received = %s", token,
1041
"reason = %s", "token mismatch", NULL);
1043
pthread_mutex_unlock(&state->token_lock);
973
1046
* TODO Add a stats call, with transaction count/rate, time since last
991
1067
if (!strcmp(rq, "vg_lookup"))
992
1068
return vg_lookup(state, r);
994
if (!strcmp(rq, "pv_list")) {
1070
if (!strcmp(rq, "pv_list"))
995
1071
return pv_list(state, r);
998
1073
if (!strcmp(rq, "vg_list"))
999
1074
return vg_list(state, r);
1001
return daemon_reply_simple("failed", "reason = %s", "no such request", NULL);
1076
if (!strcmp(rq, "dump"))
1079
return reply_fail("request not implemented");
1004
1082
static int init(daemon_state *s)
1006
1084
pthread_mutexattr_t rec;
1007
1085
lvmetad_state *ls = s->private;
1009
ls->pvid_to_pvmeta = dm_hash_create(32);
1010
ls->device_to_pvid = dm_hash_create(32);
1011
ls->vgid_to_metadata = dm_hash_create(32);
1012
ls->vgid_to_vgname = dm_hash_create(32);
1013
ls->pvid_to_vgid = dm_hash_create(32);
1014
ls->vgname_to_vgid = dm_hash_create(32);
1015
ls->lock.vg = dm_hash_create(32);
1016
1088
pthread_mutexattr_init(&rec);
1017
1089
pthread_mutexattr_settype(&rec, PTHREAD_MUTEX_RECURSIVE_NP);
1018
1090
pthread_mutex_init(&ls->lock.pvid_to_pvmeta, &rec);
1019
1091
pthread_mutex_init(&ls->lock.vgid_to_metadata, &rec);
1020
1092
pthread_mutex_init(&ls->lock.pvid_to_vgid, NULL);
1022
debug("initialised state: vgid_to_metadata = %p\n", ls->vgid_to_metadata);
1093
pthread_mutex_init(&ls->lock.vg_lock_map, NULL);
1094
pthread_mutex_init(&ls->token_lock, NULL);
1095
create_metadata_hashes(ls);
1097
ls->lock.vg = dm_hash_create(32);
1100
/* Set up stderr logging depending on the -l option. */
1101
if (!daemon_log_parse(ls->log, DAEMON_LOG_OUTLET_STDERR, ls->log_config, 1))
1104
DEBUGLOG(s, "initialised state: vgid_to_metadata = %p", ls->vgid_to_metadata);
1023
1105
if (!ls->pvid_to_vgid || !ls->vgid_to_metadata)
1056
1131
dm_hash_destroy(ls->lock.vg);
1057
dm_hash_destroy(ls->pvid_to_pvmeta);
1058
dm_hash_destroy(ls->device_to_pvid);
1059
dm_hash_destroy(ls->vgid_to_metadata);
1060
dm_hash_destroy(ls->vgid_to_vgname);
1061
dm_hash_destroy(ls->vgname_to_vgid);
1062
dm_hash_destroy(ls->pvid_to_vgid);
1066
1135
static void usage(char *prog, FILE *file)
1068
1137
fprintf(file, "Usage:\n"
1069
"%s [-V] [-h] [-d] [-d] [-d] [-f]\n\n"
1138
"%s [-V] [-h] [-f] [-l {all|wire|debug}] [-s path]\n\n"
1070
1139
" -V Show version of lvmetad\n"
1071
1140
" -h Show this help information\n"
1072
" -d Log debug messages to syslog (-d, -dd, -ddd)\n"
1073
" -R Replace a running lvmetad instance, loading its data\n"
1074
" -f Don't fork, run in the foreground\n\n", prog);
1141
" -f Don't fork, run in the foreground\n"
1142
" -l Logging message level (-l {all|wire|debug})\n"
1143
" -s Set path to the socket to listen on\n\n", prog);
1077
1146
int main(int argc, char *argv[])