aboutsummaryrefslogtreecommitdiff
path: root/bpkg/checksum.cxx
blob: efc86ddc24e2b66199f5932959a530d0e1e953e1 (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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
// file      : bpkg/checksum.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <bpkg/checksum.hxx>

#ifdef _WIN32
#  include <algorithm> // replace()
#endif

#include <bpkg/diagnostics.hxx>

using namespace std;

namespace bpkg
{
  // sha256
  //
  static bool
  check_sha256 (const path& prog)
  {
    // This one doesn't have --version or --help. Running it without any
    // arguments causes it to calculate the sum of stdin. But we can ask
    // it to calculate a sum of an empty string.
    //
    const char* args[] = {prog.string ().c_str (), "-q", "-s", "", nullptr};

    try
    {
      process_path pp (process::path_search (args[0]));

      if (verb >= 3)
        print_process (args);

      process pr (pp, args, 0, -1, 1); // Redirect stdout and stderr to a pipe.

      try
      {
        ifdstream is (move (pr.in_ofd));

        string l;
        getline (is, l);
        is.close ();

        return
          pr.wait () &&
          l ==
            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
      }
      catch (const io_error&)
      {
        // Fall through.
      }
    }
    catch (const process_error& e)
    {
      if (e.child)
        exit (1);

      // Fall through.
    }

    return false;
  }

  static process
  start_sha256 (const path& prog, const strings& ops, const path& file)
  {
    cstrings args {prog.string ().c_str (), "-q"};

    for (const string& o: ops)
      args.push_back (o.c_str ());

    args.push_back (file.string ().c_str ());
    args.push_back (nullptr);

    process_path pp (process::path_search (args[0]));

    if (verb >= 3)
      print_process (args);

    // Pipe stdout. Process exceptions must be handled by the caller.
    //
    return process (pp, args.data (), 0, -1);
  }

  // sha256sum
  //
  static bool
  check_sha256sum (const path& prog)
  {
    // sha256sum --version prints the version to stdout and exits with 0
    // status. The first line starts with "sha256sum (GNU coreutils) 8.21".
    //
    const char* args[] = {prog.string ().c_str (), "--version", nullptr};

    try
    {
      process_path pp (process::path_search (args[0]));

      if (verb >= 3)
        print_process (args);

      process pr (pp, args, 0, -1); // Redirect stdout to a pipe.

      try
      {
        ifdstream is (move (pr.in_ofd), fdstream_mode::skip);

        string l;
        getline (is, l);
        is.close ();

        return pr.wait () && l.compare (0, 9, "sha256sum") == 0;
      }
      catch (const io_error&)
      {
        // Fall through.
      }
    }
    catch (const process_error& e)
    {
      if (e.child)
        exit (1);

      // Fall through.
    }

    return false;
  }

  static process
  start_sha256sum (const path& prog, const strings& ops, const path& file)
  {
    cstrings args {prog.string ().c_str (), "-b"};

    for (const string& o: ops)
      args.push_back (o.c_str ());

    // For some reason, MSYS2-based sha256sum utility prints stray backslash
    // character at the beginning of the sum if the path contains a
    // backslash. So we get rid of them.
    //
#ifndef _WIN32
    const string& f (file.string ());
#else
    string f (file.string ());
    replace (f.begin (), f.end (), '\\', '/');
#endif

    args.push_back (f.c_str ());
    args.push_back (nullptr);

    process_path pp (process::path_search (args[0]));

    if (verb >= 3)
      print_process (args);

    // Pipe stdout. Process exceptions must be handled by the caller.
    //
    return process (pp, args.data (), 0, -1);
  }

  // shasum
  //
  static bool
  check_shasum (const path& prog)
  {
    // shasum --version prints just the version to stdout and exits with 0
    // status. The output looks like "5.84".
    //
    const char* args[] = {prog.string ().c_str (), "--version", nullptr};

    try
    {
      process_path pp (process::path_search (args[0]));

      if (verb >= 3)
        print_process (args);

      process pr (pp, args, 0, -1); // Redirect stdout to a pipe.

      try
      {
        ifdstream is (move (pr.in_ofd));

        string l;
        getline (is, l);
        is.close ();

        return pr.wait () && l.size () != 0 && l[0] >= '0' && l[0] <= '9';
      }
      catch (const io_error&)
      {
        // Fall through.
      }
    }
    catch (const process_error& e)
    {
      if (e.child)
        exit (1);

      // Fall through.
    }

    return false;
  }

  static process
  start_shasum (const path& prog, const strings& ops, const path& file)
  {
    cstrings args {prog.string ().c_str (), "-a", "256", "-b"};

    for (const string& o: ops)
      args.push_back (o.c_str ());

    args.push_back (file.string ().c_str ());
    args.push_back (nullptr);

    process_path pp (process::path_search (args[0]));

    if (verb >= 3)
      print_process (args);

    // Pipe stdout. Process exceptions must be handled by the caller.
    //
    return process (pp, args.data (), 0, -1);
  }

  // The dispatcher.
  //
  // Cache the result of finding/testing the sha256 program. Sometimes
  // a simple global variable is really the right solution...
  //
  enum class kind {sha256, sha256sum, shasum};

  static path sha256_path;
  static kind sha256_kind;

  static kind
  check (const common_options& o)
  {
    if (!sha256_path.empty ())
      return sha256_kind; // Cached.

    if (o.sha256_specified ())
    {
      const path& p (sha256_path = o.sha256 ());

      // Figure out which one it is.
      //
      const path& n (p.leaf ());
      const string& s (n.string ());

      if (s.find ("sha256sum") != string::npos)
      {
        if (!check_sha256sum (p))
          fail << p << " does not appear to be the 'sha256sum' program";

        sha256_kind = kind::sha256sum;
      }
      else if (s.find ("shasum") != string::npos)
      {
        if (!check_shasum (p))
          fail << p << " does not appear to be the 'shasum' program";

        sha256_kind = kind::shasum;
      }
      else if (s.find ("sha256") != string::npos)
      {
        if (!check_sha256 (p))
          fail << p << " does not appear to be the 'sha256' program";

        sha256_kind = kind::sha256;
      }
      else
        fail << "unknown sha256 program " << p;
    }
    else
    {
      // See if any is available. The preference order is:
      //
      // sha256    (FreeBSD)
      // sha256sum (Linux coreutils)
      // shasum    (Perl tool, Mac OS)
      //
      if (check_sha256 (sha256_path = path ("sha256")))
      {
        sha256_kind = kind::sha256;
      }
      else if (check_sha256sum (sha256_path = path ("sha256sum")))
      {
        sha256_kind = kind::sha256sum;
      }
      else if (check_shasum (sha256_path = path ("shasum")))
      {
        sha256_kind = kind::shasum;
      }
      else
        fail << "unable to find 'sha256', 'sha256sum', or 'shasum'" <<
          info << "use --sha256 to specify the sha256 program location";

      if (verb >= 3)
        info << "using '" << sha256_path << "' as the sha256 program, "
             << "use --sha256 to override";
    }

    return sha256_kind;
  }

  static process
  start (const common_options& o, const path& f)
  {
    process (*sf) (const path&, const strings&, const path&) = nullptr;

    switch (check (o))
    {
    case kind::sha256:    sf = &start_sha256;    break;
    case kind::sha256sum: sf = &start_sha256sum; break;
    case kind::shasum:    sf = &start_shasum;    break;
    }

    try
    {
      return sf (sha256_path, o.sha256_option (), f);
    }
    catch (const process_error& e)
    {
      error << "unable to execute " << sha256_path << ": " << e;

      if (e.child)
        exit (1);

      throw failed ();
    }
  }

  string
  sha256 (const common_options& o, const path& f)
  {
    if (!exists (f))
      fail << "file " << f << " does not exist";

    process pr (start (o, f));

    try
    {
      ifdstream is (move (pr.in_ofd), fdstream_mode::skip);

      // All three tools output the sum as the first word.
      //
      string s;
      is >> s;
      is.close ();

      if (pr.wait ())
      {
        if (s.size () != 64)
          fail << "'" << s << "' doesn't appear to be a SHA256 sum" <<
            info << "produced by '" << sha256_path << "'; "
               << "use --sha256 to override";

        return s;
      }

      // Child exited with an error, fall through.
    }
    // Ignore these exceptions if the child process exited with an error status
    // since that's the source of the failure.
    //
    catch (const io_error&)
    {
      if (pr.wait ())
        fail << "unable to read '" << sha256_path << "' output";
    }

    // We should only get here if the child exited with an error status.
    //
    assert (!pr.wait ());

    // While it is reasonable to assuming the child process issued diagnostics,
    // issue something just in case.
    //
    fail << "unable to calculate SHA256 sum using '" << sha256_path << "'" <<
      info << "re-run with -v for more information" << endf;
  }
}