1 #include "lib/mlrutil.h"
2 #include "lib/mlr_globals.h"
3 #include "cli/mlrcli.h"
4 #include "output/multi_lrec_writer.h"
5
6 // ----------------------------------------------------------------
multi_lrec_writer_alloc(cli_writer_opts_t * pwriter_opts)7 multi_lrec_writer_t* multi_lrec_writer_alloc(cli_writer_opts_t* pwriter_opts) {
8 multi_lrec_writer_t* pmlw = mlr_malloc_or_die(sizeof(multi_lrec_writer_t));
9 pmlw->pnames_to_lrec_writers_and_fps = lhmsv_alloc();
10 pmlw->pwriter_opts = pwriter_opts;
11 return pmlw;
12 }
13
14 // ----------------------------------------------------------------
multi_lrec_writer_free(multi_lrec_writer_t * pmlw,context_t * pctx)15 void multi_lrec_writer_free(multi_lrec_writer_t* pmlw, context_t* pctx) {
16 if (pmlw == NULL)
17 return;
18
19 for (lhmsve_t* pe = pmlw->pnames_to_lrec_writers_and_fps->phead; pe != NULL; pe = pe->pnext) {
20 lrec_writer_and_fp_t* pstate = pe->pvvalue;
21 pstate->plrec_writer->pfree_func(pstate->plrec_writer, pctx);
22 free(pstate->filename_or_command);
23 free(pstate);
24 }
25
26 lhmsv_free(pmlw->pnames_to_lrec_writers_and_fps);
27 free(pmlw);
28 }
29
30 // ----------------------------------------------------------------
multi_lrec_writer_output_srec(multi_lrec_writer_t * pmlw,lrec_t * poutrec,char * filename_or_command,file_output_mode_t file_output_mode,int flush_every_record,context_t * pctx)31 void multi_lrec_writer_output_srec(multi_lrec_writer_t* pmlw, lrec_t* poutrec, char* filename_or_command,
32 file_output_mode_t file_output_mode, int flush_every_record, context_t* pctx)
33 {
34 lrec_writer_and_fp_t* pstate = lhmsv_get(pmlw->pnames_to_lrec_writers_and_fps, filename_or_command);
35 if (pstate == NULL) {
36 pstate = mlr_malloc_or_die(sizeof(lrec_writer_and_fp_t));
37 pstate->plrec_writer = lrec_writer_alloc(pmlw->pwriter_opts);
38 MLR_INTERNAL_CODING_ERROR_IF(pstate->plrec_writer == NULL);
39 pstate->filename_or_command = mlr_strdup_or_die(filename_or_command);
40 char* mode_string = get_mode_string(file_output_mode);
41 char* mode_desc = get_mode_desc(file_output_mode);
42 if (file_output_mode == MODE_PIPE) {
43 pstate->is_popen = TRUE;
44 pstate->output_stream = popen(filename_or_command, mode_string);
45 if (pstate->output_stream == NULL) {
46 perror("popen");
47 fprintf(stderr, "%s: failed popen for %s on \"%s\".\n",
48 MLR_GLOBALS.bargv0, mode_desc, filename_or_command);
49 exit(1);
50 }
51 } else {
52 pstate->is_popen = FALSE;
53 pstate->output_stream = fopen(filename_or_command, mode_string);
54 if (pstate->output_stream == NULL) {
55 perror("fopen");
56 fprintf(stderr, "%s: failed fopen for %s on \"%s\".\n",
57 MLR_GLOBALS.bargv0, mode_desc, filename_or_command);
58 exit(1);
59 }
60 }
61
62 lhmsv_put(pmlw->pnames_to_lrec_writers_and_fps, mlr_strdup_or_die(filename_or_command), pstate, FREE_ENTRY_KEY);
63 }
64
65 pstate->plrec_writer->pprocess_func(pstate->plrec_writer->pvstate, pstate->output_stream, poutrec, pctx);
66
67 if (poutrec != NULL) {
68 if (flush_every_record)
69 fflush(pstate->output_stream);
70 } else {
71 if (pstate->is_popen) {
72 // Sadly, pclose returns an error even on well-formed commands. For example, if the popened
73 // command was "grep nonesuch" and the string "nonesuch" was not encountered, grep returns
74 // non-zero and popen flags it as an error. We cannot differentiate these from genuine
75 // failure cases so the best choice is to simply call pclose and ignore error codes.
76 // If a piped-to command does fail then it should have some output to stderr which the
77 // user can take advantage of.
78 (void)pclose(pstate->output_stream);
79 } else {
80 if (fclose(pstate->output_stream) != 0) {
81 perror("fclose");
82 fprintf(stderr, "%s: fclose error on \"%s\".\n", MLR_GLOBALS.bargv0, filename_or_command);
83 exit(1);
84 }
85 }
86 pstate->output_stream = NULL;
87 }
88 }
89
multi_lrec_writer_output_list(multi_lrec_writer_t * pmlw,sllv_t * poutrecs,char * filename_or_command,file_output_mode_t file_output_mode,int flush_every_record,context_t * pctx)90 void multi_lrec_writer_output_list(multi_lrec_writer_t* pmlw, sllv_t* poutrecs, char* filename_or_command,
91 file_output_mode_t file_output_mode, int flush_every_record, context_t* pctx)
92 {
93 if (poutrecs == NULL) // synonym for empty record-list
94 return;
95
96 while (poutrecs->phead) {
97 lrec_t* poutrec = sllv_pop(poutrecs);
98 multi_lrec_writer_output_srec(pmlw, poutrec, filename_or_command, file_output_mode,
99 flush_every_record, pctx);
100 }
101 }
102
multi_lrec_writer_drain(multi_lrec_writer_t * pmlw,context_t * pctx)103 void multi_lrec_writer_drain(multi_lrec_writer_t* pmlw, context_t* pctx) {
104 for (lhmsve_t* pe = pmlw->pnames_to_lrec_writers_and_fps->phead; pe != NULL; pe = pe->pnext) {
105 lrec_writer_and_fp_t* pstate = pe->pvvalue;
106 pstate->plrec_writer->pprocess_func(pstate->plrec_writer->pvstate, pstate->output_stream, NULL, pctx);
107 fflush(pstate->output_stream);
108 if (pstate->is_popen) {
109 // Sadly, pclose returns an error even on well-formed commands. For example, if the popened
110 // command was "grep nonesuch" and the string "nonesuch" was not encountered, grep returns
111 // non-zero and popen flags it as an error. We cannot differentiate these from genuine
112 // failure cases so the best choice is to simply call pclose and ignore error codes.
113 // If a piped-to command does fail then it should have some output to stderr which the
114 // user can take advantage of.
115 (void)pclose(pstate->output_stream);
116 } else {
117 if (fclose(pstate->output_stream) != 0) {
118 perror("fclose");
119 fprintf(stderr, "%s: fclose error on \"%s\".\n", MLR_GLOBALS.bargv0, pstate->filename_or_command);
120 exit(1);
121 }
122 }
123 }
124 }
125