summaryrefslogtreecommitdiff
path: root/src/dotd.c
blob: b09b336b5c3563b37e34b6ba14a0366036e33434 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
 *
 * Copyright 2007 Google Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty 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.
 */

#include <config.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "distcc.h"
#include "exitcode.h"
#include "dotd.h"
#include "snprintf.h"

/* The dotd file is compiler generated,
 * so it should not have lines with more than
 * twice the length of the maximum path length.
 */
#define MAX_DOTD_LINE_LEN (MAXPATHLEN * 2)

/* Replaces the first occurrence of needle in haystack with
   new_needle. haystack must be of at least hay_size,
   and hay_size must be large enough to hold the string
   after the replacement.
   new_needle should not overlap with the haystack.
   Returns 0 if all goes well, 1 otherwise.
*/
static int dcc_strgraft(char *haystack, size_t hay_size,
                        const char *needle, const char *new_needle)
{
    char *found;
    size_t needle_len = 0;
    size_t new_needle_len = 0;

    found = strstr(haystack, needle);
    if (found == NULL)
        return 0;

    needle_len = strlen(needle);
    new_needle_len = strlen(new_needle);

    if (strlen(haystack) - needle_len + new_needle_len + 1 > hay_size)
        return 1;

    /* make some room in the haystack for the new needle */
    memmove(found + new_needle_len,
            found + needle_len,
            strlen(found + needle_len) + 1);
    memcpy(found, new_needle, new_needle_len);

    return 0;
}

/* Given the name of a dotd file, and the name of the directory
 * masquerading as root, write a @p new_dotd file that
 * contains everything in dotd, but with the "root" directory removed.
 * It will also substitute client_out_name for server_out_name,
 * rewriting the dependency target.
 */
int dcc_cleanup_dotd(const char *dotd_fname,
                     char **new_dotd_fname,
                     const char *root_dir,
                     const char *client_out_name,
                     const char *server_out_name)
{
    /* When we do the substitution of server-side output name to
     * client-side output name, we may end up with a line that
     * longer than the longest line we expect from the compiler.
     */
    char buf[2 * MAX_DOTD_LINE_LEN];

    FILE *dotd, *tmp_dotd;
    char *found;
    int ret;

    dotd = fopen(dotd_fname, "r");
    if (dotd == NULL) {
        return 1;
    }
    ret = dcc_make_tmpnam(dcc_find_basename(dotd_fname),
                          ".d", new_dotd_fname);

    if (ret) {
        fclose(dotd);
        return ret;
    }

    tmp_dotd = fopen(*new_dotd_fname, "w");
    if ((tmp_dotd == NULL)) {
        fclose(dotd);
        return 1;
    }

    while (fgets(buf, MAX_DOTD_LINE_LEN, dotd)) {
        if ((strchr(buf, '\n') == NULL) && !feof(dotd)) {
            /* Line length must have exceeded MAX_DOTD_LINE_LEN: bail out. */
            fclose(dotd);
            fclose(tmp_dotd);
            return 1;
        }

        /* First, the dependency target substitution */
        if (dcc_strgraft(buf, sizeof(buf),
                         server_out_name, client_out_name)) {
            fclose(dotd);
            fclose(tmp_dotd);
            return 1;
        }

        /* Second, the trimming of the "root" directory" */
        found = strstr(buf, root_dir);
        while (found) {
            char *rest_of_buf = found + strlen(root_dir);
            memmove(found, rest_of_buf, strlen(rest_of_buf) + 1);
            found = strstr(found, root_dir);
        }
        if (fprintf(tmp_dotd, "%s", buf) < 0) {
            fclose(dotd);
            fclose(tmp_dotd);
            return 1;
        }
    }
    if (ferror(dotd) || ferror(tmp_dotd)) {
       return 1;
    }
    fclose(dotd);
    if (fclose(tmp_dotd) < 0) {
        return 1;
    }
    return 0;
}

/* Go through arguments (in @p argv), and relevant environment variables, and
 * find out where the dependencies output should go.  Return that location in a
 * newly allocated string in @p dotd_fname.  @p needs_dotd is set to true if the
 * compilation command line and environent imply that a .d file must be
 * produced.  @p sets_dotd_target is set to true if there is a -MQ or -MT
 * option.  This is to be used on the client, so that the client knows where to
 * put the .d file it gets from the server. @p dotd_target is set only if
 * @needs_dotd is true and @sets_dotd_target is false and the target is given in
 * the DEPENDENCIES_OUTPUT environment variable.
 *
 * Note: -M is not handled here, because this option implies -E.
 *
 * TODO(manos): it does not support SUNPRO_DEPENDENCIES.
 */
int dcc_get_dotd_info(char **argv, char **dotd_fname,
                      int *needs_dotd, int *sets_dotd_target,
                      char **dotd_target)
{
    char *deps_output = 0;

    char *input_file;
    char *output_file;
    char **new_args;  /* will throw this away */
    int has_dash_o = 0;
    char *env_var = 0;
    int ret;
    int i;
    char *a;

    *needs_dotd = 0;
    *sets_dotd_target = 0;
    *dotd_target = NULL;

    env_var = getenv("DEPENDENCIES_OUTPUT");

    if (env_var != NULL) {
        *needs_dotd = 1;
    }

    for (i = 0; (a = argv[i]); i++) {
        if (strcmp(a, "-MT") == 0) {
            *sets_dotd_target = 1;
            ++i;
            continue;
        }
        if (strcmp(a, "-MQ") == 0) {
            *sets_dotd_target = 1;
            ++i;
            continue;
        }
        /* Catch-all for all -MD, -MMD, etc, options.
         * -MQ and -MT do not imply a deps file is expected.
         */
        if (strncmp(a, "-M", 2) == 0) {
            *needs_dotd = 1;
        }
        if (strcmp(a, "-MF") == 0) {
            ++i;
            deps_output = argv[i];
        } else if (strncmp(a, "-MF", 3) == 0) {
            deps_output = argv[i] + 3;
        } else if (strcmp(a, "-o") == 0) {
            has_dash_o = 1;
        }
    }

    /* TODO(csilvers): we also need to parse -Wp,-x,-y,-z, in the same
     * way we do gcc flags in the for loop above.  Note that the -Wp
     * flags are passed to cpp, with slightly different semantics than
     * gcc flags (eg -Wp,-MD takes a filename argument, while -MD does
     * not).
     */

    if (deps_output) {
        *dotd_fname = strdup(deps_output);
        if (*dotd_fname == NULL) {
            return EXIT_OUT_OF_MEMORY;
        } else {
            return 0;
        }
    }

    /* ok, so there is no explicit setting of the deps filename. */
    deps_output = env_var;
    if (deps_output) {
        char *space;
        *dotd_fname = strdup(deps_output);
        if (*dotd_fname == NULL) {
            return EXIT_OUT_OF_MEMORY;
        }
        space = strchr(*dotd_fname, ' ');
        if (space != NULL) {
            *space = '\0';
            *dotd_target = space + 1;
        }

        return 0;
    }

    /* and it's not set explicitly in the variable */

    { /* Call dcc_scan_args to find the input/output files in order to calculate
         a name for the .d file.*/

        char *extension;
        char *tmp_dotd_fname;
        ret = dcc_scan_args(argv, &input_file, &output_file, &new_args);
        /* if .o is set, just append .d.
         * otherwise, take the basename of the input, and set the suffix to .d */
        if (has_dash_o)
          tmp_dotd_fname = strdup(output_file);
        else
          tmp_dotd_fname = strdup(input_file);
        if (tmp_dotd_fname == NULL) return EXIT_OUT_OF_MEMORY;
        extension = dcc_find_extension(tmp_dotd_fname);
        /* Whether derived from input or output filename, we peel the extension
           off (if it exists). */
        if (extension) {
          /* dcc_find_extension guarantees that there is space for 'd'. */
          extension[1] = 'd';
          extension[2] = '\0';
          *dotd_fname = tmp_dotd_fname;
        }
        else { /* There is no extension (or name ends with a "."). */
          if (tmp_dotd_fname[strlen(tmp_dotd_fname) - 1] == '.')
            checked_asprintf(dotd_fname, "%s%s", tmp_dotd_fname, "d");
          else
            checked_asprintf(dotd_fname, "%s%s", tmp_dotd_fname, ".d");
          if (*dotd_fname == NULL) {
            return EXIT_OUT_OF_MEMORY;
          }
          free(tmp_dotd_fname);
        }
        return 0;
    }
}