1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
3/**
4 * @copyright 2013 Couchbase, Inc.
5 *
6 * @author Filipe Manana  <filipe@couchbase.com>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
9 * use this file except in compliance with the License. You may obtain a copy of
10 * the License at
11 *
12 *  http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 * License for the specific language governing permissions and limitations under
18 * the License.
19 **/
20
21#include "config.h"
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <errno.h>
26#include <libcouchstore/couch_db.h>
27#include "../file_merger.h"
28#include "../util.h"
29#include "util.h"
30
31#define LINE_BUF_SIZE (8 * 1024)
32#define MERGE_ERROR_CODE(Err) (100 + (Err))
33
34
35int main(int argc, char *argv[])
36{
37    char view_file_type;
38    int num_files;
39    int i, j;
40    char **view_files;
41    char dest_file[LINE_BUF_SIZE];
42    file_merger_error_t error;
43    cb_thread_t exit_thread;
44    int status = 0;
45
46    (void) argc;
47    (void) argv;
48
49    /*
50     * Disable buffering for stdout since file merger messages needs
51     * to be immediately available at erlang side
52     */
53    setvbuf(stdout, (char *) NULL, _IONBF, 0);
54
55    if (set_binary_mode() < 0) {
56        fprintf(stderr, "Error setting binary mode\n");
57        exit(EXIT_FAILURE);
58    }
59
60    if (fscanf(stdin, "%c\n", &view_file_type) != 1) {
61        fprintf(stderr, "Error reading view file type.\n");
62        exit(EXIT_FAILURE);
63    }
64    if (view_file_type != 'i' && view_file_type != 'v') {
65        fprintf(stderr, "View file type must be 'i' or 'v'.\n");
66        exit(EXIT_FAILURE);
67    }
68
69    if (fscanf(stdin, "%d\n", &num_files) != 1) {
70        fprintf(stderr, "Error reading number of files to merge.\n");
71        exit(EXIT_FAILURE);
72    }
73    if (num_files <= 0) {
74        fprintf(stderr, "Number of files to merge is negative or zero.\n");
75        exit(EXIT_FAILURE);
76    }
77
78    view_files = (char **) malloc(sizeof(char *) * num_files);
79    if (view_files == NULL) {
80        fprintf(stderr, "Memory allocation failure.\n");
81        exit(EXIT_FAILURE);
82    }
83
84    for (i = 0; i < num_files; ++i) {
85        view_files[i] = (char *) malloc(LINE_BUF_SIZE);
86        if (view_files[i] == NULL) {
87            for (j = 0; j < i; ++j) {
88                free(view_files[j]);
89            }
90            free(view_files);
91            fprintf(stderr, "Memory allocation failure.\n");
92            exit(EXIT_FAILURE);
93        }
94
95        if (couchstore_read_line(stdin, view_files[i], LINE_BUF_SIZE) != view_files[i]) {
96            for (j = 0; j <= i; ++j) {
97                free(view_files[j]);
98            }
99            free(view_files);
100            fprintf(stderr, "Error reading view file number %d.\n", (i + 1));
101            exit(EXIT_FAILURE);
102        }
103    }
104
105    if (couchstore_read_line(stdin, dest_file, LINE_BUF_SIZE) != dest_file) {
106        fprintf(stderr, "Error reading destination file name.\n");
107        status = 1;
108        goto finished;
109    }
110
111    status = start_exit_listener(&exit_thread);
112    if (status) {
113        fprintf(stderr, "Error starting stdin exit listener thread\n");
114        goto finished;
115    }
116
117    if (num_files > 1) {
118        const char **src_files = (const char **) view_files;
119        switch (view_file_type) {
120        case 'i':
121            error = merge_view_ids_ops_files(src_files, num_files, dest_file);
122            break;
123        case 'v':
124            error = merge_view_kvs_ops_files(src_files, num_files, dest_file);
125            break;
126        default:
127            fprintf(stderr, "Unknown view file type: %c\n", view_file_type);
128            status = 1;
129            goto finished;
130        }
131
132        if (error == FILE_MERGER_SUCCESS) {
133            for (i = 0; i < num_files; ++i) {
134                /* Ignore failures.
135                   Erlang side will eventually delete the files later. */
136                remove(view_files[i]);
137            }
138        } else {
139            status = MERGE_ERROR_CODE(error);
140            goto finished;
141        }
142    } else {
143        if (rename(view_files[0], dest_file) != 0) {
144            fprintf(stderr, "Error renaming file %s to %s: %s\n",
145                    view_files[0], dest_file, strerror(errno));
146            status = 1;
147            goto finished;
148        }
149    }
150
151 finished:
152    for (i = 0; i < num_files; ++i) {
153        free(view_files[i]);
154    }
155    free(view_files);
156
157    return status;
158}
159