1 /* COPYING ******************************************************************
2 For copyright and licensing terms, see the file named COPYING.
3 // **************************************************************************
4 */
5
6 #include <string>
7 #include <list>
8 #include <memory>
9 #include <map>
10 #include <vector>
11 #include <iostream>
12 #include <iomanip>
13 #include <fstream>
14 #include <sstream>
15 #include <cassert>
16 #include <cctype>
17 #include <cstring>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <climits>
21 #include <cerrno>
22 #include <ctime>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <ftw.h>
28 #include "popt.h"
29 #include "CubeHash.h"
30 #include "redo.h"
31 #include "jobserver.h"
32 #include "lockfile.h"
33 #include "wait.h"
34 #include "FileDescriptorOwner.h"
35 #if defined(__unix__) || defined(__UNIX__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__INTERIX)
36 extern "C" char ** environ;
37 #endif
38
39 bool debug(false);
40
41 namespace {
42
43 struct catchall_definition : public popt::definition {
44 public:
catchall_definition__anon5955c6770111::catchall_definition45 catchall_definition() {}
execute__anon5955c6770111::catchall_definition46 virtual bool execute(popt::processor &, char) { return true; }
47 // We'll catch this later when it is re-tried as a short option on its own.
execute__anon5955c6770111::catchall_definition48 virtual bool execute(popt::processor &, char, const char *) { return false; }
execute__anon5955c6770111::catchall_definition49 virtual bool execute(popt::processor &, const char *) { return true; }
~catchall_definition__anon5955c6770111::catchall_definition50 virtual ~catchall_definition() {}
51 };
52
53 struct special_string_definition : public popt::string_definition {
54 public:
special_string_definition__anon5955c6770111::special_string_definition55 special_string_definition(char s, const char * l, const char * a, const char * d, const char * & v) : string_definition(s, l, a, d, v) {}
execute__anon5955c6770111::special_string_definition56 virtual bool execute(popt::processor & proc, const char * s)
57 {
58 if (const char * long_name = query_long_name()) {
59 const std::size_t len(std::strlen(long_name));
60 if (0 == std::memcmp(long_name, s, len) && '=' == s[len]) {
61 action(proc, s + len + 1);
62 return true;
63 }
64 }
65 return false;
66 }
67 };
68
69 enum { MAX_META_DEPTH = 1U };
70 bool keep_going(false);
71 bool silent(false);
72 bool verbose(false);
73 int redoparent_fd = -1;
74 std::string makelevel;
75
76 }
77
78 extern
79 std::ostream &
msg(const char * prog,const char * prefix)80 msg (
81 const char * prog,
82 const char * prefix
83 ) {
84 std::clog << prog;
85 if (!makelevel.empty())
86 std::clog << "[" << makelevel << "]";
87 return std::clog << ": " << prefix << ": ";
88 }
89
90 /* Filename manipulation ****************************************************
91 // **************************************************************************
92 */
93
94 static inline
95 const char *
extension(const char * s)96 extension (
97 const char * s
98 ) {
99 if (const char * dot = std::strchr(s, '.'))
100 return dot;
101 return std::strchr(s, '\0');
102 }
103
104 static inline
105 const char *
basename_of(const char * s)106 basename_of (
107 const char * s
108 ) {
109 if (const char * slash = std::strrchr(s, '/'))
110 s = slash + 1;
111 return s;
112 }
113
114 static inline
115 bool
is_root(const char * path)116 is_root(const char *path)
117 {
118 return ('/' == path[0]) && !path[1];
119 }
120
121 static inline
122 bool
is_dot_or_dotdot(const char * path)123 is_dot_or_dotdot(const char *path)
124 {
125 return !path[0] || (('.' == path[0]) && (!path[1] || (('.' == path[1]) && !path[2])));
126 }
127
128 static inline
129 bool
is_root_or_ends_with_dot_or_dotdot(const char * path)130 is_root_or_ends_with_dot_or_dotdot(const char *path)
131 {
132 return is_root(path) || is_dot_or_dotdot(basename_of(path));
133 }
134
135 /* Wrappers for POSIX API calls. ********************************************
136 // **************************************************************************
137 */
138
139 static
140 int
unlink_cb(const char * fpath,const struct stat *,int typeflag,struct FTW *)141 unlink_cb(const char *fpath, const struct stat *, int typeflag, struct FTW *)
142 {
143 switch (typeflag) {
144 case FTW_D:
145 case FTW_DP:
146 case FTW_DNR:
147 case FTW_F:
148 case FTW_SL:
149 return std::remove(fpath);
150 default:
151 return static_cast<void>(errno = EINVAL), -1;
152 }
153 }
154
155 static inline
156 int
rmrf(const char * path)157 rmrf(const char *path)
158 {
159 if (is_root_or_ends_with_dot_or_dotdot(path))
160 return static_cast<void>(errno = EINVAL), -1;
161 struct stat stbuf;
162 if (0 <= lstat(path, &stbuf) && S_ISDIR(stbuf.st_mode))
163 return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
164 else
165 return std::remove(path);
166 }
167
168 static inline
169 int
spawn(const char * prog,const char * argv[],const char * envv[])170 spawn (
171 const char * prog,
172 const char * argv[],
173 const char * envv[]
174 ) {
175 std::clog << std::flush;
176 int pid = fork();
177 if (0 != pid)
178 return pid;
179 execve(prog, const_cast<char **>(argv), const_cast<char **>(envv));
180 const int error(errno);
181 std::clog << prog << ": " << std::strerror(error) << '\n' << std::flush;
182 _exit(255);
183 }
184
185 static
186 void
makepath(const std::string & dir)187 makepath (
188 const std::string & dir
189 ) {
190 const char * arg(dir.c_str());
191 const char * b(basename_of(arg));
192 if (b != arg)
193 makepath(std::string(arg, static_cast<std::size_t>(b - 1 - arg)));
194 mkdir(arg, 0777);
195 }
196
197 static inline
198 bool
exists(const std::string & name)199 exists(
200 const std::string & name
201 ) {
202 return 0 <= access(name.c_str(), F_OK);
203 }
204
205 /* string manipulation ******************************************************
206 // **************************************************************************
207 */
208
209 static inline
210 int
x2d(char c)211 x2d (
212 char c
213 ) {
214 if (!std::isxdigit(c)) return -1;
215 if (std::isdigit(c)) return c - '0';
216 if (std::isupper(c)) return c - 'A' + 10;
217 if (std::islower(c)) return c - 'a' + 10;
218 return -1;
219 }
220
221 static inline
222 std::list<std::string>
split(const char * s,bool prefixdash)223 split(
224 const char * s,
225 bool prefixdash
226 ) {
227 std::list<std::string> l;
228 if (prefixdash && *s && std::isspace(*s)) prefixdash = false;
229 for (;;) {
230 while (!prefixdash && *s && std::isspace(*s)) ++s;
231 if (!*s) break;
232 std::string r(prefixdash ? "-" : "");
233 prefixdash = false;
234 bool quoted(false);
235 while (char c = *s) {
236 if ('\"' == c)
237 quoted = !quoted;
238 else if ('\\' == c) {
239 c = *++s;
240 if (c)
241 r += c;
242 else {
243 r += '\\';
244 break;
245 }
246 } else if (!quoted && std::isspace(c))
247 break;
248 else
249 r += c;
250 ++s;
251 }
252 l.push_back(r);
253 }
254 return l;
255 }
256
257 static
258 std::vector<const char *>
convert(const std::list<std::string> & s)259 convert (
260 const std::list<std::string> & s
261 ) {
262 std::vector<const char *> v;
263 for (std::list<std::string>::const_iterator i(s.begin()); s.end() != i; ++i)
264 v.push_back(i->c_str());
265 return v;
266 }
267
268 /* struct Information and the cache of directory entry information **********
269 // **************************************************************************
270 */
271
272 namespace {
273
274 struct Information {
275 enum { NOTHING, SPECIAL, DIRECTORY, FILE } type;
276 std::time_t last_written;
277 unsigned char hash[32]; // 256 bits
278 };
279
280 typedef std::map<std::string, Information> InfoMap;
281 InfoMap file_info_map;
282
283 }
284
285 static inline
286 Information
read_file_info(const std::string & name,const Information * old_info)287 read_file_info (
288 const std::string & name,
289 const Information * old_info
290 ) {
291 Information i;
292 struct stat stbuf;
293 if (0 > lstat(name.c_str(), &stbuf)) {
294 i.type = i.NOTHING;
295 i.last_written = -1;
296 for ( std::size_t j(0);j < sizeof i.hash/sizeof *i.hash; ++j)
297 i.hash[j] = 0;
298 } else {
299 i.last_written = stbuf.st_mtime;
300 if (S_ISREG(stbuf.st_mode)) {
301 i.type = i.FILE;
302 if (old_info && old_info->last_written == i.last_written) {
303 memmove(i.hash, old_info->hash, sizeof i.hash);
304 } else {
305 // This is Dan Bernstein's SHA-3-AHS256 proposal from 2010-11.
306 CubeHash h(16U, 16U, 32U, 32U, 8U * sizeof i.hash/sizeof *i.hash);
307 std::ifstream f(name.c_str(), std::ios::binary);
308 if (!f.fail()) {
309 char buf[4096];
310 for (;;) {
311 f.read(buf, sizeof buf);
312 h.Update(reinterpret_cast<unsigned char *>(buf), static_cast<std::size_t>(f.gcount()));
313 if (f.eof()) break;
314 }
315 }
316 h.Final();
317 for ( std::size_t j(0);j < sizeof i.hash/sizeof *i.hash; ++j)
318 i.hash[j] = h.hashval[j];
319 }
320 } else {
321 if (S_ISDIR(stbuf.st_mode))
322 i.type = i.DIRECTORY;
323 else
324 i.type = i.SPECIAL;
325 for ( std::size_t j(0);j < sizeof i.hash/sizeof *i.hash; ++j)
326 i.hash[j] = 0;
327 }
328 }
329 return i;
330 }
331
332 static
333 Information &
get_file_info(const std::string & name,const Information * old_info)334 get_file_info (
335 const std::string & name,
336 const Information * old_info
337 ) {
338 InfoMap::iterator f(file_info_map.find(name));
339 if (f == file_info_map.end())
340 f = file_info_map.insert(InfoMap::value_type(name, read_file_info(name, old_info))).first;
341 return f->second;
342 }
343
344 static inline
345 void
delete_file_info(const std::string & name)346 delete_file_info (
347 const std::string & name
348 ) {
349 file_info_map.erase(name);
350 }
351
352 /* Parsing and updating of the .redo database *******************************
353 // **************************************************************************
354 */
355
356 static inline
357 void
read_db_line(std::istream & s,Information & i,std::string & name)358 read_db_line (
359 std::istream & s,
360 Information & i,
361 std::string & name
362 ) {
363 switch (int c = s.get()) {
364 default:
365 case EOF:
366 case 'a':
367 i.type = i.NOTHING;
368 break;
369 case 's':
370 i.type = i.SPECIAL;
371 goto atime;
372 case 'd':
373 i.type = i.DIRECTORY;
374 goto atime;
375 case 'f':
376 i.type = i.FILE;
377 goto cksum;
378 cksum:
379 {
380 #if defined(__WATCOMC__)
381 s.setf(s.skipws);
382 s.ipfx(0);
383 #else
384 s >> std::skipws;
385 std::istream::sentry ipfx(s, /*noskipws=*/false);
386 #endif
387 for ( std::size_t j(0);j < sizeof i.hash/sizeof *i.hash; ++j) {
388 char two[2];
389 s.read(two, 2);
390 i.hash[j] = static_cast<unsigned char>(x2d(two[0]) << 4) + static_cast<unsigned char>(x2d(two[1]));
391 }
392 #if defined(__WATCOMC__)
393 s.isfx();
394 s.unsetf(s.skipws);
395 #else
396 s >> std::noskipws;
397 #endif
398 }
399 atime:
400 #if defined(__WATCOMC__)
401 s.setf(s.skipws);
402 #else
403 s >> std::skipws;
404 #endif
405 s >> std::hex >> i.last_written >> std::dec;
406 #if defined(__WATCOMC__)
407 s.unsetf(s.skipws);
408 #else
409 s >> std::noskipws;
410 #endif
411 break;
412 // FIXME: Delete this special case once we switch to the new .redo database filenames.
413 case '\n':
414 case '\t':
415 case '\f':
416 case '\b':
417 case '\v':
418 case '\r':
419 case ' ':
420 {
421 char md5sum[128/8];
422 i.type = i.FILE;
423 #if defined(__WATCOMC__)
424 s.setf(s.skipws);
425 #else
426 s >> std::skipws;
427 #endif
428 s >> name >> std::hex >> i.last_written >> std::dec;
429 for (std::size_t j(0); j < sizeof md5sum/sizeof *md5sum; ++j) {
430 char two[2];
431 s.read(two, 2);
432 md5sum[j] = static_cast<char>(x2d(two[0]) << 4) + static_cast<char>(x2d(two[1]));
433 }
434 #if defined(__WATCOMC__)
435 s.unsetf(s.skipws);
436 #else
437 s >> std::noskipws;
438 #endif
439 return;
440 }
441 }
442 while (!s.eof() && std::isspace(s.peek()))
443 s.get();
444 char namebuf[PATH_MAX];
445 s.getline(namebuf, sizeof namebuf);
446 name = namebuf;
447 }
448
449 static inline
450 void
write_db_line(std::ostream & s,const Information & info,const char * name)451 write_db_line (
452 std::ostream & s,
453 const Information & info,
454 const char * name
455 ) {
456 switch (info.type) {
457 case Information::NOTHING: s.put('a') << name << '\n'; break;
458 case Information::SPECIAL: s.put('s') << std::hex << info.last_written << ' ' << std::dec << name << '\n'; break;
459 case Information::DIRECTORY: s.put('d') << std::hex << info.last_written << ' ' << std::dec << name << '\n'; break;
460 case Information::FILE:
461 s.put('f') << std::hex << std::setfill('0');
462 for ( std::size_t j(0);j < (sizeof info.hash/sizeof *info.hash); ++j)
463 s << std::setw(2) << static_cast<unsigned int>(info.hash[j]);
464 s << ' ' << info.last_written << ' ' << std::setfill(' ') << std::dec << name << '\n';
465 break;
466 }
467 }
468
469 static inline
470 void
puthash(std::ostream & s,const Information & info)471 puthash (
472 std::ostream & s,
473 const Information & info
474 ) {
475 s << std::hex << std::setfill('0');
476 for ( std::size_t j(0);j < sizeof info.hash/sizeof *info.hash; ++j)
477 s << std::setw(2) << static_cast<unsigned int>(info.hash[j]);
478 s << std::setfill(' ') << std::dec;
479 }
480
481 /* Redo internals ***********************************************************
482 // **************************************************************************
483 */
484
485 static inline
486 bool
record_prerequisites(const char * prog,const std::vector<const char * > & filev)487 record_prerequisites (
488 const char * prog,
489 const std::vector<const char *> & filev
490 ) {
491 if (-1 == redoparent_fd) {
492 msg(prog, "ERROR") << "Not invoked within a .do script.\n";
493 return false;
494 }
495 if (debug) {
496 msg(prog, "INFO") << "RECORD-PREREQUISITES: >";
497 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
498 const char * arg(*i);
499 std::clog << ' ' << arg;
500 }
501 std::clog << '<' << std::endl;
502 }
503
504 std::ostringstream tmp_db;
505 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
506 const char * arg(*i);
507 const Information & info(get_file_info(arg, nullptr));
508 write_db_line(tmp_db, info, arg);
509 if (tmp_db.fail()) {
510 int error = errno;
511 msg(prog, "ERROR") << std::strerror(error) << "\n";
512 return false;
513 }
514 }
515 const std::string & s(tmp_db.str());
516 if (0 > write(redoparent_fd, s.c_str(), s.length())) {
517 int error = errno;
518 msg(prog, "ERROR") << std::strerror(error) << "\n";
519 return false;
520 }
521
522 return true;
523 }
524
525 static inline bool redo (bool, const char * prog, unsigned meta_depth, const std::vector<const char *> & filev);
526
527 static
528 bool
redo_ifcreate(const char * prog,const std::vector<const char * > & filev)529 redo_ifcreate (
530 const char * prog,
531 const std::vector<const char *> & filev
532 ) {
533 bool status(true);
534 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
535 const char * arg(*i);
536 if (0 <= access(arg, F_OK)) {
537 msg(prog, "ERROR") << arg << ": File/directory exists.\n";
538 status = false;
539 continue;
540 }
541 }
542 if (!status) return status;
543
544 return record_prerequisites (prog, filev);
545 }
546
547 static inline
548 bool
redo_ifcreate_1(const char * prog,const char * file)549 redo_ifcreate_1 (
550 const char * prog,
551 const char * file
552 ) {
553 std::vector<const char *> filev;
554 filev.push_back(file);
555 return redo_ifcreate(prog, filev);
556 }
557
558 static inline
559 bool
redo_ifchange(const char * prog,unsigned meta_depth,const std::vector<const char * > & filev)560 redo_ifchange (
561 const char * prog,
562 unsigned meta_depth,
563 const std::vector<const char *> & filev
564 ) {
565 return redo(false, prog, meta_depth, filev) && record_prerequisites(prog, filev);
566 }
567
568 static inline
569 bool
redo_ifchange_1(const char * prog,unsigned meta_depth,const char * file)570 redo_ifchange_1 (
571 const char * prog,
572 unsigned meta_depth,
573 const char * file
574 ) {
575 std::vector<const char *> filev;
576 filev.push_back(file);
577 return redo_ifchange(prog, meta_depth, filev);
578 }
579
580 class RedoParentFDStack {
581 public:
RedoParentFDStack(int new_fd)582 RedoParentFDStack ( int new_fd ) : old_fd(redoparent_fd) { redoparent_fd = new_fd; }
~RedoParentFDStack()583 ~RedoParentFDStack() { redoparent_fd = old_fd; }
584 protected:
585 int old_fd;
586 };
587
588 static inline
589 bool
find_do_file(const char * prog,unsigned meta_depth,std::string dir,const char * const b,std::string & dofile_name,std::string & base,std::string & ext)590 find_do_file (
591 const char * prog,
592 unsigned meta_depth,
593 std::string dir,
594 const char * const b,
595 std::string & dofile_name,
596 std::string & base,
597 std::string & ext
598 ) {
599 const char * const be(extension(b));
600 for (;;) {
601 base = b;
602 ext = std::string();
603 dofile_name = dir + base + ".do";
604 if (exists(dofile_name)) {
605 redo_ifchange_1(prog, meta_depth + 1, dofile_name.c_str());
606 return true;
607 } else
608 redo_ifcreate_1(prog, dofile_name.c_str());
609
610 for (const char * e(be); ; e = extension(e + 1)) {
611 base = std::string(b, static_cast<std::size_t>(e - b));
612 ext = e;
613 dofile_name = dir + "default" + ext + ".do";
614 if (exists(dofile_name)) {
615 redo_ifchange_1(prog, meta_depth + 1, dofile_name.c_str());
616 return true;
617 } else
618 redo_ifcreate_1(prog, dofile_name.c_str());
619
620 if (!*e) break;
621 }
622
623 std::string::size_type len(dir.length());
624 if (len < 2) return false;
625 std::string::size_type slash(dir.find_last_of('/', len - 2));
626 if (std::string::npos == slash)
627 dir = std::string();
628 else
629 dir = dir.substr(0, slash + 1);
630 }
631 }
632
633 /* Jobs *********************************************************************
634 // **************************************************************************
635 */
636
637 namespace {
638
639 struct Job {
640 pid_t pid;
641 const char * arg;
642 std::string do_file_name;
643
644 Job(const char *);
645 bool run ( const char *, unsigned int );
646 bool finish ( const char *, int, int );
647
648 protected:
649 std::string target;
650 std::string tmp_target;
651 std::string database_name;
652 std::string tmp_database_name;
653 std::string lock_database_name;
654 FileDescriptorOwner lock_fd;
655 };
656
657 typedef std::list<std::shared_ptr<Job> > JobList;
658
659 }
660
Job(const char * a)661 Job::Job(
662 const char * a
663 ) :
664 pid(-1),
665 arg(a),
666 do_file_name(),
667 target(a),
668 tmp_target(target + ".doing"),
669 database_name(".redo/" + target + ".prereqs"),
670 tmp_database_name(database_name + ".build"),
671 lock_database_name(database_name + ".lock"),
672 lock_fd(-1)
673 {
674 }
675
676 inline
677 bool
run(const char * prog,unsigned meta_depth)678 Job::run (
679 const char * prog,
680 unsigned meta_depth
681 ) {
682 pid = -1;
683
684 const char * b(basename_of(arg));
685 if (b != arg)
686 makepath(".redo/" + std::string(arg, static_cast<std::size_t>(b - 1 - arg)));
687
688 if (debug) {
689 msg(prog, "INFO") << lock_database_name << ": Locking ...\n";
690 }
691 lock_fd.reset(open_lockfile_exclusive_or_wait_at(AT_FDCWD, lock_database_name.c_str()));
692 if (0 > lock_fd.get()) {
693 const int error(errno);
694 msg(prog, "ERROR") << lock_database_name << ": " << std::strerror(error) << "\n";
695 return false;
696 }
697 FileDescriptorOwner db_fd(open(tmp_database_name.c_str(), O_WRONLY|O_TRUNC|O_CREAT|O_NOCTTY, 0777));
698 if (0 > db_fd.get()) {
699 const int error(errno);
700 msg(prog, "ERROR") << tmp_database_name << ": " << std::strerror(error) << "\n";
701 return false;
702 }
703
704 RedoParentFDStack saved_parent(db_fd.get());
705 const std::string dir(arg, static_cast<std::size_t>(b - arg));
706 std::string base, ext;
707 if (!find_do_file(prog, meta_depth, dir, b, do_file_name, base, ext)) {
708 msg(prog, "ERROR") << arg << ": Cannot find .do file to use.\n";
709 return false;
710 }
711 const std::string fullbase(dir + base);
712 std::remove(tmp_target.c_str());
713
714 const char * argv[9] = {
715 do_file_name.c_str(),
716 fullbase.c_str(),
717 ext.c_str(),
718 tmp_target.c_str(),
719 nullptr
720 };
721 std::vector<const char *> envv;
722 for (char **e(environ); *e; ++e)
723 envv.push_back(*e);
724 std::ostringstream redoflags;
725 redoflags << "REDOFLAGS=";
726 if (keep_going) redoflags << " --keep-going";
727 if (debug) redoflags << " --debug";
728 if (silent) redoflags << " --silent";
729 if (verbose) redoflags << " --verbose";
730 if (-1 != db_fd.get()) redoflags << " --redoparent-fd=" << db_fd.get();
731 if (-1 != jobserver::fds[0]) {
732 redoflags << " --jobserver-fds=" << jobserver::fds[0];
733 if (-1 != jobserver::fds[1])
734 redoflags << "," << jobserver::fds[1];
735 }
736 const std::string & redoflags_str(redoflags.str());
737 envv.push_back(redoflags_str.c_str());
738 envv.push_back(nullptr);
739
740 if (verbose)
741 msg(prog, "INFO") << "spawn: " << do_file_name << " " << fullbase << " " << ext << " " << tmp_target << "\n" << std::flush;
742 pid = spawn(do_file_name.c_str(), argv, const_cast<const char **>(&envv.front()));
743
744 return true;
745 }
746
747 inline
748 bool
finish(const char * prog,int exit_status,int exit_code)749 Job::finish (
750 const char * prog,
751 int exit_status,
752 int exit_code
753 ) {
754 pid = -1;
755 if (WAIT_STATUS_EXITED != exit_status || 0 < exit_code) {
756 msg(prog, "ERROR") << target << ": Not done.\n";
757 rmrf(tmp_target.c_str());
758 lock_fd.reset(-1);
759 return false;
760 }
761 struct stat stbuf;
762 if (0 > lstat(tmp_target.c_str(), &stbuf)) {
763 std::ofstream tmp(tmp_target.c_str(), std::ios::app);
764 }
765 if ((0 <= lstat(target.c_str(), &stbuf)) && S_ISDIR(stbuf.st_mode)) {
766 if (0 > rmrf(target.c_str())) {
767 const int error(errno);
768 msg(prog, "ERROR") << target << ": Unable to remove contents of target directory: " << std::strerror(error) << "\n";
769 rmrf(tmp_target.c_str());
770 lock_fd.reset(-1);
771 return false;
772 }
773 }
774 delete_file_info(target);
775 if (0 > std::rename(tmp_database_name.c_str(), database_name.c_str())) {
776 const int error(errno);
777 msg(prog, "ERROR") << tmp_database_name << ": Unable to rename database file: " << std::strerror(error) << "\n";
778 rmrf(tmp_target.c_str());
779 lock_fd.reset(-1);
780 return false;
781 }
782 if (0 > std::rename(tmp_target.c_str(), target.c_str())) {
783 const int error(errno);
784 msg(prog, "ERROR") << target << ": Unable to rename target file: " << std::strerror(error) << "\n";
785 rmrf(tmp_target.c_str());
786 lock_fd.reset(-1);
787 return false;
788 }
789 if (!silent) {
790 msg(prog, "INFO") << target << ": Redone.\n" << std::flush;
791 }
792 lock_fd.reset(-1);
793 return true;
794 }
795
796 static inline
797 bool
run(const char * prog,unsigned meta_depth,JobList & jobs)798 run (
799 const char * prog,
800 unsigned meta_depth,
801 JobList & jobs
802 ) {
803 bool status = true;
804 JobList::iterator begin(jobs.begin()), next_to_run(begin), next_to_await(begin), end(jobs.end());
805 while (next_to_await != end) {
806 if (debug) {
807 if (next_to_run != end)
808 msg(prog, "INFO") << "Jobs still available to start.\n";
809 if (next_to_await != next_to_run)
810 msg(prog, "INFO") << "Jobs still available to await.\n";
811 }
812 while ((next_to_run != end) && jobserver::try_procure_slot(prog)) {
813 Job * const job(next_to_run->get());
814 if (!job->run(prog, meta_depth)) {
815 status = false;
816 jobserver::vacate_slot(prog);
817 } else if (-1 == job->pid) {
818 const int error(errno);
819 msg(prog, "ERROR") << job->do_file_name << ": " << std::strerror(error) << "\n";
820 status = false;
821 jobserver::vacate_slot(prog);
822 }
823 ++next_to_run;
824 }
825 while ((next_to_await != next_to_run) && (-1 == next_to_await->get()->pid)) {
826 ++next_to_await;
827 }
828 if (next_to_await != next_to_run) {
829 int exit_status, exit_code;
830 pid_t pid;
831 const int rc(wait_blocking_for_anychild_exit(pid, exit_status, exit_code));
832 if (0 >= rc) {
833 const int error(errno);
834 msg(prog, "ERROR") << std::strerror(error) << "\n";
835 status = false;
836 continue;
837 }
838 if (debug)
839 msg(prog, "INFO") << "Reaped child process ID " << pid << ".\n";
840 bool found = false;
841 for ( JobList::iterator i(next_to_await); i != next_to_run; ++i ) {
842 Job * const job(i->get());
843 if (pid == job->pid) {
844 if (!job->finish(prog, exit_status, exit_code))
845 status = false;
846 jobserver::vacate_slot(prog);
847 found = true;
848 break;
849 }
850 }
851 if (!found) {
852 msg(prog, "ERROR") << "Unknown child process ID " << pid << ".\n";
853 }
854 }
855 }
856 return status;
857 }
858
859 /* Redo internals ***********************************************************
860 // **************************************************************************
861 */
862
863 static inline
864 bool
satisfies_existence(const char * prog,const std::string & target_name)865 satisfies_existence (
866 const char * prog,
867 const std::string & target_name
868 ) {
869 if (!exists(target_name)) {
870 if (verbose)
871 msg(prog, "INFO") << target_name << " needs rebuilding because it does not exist.\n";
872 return false;
873 }
874 return true;
875 }
876
877 static inline
878 bool
satisfies_prerequisites(const char * prog,const std::string & target_name)879 satisfies_prerequisites (
880 const char * prog,
881 const std::string & target_name
882 ) {
883 const std::string database_name(".redo/" + target_name + ".prereqs");
884 const std::string lock_database_name(database_name + ".lock");
885 const FileDescriptorOwner lock_fd(open_lockfile_shared_or_wait_at(AT_FDCWD, lock_database_name.c_str()));
886 if (0 > lock_fd.get()) return false;
887 std::ifstream file(database_name.c_str());
888 if (file.fail()) return false;
889 bool satisfaction(true);
890 while (EOF != file.peek()) {
891 Information db_info;
892 std::string prereq_name;
893 read_db_line(file, db_info, prereq_name);
894 const Information & fs_info(get_file_info(prereq_name, &db_info));
895 if (db_info.type != fs_info.type) {
896 if (verbose) {
897 msg(prog, "INFO") << target_name << " needs rebuilding because " << prereq_name;
898 if (fs_info.NOTHING == fs_info.type)
899 std::clog << " does not exist.\n";
900 else
901 std::clog << " has changed type.\n";
902 }
903 satisfaction = false;
904 if (!keep_going) break;
905 } else
906 if (fs_info.NOTHING != fs_info.type
907 && fs_info.last_written != db_info.last_written
908 ) {
909 if (fs_info.SPECIAL == fs_info.type || fs_info.DIRECTORY == fs_info.type) {
910 if (verbose) {
911 char fs_buf[64], db_buf[64];
912 struct std::tm fs_tm(*std::localtime(&fs_info.last_written));
913 struct std::tm db_tm(*std::localtime(&db_info.last_written));
914 std::strftime(fs_buf, sizeof fs_buf, "%F %T %z", &fs_tm);
915 std::strftime(db_buf, sizeof db_buf, "%F %T %z", &db_tm);
916 msg(prog, "INFO") << target_name << " needs rebuilding because " << prereq_name << " has changed timestamp from " << db_buf << " to " << fs_buf << ".\n";
917 }
918 satisfaction = false;
919 if (!keep_going) break;
920 } else
921 if (0 != std::memcmp(fs_info.hash, db_info.hash, sizeof db_info.hash)) {
922 if (verbose) {
923 msg(prog, "INFO") << target_name << " needs rebuilding because " << prereq_name << " has changed hash value from ";
924 puthash(std::clog, db_info);
925 std::clog << " to ";
926 puthash(std::clog, fs_info);
927 std::clog << ".\n";
928 }
929 satisfaction = false;
930 if (!keep_going) break;
931 }
932 }
933 }
934 return satisfaction;
935 }
936
937 static inline
938 bool
is_sourcefile(const std::string & name)939 is_sourcefile(
940 const std::string & name
941 ) {
942 if (!exists(name)) return false;
943 if (exists(".redo/" + name + ".prereqs")) return false;
944 if (exists(".redo/" + name + ".prereqsne")) return false;
945 if (exists(".redo/" + name + ".prereqs.build")) return false;
946 if (exists(".redo/" + name + ".prereqsne.build")) return false;
947 return true;
948 }
949
950 static inline
951 bool
recurse_prerequisites(const char * prog,unsigned meta_depth,const std::string & name)952 recurse_prerequisites (
953 const char * prog,
954 unsigned meta_depth,
955 const std::string & name
956 ) {
957 const std::string database_name(".redo/" + name + ".prereqs");
958 const std::string lock_database_name(database_name + ".lock");
959 const FileDescriptorOwner lock_fd(open_lockfile_shared_or_wait_at(AT_FDCWD, lock_database_name.c_str()));
960 if (0 > lock_fd.get()) return false;
961 std::ifstream file(database_name.c_str());
962 if (file.fail()) return false;
963 std::list<std::string> files;
964 while (EOF != file.peek()) {
965 Information info;
966 std::string prereq_name;
967 read_db_line(file, info, prereq_name);
968 if (info.NOTHING != info.type && !is_sourcefile(prereq_name))
969 files.push_back(prereq_name);
970 }
971 return files.empty() || redo(false, prog, meta_depth, convert(files));
972 }
973
974 static
975 bool
redo(bool unconditional,const char * prog,unsigned meta_depth,const std::vector<const char * > & filev)976 redo (
977 bool unconditional,
978 const char * prog,
979 unsigned meta_depth,
980 const std::vector<const char *> & filev
981 ) {
982 if (meta_depth >= MAX_META_DEPTH) return true;
983 bool status(true);
984 if (debug) {
985 msg(prog, "INFO") << "REDO" << (unconditional ? "" : "-IFCHANGE-INTERNAL") << ": >";
986 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
987 const char * arg(*i);
988 std::clog << ' ';
989 if (is_sourcefile(arg)) std::clog << "(" << arg << ")"; else std::clog << arg;
990 }
991 std::clog << '<' << std::endl;
992 }
993
994 JobList jobs;
995
996 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
997 const char * arg(*i);
998 if (is_root_or_ends_with_dot_or_dotdot(arg)) continue; // Treat as source files, because they always exist.
999 if (is_sourcefile(arg)) continue;
1000 if (!unconditional) {
1001 if (satisfies_existence(prog, arg)) {
1002 if (recurse_prerequisites(prog, meta_depth, arg)
1003 && satisfies_existence(prog, arg)
1004 && satisfies_prerequisites(prog, arg)
1005 )
1006 continue;
1007 }
1008 }
1009
1010 jobs.push_back(std::shared_ptr<Job>(new Job(arg)));
1011 }
1012 if (!run(prog, meta_depth, jobs))
1013 status = false;
1014 if (debug) {
1015 msg(prog, "INFO") << "Redo status = " << status << ".\n" << std::flush;
1016 }
1017 return status;
1018 }
1019
1020 /* Main functions ***********************************************************
1021 // **************************************************************************
1022 */
1023
1024 static
1025 bool
parse_fds(const char * prog,const char * str,int fds[],size_t count)1026 parse_fds(
1027 const char * prog,
1028 const char * str,
1029 int fds[],
1030 size_t count
1031 ) {
1032 char * s(const_cast<char *>(str));
1033 for (size_t j(0); j < count; ++j) {
1034 if (j > 0) {
1035 if (!*s) {
1036 fds[j] = fds[j - 1];
1037 continue;
1038 } else
1039 if (',' != *s) {
1040 msg(prog, "ERROR") << str << ": " << s << ": Missing comma.\n";
1041 return false;
1042 } else
1043 ++s;
1044 }
1045 const char * old(s);
1046 long i(std::strtol(old, &s, 0));
1047 if (old == s) {
1048 msg(prog, "ERROR") << str << ": " << s << ": File descriptor number not supplied.\n";
1049 return false;
1050 }
1051 if (i > INT_MAX) {
1052 msg(prog, "ERROR") << str << ": " << s << ": File descriptor number too large.\n";
1053 return false;
1054 }
1055 fds[j] = static_cast<int>(i);
1056 }
1057 if (*s) {
1058 msg(prog, "ERROR") << str << ": " << s << ": Trailing junk after file descriptor(s).\n";
1059 return false;
1060 }
1061 return true;
1062 }
1063
1064 int
redo_main(const char * prog,int argc,const char * argv[])1065 redo_main ( const char * prog, int argc, const char * argv[] )
1066 {
1067 std::vector<const char *> filev;
1068
1069 try {
1070 std::string jobserver_fds_string;
1071 std::string redoparent_fd_string;
1072 const char * jobserver_fds_c_str = nullptr;
1073 const char * redoparent_fd_c_str = nullptr;
1074 const char * directory = nullptr;
1075 unsigned long max_jobs = 0;
1076 popt::bool_definition silent_option('s', "silent", "Operate quietly.", silent);
1077 popt::bool_definition quiet_option('\0', "quiet", "alias for --silent", silent);
1078 popt::bool_definition keep_going_option('k', "keep-going", "Continue with the next target if a .do script fails.", keep_going);
1079 popt::bool_definition debug_option('d', "debug", "Output debugging information.", debug);
1080 popt::bool_definition verbose_option('\0', "verbose", "Display information about the database.", verbose);
1081 popt::bool_definition print_option('p', "print", "alias for --verbose", verbose);
1082 popt::unsigned_number_definition jobs_option('j', "jobs", "number", "Allow multiple jobs to run in parallel.", max_jobs, 0);
1083 popt::string_definition directory_option('C', "directory", "directory", "Change to directory before doing anything.", directory);
1084 popt::definition * top_table[] = {
1085 &silent_option,
1086 &quiet_option,
1087 &debug_option,
1088 &keep_going_option,
1089 &verbose_option,
1090 &print_option,
1091 &jobs_option,
1092 &directory_option
1093 };
1094 popt::top_table_definition main_option(sizeof top_table/sizeof *top_table, top_table, "Main options", "filename(s)");
1095 catchall_definition ignore;
1096 special_string_definition new_make_jobserver_option('J', "jobserver-auth", "fd-list", "Provide the file descriptor numbers of the jobserver pipe.", jobserver_fds_c_str);
1097 special_string_definition old_make_jobserver_option('\0', "jobserver-fds", "fd-list", "Provide the file descriptor numbers of the jobserver pipe.", jobserver_fds_c_str);
1098 popt::definition * make_env_table[] = {
1099 &silent_option,
1100 &quiet_option,
1101 &debug_option,
1102 &keep_going_option,
1103 &verbose_option,
1104 &print_option,
1105 &jobs_option,
1106 &new_make_jobserver_option,
1107 &old_make_jobserver_option,
1108 &ignore
1109 };
1110 popt::table_definition make_env_main_option(sizeof make_env_table/sizeof *make_env_table, make_env_table, "Main options (environment variable arguments)");
1111 special_string_definition redo_jobserver_option('\0', "jobserver-fds", "fd-list", "Provide the file descriptor numbers of the jobserver pipe.", jobserver_fds_c_str);
1112 special_string_definition redoparent_option('\0', "redoparent-fd", "fd", "Provide the file descriptor number of the redo database current parent file.", redoparent_fd_c_str);
1113 popt::definition * redo_env_table[] = {
1114 &silent_option,
1115 &quiet_option,
1116 &debug_option,
1117 &keep_going_option,
1118 &verbose_option,
1119 &print_option,
1120 &jobs_option,
1121 &redo_jobserver_option,
1122 &redoparent_option,
1123 };
1124 popt::table_definition redo_env_main_option(sizeof redo_env_table/sizeof *redo_env_table, redo_env_table, "Main options (environment variable arguments)");
1125
1126 static const char * vars[] = { "REDOFLAGS", "MAKEFLAGS", "MFLAGS" };
1127 for ( unsigned i = 0 ; i < sizeof vars/sizeof *vars; ++i ) {
1128 const char * var(vars[i]);
1129 if (const char * s = std::getenv(var)) {
1130 std::list<std::string> args(split(s, 1 == i)); // Special bodge for MAKEFLAGS, which omits the "-" from the first option.
1131 std::vector<const char *> env_argv(convert(args));
1132 std::vector<const char *> env_filev;
1133 popt::arg_processor<std::vector<const char *>::const_iterator> p(env_argv.begin(), env_argv.end(), prog, i ? make_env_main_option : redo_env_main_option, env_filev);
1134 p.process(false /* allow intermingling of options and arguments */);
1135 if (p.stopped())
1136 msg(prog, "WARNING") << var << ": Ignoring halt of option processing.\n";
1137 if (1 == i) { // Special bodge for MAKEFLAGS, which BSD make puts macro definitions into.
1138 for ( std::vector<const char *>::iterator j = env_filev.begin(); j != env_filev.end(); ) {
1139 if (std::strchr(*j, '='))
1140 j = env_filev.erase(j);
1141 else
1142 ++j;
1143 }
1144 }
1145 if (!env_filev.empty())
1146 msg(prog, "WARNING") << var << ": " << env_filev.front() << ": Ignoring filename(s).\n";
1147 if (jobserver_fds_c_str) { jobserver_fds_string = jobserver_fds_c_str; jobserver_fds_c_str = nullptr; }
1148 if (redoparent_fd_c_str) { redoparent_fd_string = redoparent_fd_c_str; redoparent_fd_c_str = nullptr; }
1149 break;
1150 }
1151 }
1152
1153 popt::arg_processor<const char **> p(argv + 1, argv + argc, prog, main_option, filev);
1154 p.process(false /* allow intermingling of options and arguments */);
1155 if (p.stopped()) return EXIT_SUCCESS;
1156 if (directory) {
1157 const int r(chdir(directory));
1158 if (r < 0) {
1159 const int error(errno);
1160 msg(prog, "FATAL") << directory << ": " << std::strerror(error) << "\n";
1161 return EXIT_FAILURE;
1162 }
1163 }
1164 if (jobserver_fds_c_str) { jobserver_fds_string = jobserver_fds_c_str; jobserver_fds_c_str = nullptr; }
1165 if (redoparent_fd_c_str) { redoparent_fd_string = redoparent_fd_c_str; redoparent_fd_c_str = nullptr; }
1166
1167 if (!jobserver_fds_string.empty()) {
1168 if (!parse_fds(prog, jobserver_fds_string.c_str(), jobserver::fds, sizeof jobserver::fds/sizeof *jobserver::fds))
1169 return EXIT_FAILURE;
1170 if (jobs_option.is_set())
1171 msg(prog, "WARNING") << "Ignoring jobs option when there's already a job server.\n";
1172 } else {
1173 if (jobs_option.is_set()) {
1174 if (max_jobs < 1U) {
1175 msg(prog, "FATAL") << "Invalid job limit.\n";
1176 return EXIT_FAILURE;
1177 }
1178 if (0 > pipe(jobserver::fds)) {
1179 const int error(errno);
1180 msg(prog, "FATAL") << "pipe: " << std::strerror(error) << "\n";
1181 return EXIT_FAILURE;
1182 }
1183 for (unsigned long m(max_jobs); m > 1U; --m)
1184 jobserver::vacate_slot(prog);
1185 }
1186 }
1187 if (!redoparent_fd_string.empty()) {
1188 if (!parse_fds(prog, redoparent_fd_string.c_str(), &redoparent_fd, 1U))
1189 return EXIT_FAILURE;
1190 }
1191 } catch (const popt::error & e) {
1192 msg(prog, "FATAL") << e.arg << ": " << e.msg << "\n";
1193 return EXIT_FAILURE;
1194 }
1195
1196 if (filev.empty()) {
1197 msg(prog, "FATAL") << "No filenames supplied.\n";
1198 return EXIT_FAILURE;
1199 }
1200
1201 const unsigned meta_depth(0U);
1202
1203 // Increment MAKELEVEL
1204 char gnulevelbuf[sizeof "MAKELEVEL=" + 64] = "MAKELEVEL=";
1205 char bsdlevelbuf[sizeof "__MKLVL__=" + 64] = "__MKLVL__=";
1206 {
1207 const char * level(std::getenv("MAKELEVEL"));
1208 if (!level)
1209 level = std::getenv("__MKLVL__");
1210 if (level) {
1211 makelevel = level;
1212 unsigned long ul(std::strtoul(level, const_cast<char **>(&level), 0));
1213 snprintf(gnulevelbuf + sizeof "MAKELEVEL=" - 1, 64, "%lu", ul + 1);
1214 snprintf(bsdlevelbuf + sizeof "__MKLVL__=" - 1, 64, "%lu", ul + 1);
1215 } else
1216 {
1217 snprintf(gnulevelbuf + sizeof "MAKELEVEL=" - 1, 64, "%lu", 1UL);
1218 snprintf(bsdlevelbuf + sizeof "__MKLVL__=" - 1, 64, "%lu", 1UL);
1219 }
1220 }
1221 putenv(gnulevelbuf);
1222 putenv(bsdlevelbuf);
1223
1224 if (0 == std::strcmp(prog, "redo-ifcreate"))
1225 return redo_ifcreate(prog, filev) ? EXIT_SUCCESS : EXIT_FAILURE;
1226 else
1227 if (0 == std::strcmp(prog, "redo-ifchange"))
1228 return redo_ifchange(prog, meta_depth, filev) ? EXIT_SUCCESS : EXIT_FAILURE;
1229 else
1230 if (0 == std::strcmp(prog, "redo")) {
1231 mkdir(".redo", 0777);
1232 return redo(true, prog, meta_depth, filev) ? EXIT_SUCCESS : EXIT_FAILURE;
1233 } else
1234 {
1235 msg(prog, "FATAL") << prog << ": Unknown function.\n";
1236 return EXIT_FAILURE;
1237 }
1238 }
1239
1240 int
cubehash_main(const char * prog,int argc,const char * argv[])1241 cubehash_main ( const char * prog, int argc, const char * argv[] )
1242 {
1243 std::vector<const char *> filev;
1244
1245 try {
1246 const char * directory = nullptr;
1247 popt::string_definition directory_option('C', "directory", "directory", "Change to directory before doing anything.", directory);
1248 popt::definition * top_table[] = {
1249 &directory_option
1250 };
1251 popt::top_table_definition main_option(sizeof top_table/sizeof *top_table, top_table, "Main options", "filename(s)");
1252 popt::arg_processor<const char **> p(argv + 1, argv + argc, prog, main_option, filev);
1253 p.process(false /* allow intermingling of options and arguments */);
1254 if (p.stopped()) return EXIT_SUCCESS;
1255 if (directory) {
1256 const int r(chdir(directory));
1257 if (r < 0) {
1258 const int error(errno);
1259 msg(prog, "FATAL") << directory << ": " << std::strerror(error) << "\n";
1260 return EXIT_FAILURE;
1261 }
1262 }
1263 } catch (const popt::error & e) {
1264 msg(prog, "FATAL") << e.arg << ": " << e.msg << "\n";
1265 return EXIT_FAILURE;
1266 }
1267
1268 if (filev.empty()) {
1269 msg(prog, "FATAL") << "No filenames supplied.\n";
1270 return EXIT_FAILURE;
1271 }
1272
1273 for ( std::vector<const char *>::const_iterator i = filev.begin(); i != filev.end(); ++i ) {
1274 const char * arg(*i);
1275 const Information & info(get_file_info(arg, nullptr));
1276 write_db_line(std::cout, info, arg);
1277 }
1278 return EXIT_SUCCESS;
1279 }
1280
1281 int
main(int argc,const char * argv[])1282 main ( int argc, const char * argv[] )
1283 {
1284 const char * prog(basename_of(argv[0]));
1285
1286 if (0 == std::strcmp(prog, "cubehash"))
1287 return cubehash_main(prog, argc, argv);
1288 else
1289 return redo_main(prog, argc, argv);
1290 }
1291