summaryrefslogtreecommitdiffstats
path: root/base/zygote_manager.cc
blob: 2310c2d66008108e81fa0bf38f1a597d5f980d63 (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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/zygote_manager.h"

#if defined(OS_LINUX)

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <sys/file.h>     // for flock()
#include <sys/stat.h>
#include <sys/uio.h>      // for struct iovec
#include <sys/wait.h>
#include <unistd.h>       // for ssize_t

#include <string>

#include "base/eintr_wrapper.h"
#include "base/file_descriptor_shuffle.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/process_util.h"
#include "base/reserved_file_descriptors.h"
#include "base/singleton.h"

using file_util::Delete;

// See comment below, where sigaction is called.
static void SIGCHLDHandler(int signal) {
}

namespace base {

const char ZygoteManager::kZMagic[] = "zygo";

ZygoteManager::~ZygoteManager() {
  if (server_fd_ != -1) {
    close(server_fd_);
    server_fd_ = -1;
  }
  if (client_fd_ != -1) {
    close(client_fd_);
    client_fd_ = -1;
  }
  if (lockfd_ != -1) {
    close(lockfd_);
    lockfd_ = -1;
  }
  if (canary_fd_ != -1) {
    // Wake up the poll() in ReadAndHandleMessage()
    close(canary_fd_);
    canary_fd_ = -1;
  }
}

// Runs in client process
ZygoteManager* ZygoteManager::Get() {
  static bool checked = false;
  static bool enabled = false;
  if (!checked) {
    enabled = (getenv("ENABLE_ZYGOTE_MANAGER") != NULL);
    // sanity check - make sure all the places that relaunch chrome
    // have been zygotified.
    if (enabled)
      DCHECK(getenv("ZYGOTE_MANAGER_STARTED") == NULL)
          << "fork/exec used instead of LongFork";
    (void) setenv("ZYGOTE_MANAGER_STARTED", "1", 1);
    checked = true;
  }
  if (!enabled)
    return NULL;
  return Singleton<ZygoteManager>::get();
}

// Runs in zygote manager process
int ZygoteManager::UnpickleHeader(const Pickle& reply, void** iter) {
  std::string magic;
  if (!reply.ReadString(iter, &magic) || magic != std::string(kZMagic)) {
    LOG(ERROR) << "reply didn't start with " << kZMagic;
    return ZMBAD;
  }
  pid_t clientpid = (pid_t) -1;
  if (!reply.ReadInt(iter, &clientpid)) {
    LOG(ERROR) << "Can't read client pid";
    return ZMBAD;
  }
  if (clientpid != getpid()) {
    LOG(ERROR) << "got client pid " << clientpid << ", expected " << getpid();
    DCHECK(clientpid == getpid());
    return ZMBAD;
  }
  int kind;
  if (!reply.ReadInt(iter, &kind)) {
    LOG(ERROR) << "can't read kind";
    return ZMBAD;
  }
  return kind;
}

// Runs in client process (only used in unit test)
bool ZygoteManager::Ping(base::TimeDelta* delta) {
  if (client_fd_ == -1)
    return false;

  Pickle pickle;
  pickle.WriteString(kZMagic);
  pickle.WriteInt(getpid());
  pickle.WriteInt(ZMPING);

  int bytes_sent;
  int bytes_read = -1;

  TimeTicks time_sent = TimeTicks::HighResNow();

  // Lock fork server, send the pickle, wait for the reply, unlock
  if (flock(lockfd_, LOCK_EX))
    LOG(ERROR) << "flock failed, errno " << errno;
  bytes_sent = HANDLE_EINTR(write(client_fd_,
      const_cast<void *>(pickle.data()), pickle.size()));
  if (bytes_sent > 0) {
    bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN));
  }
  if (flock(lockfd_, LOCK_UN))
    LOG(ERROR) << "flock failed, errno " << errno;

  TimeTicks time_received = TimeTicks::HighResNow();

  if (bytes_sent < 1) {
    LOG(ERROR) << "Can't send to zm, errno " << errno;
    return false;
  }
  if (bytes_read < 1) {
    LOG(ERROR) << "Can't get from zm, errno " << errno;
    return false;
  }

  // Unpickle the reply
  Pickle reply(msg_buf_, bytes_read);
  void* iter = NULL;
  int kind = UnpickleHeader(reply, &iter);
  if (kind != ZMPINGED) {
    LOG(ERROR) << "reply wrong kind " << kind;
    return false;
  }
  *delta = TimeTicks::HighResNow() - time_sent;
  LOG(INFO) << "Round trip time in microseconds: " << delta->InMicroseconds();
  return true;
}

// Runs in zygote manager process
void ZygoteManager::PingHandler(const Pickle& request, void* iter,
    Pickle* reply, std::vector<std::string>** newargv) {
    reply->WriteInt(ZMPINGED);
}

// Runs in browser process, called only by base::ForkApp()
pid_t ZygoteManager::LongFork(const std::vector<std::string>& argv,
           const file_handle_mapping_vector& fds_to_remap) {
  if (client_fd_ == -1)
    return -1;

  Pickle pickle;

  // Encode the arguments and the desired remote fd numbers in the pickle,
  // and the fds in a separate buffer
  pickle.WriteString(kZMagic);
  pickle.WriteInt(getpid());
  pickle.WriteInt(ZMFORK);
  pickle.WriteInt(argv.size());
  std::vector<std::string>::const_iterator argi;
  for (argi = argv.begin(); argi != argv.end(); ++argi)
    pickle.WriteString(*argi);
  pickle.WriteInt(fds_to_remap.size());

  // Wrap the pickle and the fds together in a msghdr
  ::msghdr msg;
  memset(&msg, 0, sizeof(msg));
  struct iovec iov;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = cmsg_buf_;
  msg.msg_controllen = CMSG_LEN(sizeof(int) * fds_to_remap.size());
  struct cmsghdr* cmsg;
  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = msg.msg_controllen;
  int* wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
  for (size_t i = 0; i < fds_to_remap.size(); i++) {
    pickle.WriteInt(fds_to_remap[i].second);
    wire_fds[i] = fds_to_remap[i].first;
  }
  iov.iov_base = const_cast<void *>(pickle.data());
  iov.iov_len = pickle.size();

  int bytes_sent;
  int bytes_read = -1;

  // Lock fork server, send the pickle, wait for the reply, unlock
  if (flock(lockfd_, LOCK_EX))
    LOG(ERROR) << "flock failed, errno " << errno;
  bytes_sent = HANDLE_EINTR(sendmsg(client_fd_, &msg, MSG_WAITALL));
  if (bytes_sent > 0) {
    bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN));
  }
  if (flock(lockfd_, LOCK_UN))
    LOG(ERROR) << "flock failed, errno " << errno;

  if (bytes_sent < 1) {
    LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_;
    return (pid_t) -1;
  }
  if (bytes_read < 1) {
    LOG(ERROR) << "Can't get from zm, errno " << errno;
    return (pid_t) -1;
  }

  // Unpickle the reply
  Pickle reply(msg_buf_, bytes_read);
  void* iter = NULL;
  int kind = UnpickleHeader(reply, &iter);
  if (kind != ZMFORKED) {
    LOG(ERROR) << "reply wrong kind " << kind;
    return (pid_t) -1;
  }
  pid_t newpid = (pid_t) -1;
  int pid_errno;
  if (!reply.ReadInt(&iter, &newpid) || !reply.ReadInt(&iter, &pid_errno)) {
    LOG(ERROR) << "fork failed, can't read pid/errno";
    return (pid_t) -1;
  }
  if ((newpid == (pid_t) -1) || pid_errno != 0) {
    LOG(ERROR) << "fork failed, pid " << newpid << ", errno " << pid_errno;
    return (pid_t) -1;
  }
  return newpid;
}

// Runs in zygote manager process
bool ZygoteManager::LongForkHandler(const Pickle& request, void* iter,
    Pickle* reply, std::vector<std::string>** newargv,
    const int wire_fds[], int num_wire_fds) {
  file_handle_mapping_vector fds_to_remap;
  pid_t childpid;

  reply->WriteInt(ZMFORKED);

  // Unpickle commandline for new child
  std::vector<std::string>* argv = new std::vector<std::string>;
  int argc;
  request.ReadInt(&iter, &argc);
  for (int i = 0; i < argc; i++) {
    std::string arg;
    if (!request.ReadString(&iter, &arg)) {
      LOG(ERROR) << "can't read arg?";
      goto error_reply;
    }
    argv->push_back(arg);
  }
  // Unpickle file descriptor map for new child
  int numfds;
  request.ReadInt(&iter, &numfds);
  DCHECK(numfds == num_wire_fds);
  if (numfds != num_wire_fds) {
    LOG(ERROR) << "numfds " << numfds << " != num_wire_fds " << num_wire_fds;
    goto error_reply;
  }
  for (int i = 0; i < numfds; i++) {
    int fd;
    if (!request.ReadInt(&iter, &fd)) {
      LOG(ERROR) << "can't read fd?";
      goto error_reply;
    }
    fds_to_remap.push_back(std::pair<int, int>(wire_fds[i], fd));
  }

  // Mitosis!
  childpid = fork();

  if (childpid != 0) {
    // parent
    // first off, close our copy of the child's file descriptors
    for (file_handle_mapping_vector::const_iterator
        it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) {
      close(it->first);
    }

    // Finish formatting the reply
    reply->WriteInt(childpid);
    if (childpid == (pid_t) -1) {
      reply->WriteInt(errno);
      return false;
    } else {
      reply->WriteInt(0);
    }
  } else {
    // child
    // Apply file descriptor map
    InjectiveMultimap fd_shuffle;
    for (file_handle_mapping_vector::const_iterator
        it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) {
      fd_shuffle.push_back(InjectionArc(it->first, it->second, false));
    }

    // Avoid closing descriptor children will need to contact fork server.
    fd_shuffle.push_back(InjectionArc(client_fd_, client_fd_, false));
    // Avoid closing log descriptor we're using
    int logfd = logging::GetLoggingFileDescriptor();
    if (logfd != -1)
      fd_shuffle.push_back(InjectionArc(logfd, logfd, false));
    // And of course avoid closing the cached fds.
    std::map<std::string, int>::iterator i;
    for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) {
      fd_shuffle.push_back(InjectionArc(i->second, i->second, false));
    }

    // If there is any clash in the mapping, this function will DCHECK.
    if (!ShuffleFileDescriptors(fd_shuffle))
      exit(127);

    // Open this after shuffle to avoid using reserved slots.
    lockfd_ = open(lockfile_.c_str(), O_RDWR, 0);
    if (lockfd_ == -1) {
      // TODO(dkegel): real error handling
      exit(126);
    }
    // Mark it as not to be closed.
    fd_shuffle.push_back(InjectionArc(lockfd_, lockfd_, false));

    // Also closes reserved fds.
    CloseSuperfluousFds(fd_shuffle);

    *newargv = argv;
    // Because *newargv is set, we will return to main instead of looping
  }
  return true;

 error_reply:
  reply->WriteInt(-1);
  reply->WriteInt(-1);
  for (int i=0; i<num_wire_fds; i++)
    close(wire_fds[i]);
  return false;
}

// Runs in browser process, called by ProcessWatcher::EnsureProcessTerminated().
void ZygoteManager::EnsureProcessTerminated(pid_t childpid) {
  if (client_fd_ == -1)
    return;

  Pickle pickle;

  pickle.WriteString(kZMagic);
  pickle.WriteInt(getpid());
  pickle.WriteInt(ZMREAP);
  pickle.WriteInt(childpid);

  int bytes_sent = HANDLE_EINTR(
      write(client_fd_, const_cast<void*>(pickle.data()), pickle.size()));

  if (bytes_sent < 1) {
    LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_;
  }
}

// Runs in zygote manager process
void ZygoteManager::EnsureProcessTerminatedHandler(const Pickle& request,
    void* iter) {
  pid_t childpid;
  request.ReadInt(&iter, &childpid);
  NOTIMPLEMENTED();
  // TODO(dkegel): put childpid on a watch list, and terminate it
  // after a while as chrome/common/process_watcher does.
}

static bool ValidateFilename(const std::string& filename) {
  // We only have to open one kind of file, but we don't know
  // the directory it's in, so be as restrictive as we can within
  // those bounds.

  static const char* allowed_prefix = "/";
  if (filename.compare(0, strlen(allowed_prefix), allowed_prefix) != 0) {
    LOG(ERROR) << "filename did not start with " << allowed_prefix;
    return false;
  }
  static const char* allowed_suffix = ".pak";
  if ((filename.length() <= strlen(allowed_suffix)) ||
      (filename.compare(filename.length() - strlen(allowed_suffix),
                       strlen(allowed_suffix), allowed_suffix) != 0)) {
    LOG(ERROR) << "filename did not end in " << allowed_suffix;
    return false;
  }
  if (filename.find("../") != std::string::npos) {
    LOG(ERROR) << "filename contained relative component";
    return false;
  }
  static const char* forbidden_prefixes[] = {
    "/var/", "/tmp/", "/etc/", "/dev/", "/proc/", 0 };
  for (const char** p = forbidden_prefixes;
       *p; p++) {
    if (filename.compare(0, strlen(*p), *p) == 0) {
      LOG(ERROR) << "filename began with " << *p;
      return false;
    }
  }
  return true;
}

// Runs in browser process
int ZygoteManager::OpenFile(const std::string& filename) {
  // For security reasons, we only support .pak files,
  // and only in certain locations.
  if (!ValidateFilename(filename)) {
    LOG(INFO) << "ZygoteManager: filename " << filename << " disallowed.";
    return -1;
  }

  std::map<std::string, int>::iterator mapiter = cached_fds_.find(filename);
  if (mapiter != cached_fds_.end())
    return mapiter->second;

  if (client_fd_ == -1)
    return -1;

  Pickle pickle;

  pickle.WriteString(kZMagic);
  pickle.WriteInt(getpid());
  pickle.WriteInt(ZMOPEN);
  pickle.WriteString(filename);

  // Get ready to receive fds
  ::msghdr msg = {0};
  struct iovec iov = {msg_buf_, kMAX_MSG_LEN};
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = cmsg_buf_;
  msg.msg_controllen = kMAX_CMSG_LEN;

  ssize_t bytes_sent;
  ssize_t bytes_read = 0;

  if (flock(lockfd_, LOCK_EX))
    LOG(ERROR) << "flock failed, errno " << errno;
  bytes_sent = HANDLE_EINTR(
      write(client_fd_, const_cast<void *>(pickle.data()), pickle.size()));
  if (bytes_sent > 0) {
    bytes_read = HANDLE_EINTR(recvmsg(client_fd_, &msg, MSG_WAITALL));
  }
  if (flock(lockfd_, LOCK_UN))
    LOG(ERROR) << "flock failed, errno " << errno;

  if (bytes_sent < 1) {
    LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_;
    return -1;
  }
  if (bytes_read < 1) {
    LOG(ERROR) << "Can't get from zm, errno " << errno;
    return -1;
  }

  // Locate the sole block of sent file descriptors within the list of
  // control messages
  const int* wire_fds = NULL;
  unsigned num_wire_fds = 0;
  if (msg.msg_controllen > 0) {
    struct cmsghdr* cmsg;
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
         cmsg = CMSG_NXTHDR(&msg, cmsg)) {
      if (cmsg->cmsg_level == SOL_SOCKET &&
          cmsg->cmsg_type == SCM_RIGHTS) {
        const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
        assert(payload_len % sizeof(int) == 0);
        wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
        num_wire_fds = payload_len / sizeof(int);
        break;
      }
    }
  }
  DCHECK(!(msg.msg_flags & MSG_CTRUNC));

  // Unpickle the reply
  Pickle reply(msg_buf_, bytes_read);
  void* iter = NULL;
  int kind = UnpickleHeader(reply, &iter);
  if (kind != ZMOPENED) {
    LOG(ERROR) << "reply wrong kind " << kind;
    goto error_close;
  }
  int newfd_errno;
  if (!reply.ReadInt(&iter, &newfd_errno)) {
    LOG(ERROR) << "open failed, can't read errno";
    goto error_close;
  }
  if (newfd_errno != 0) {
    LOG(ERROR) << "open failed, errno " << newfd_errno;
    goto error_close;
  }
  if (num_wire_fds != 1) {
    LOG(ERROR) << "open failed, reply wrong number fds " << num_wire_fds;
    goto error_close;
  }
  if (wire_fds[0] == -1) {
    LOG(ERROR) << "open failed, fd -1";
    NOTREACHED();
    return -1;
  }
  return wire_fds[0];

 error_close:
  for (unsigned i=0; i<num_wire_fds; i++)
    close(wire_fds[i]);
  return -1;
}

// Runs in zygote manager process
bool ZygoteManager::OpenFileHandler(const Pickle& request, void* iter,
    Pickle* reply, ::msghdr* msg) {
  reply->WriteInt(ZMOPENED);

  std::string filename;
  if (!request.ReadString(&iter, &filename)) {
    LOG(ERROR) << "no filename?";
    return false;
  }
  if (!ValidateFilename(filename)) {
    // Fake a unix error code
    reply->WriteInt(EPERM);
    return false;
  }

  std::map<std::string, int>::iterator i;
  int newfd;
  std::map<std::string, int>::iterator mapiter = cached_fds_.find(filename);
  if (mapiter == cached_fds_.end()) {
    // Verify that file is a plain file
    struct stat statbuf;
    if (lstat(filename.c_str(), &statbuf) != 0) {
      LOG(ERROR) << "can't stat " << filename << ", errno " << errno;
      return false;
    }
    if (!S_ISREG(statbuf.st_mode)) {
      LOG(ERROR) << "not regular file " << filename;
      // Fake a unix error code
      reply->WriteInt(EISDIR);
      return false;
    }
    newfd = open(filename.c_str(), O_RDONLY, 0);
    if (newfd != -1) {
      cached_fds_[filename] = newfd;
    } else {
      LOG(ERROR) << "can't open " << filename << ", errno " << errno;
    }
  } else {
    newfd = mapiter->second;
  }
  if (newfd == -1) {
    reply->WriteInt(errno);
  } else {
    reply->WriteInt(0);
    msg->msg_control = cmsg_buf_;
    msg->msg_controllen = CMSG_LEN(sizeof(int));
    struct cmsghdr* cmsg;
    cmsg = CMSG_FIRSTHDR(msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = msg->msg_controllen;
    int* wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
    wire_fds[0] = newfd;
  }

  return true;
}

// Runs in zygote manager process
bool ZygoteManager::ReadAndHandleMessage(std::vector<std::string>** newargv) {
  // Wait for activity either on canary fd or main fd.
  struct pollfd watcher[2];
  memset(watcher, 0, sizeof(watcher));
  watcher[0].fd = canary_fd_;
  watcher[0].events = POLLIN|POLLHUP;
  watcher[1].fd = server_fd_;
  watcher[1].events = POLLIN;
  // Wait at most one minute.  This lets us detect case where
  // canary socket is closed abruptly because the main client aborted.
  // Also lets us reap dead children once a minute even if we don't get SIGCHLD.
  // We'd like to wait less time, but that's hard on battery life.
  // Note: handle EINTR manually here, not with wrapper, as we need
  // to return when we're interrupted so caller can reap promptly.
  int nactive = poll(watcher, 2, 60*1000);

  if (nactive == -1) {
    if (errno == EINTR) {
      LOG(INFO) << "poll interrupted";
      // Probably SIGCHLD.  Return to main loop so it can reap.
      return true;
    }
    LOG(ERROR) << "poll failed, errno " << errno << ", aborting";
    return false;
  }

  // If it was the canary, exit
  if (watcher[0].revents != 0) {
    LOG(INFO) << "notified of peer destruction, exiting";
    return false;
  }
  if ((watcher[1].revents & POLLIN) != POLLIN) {
    // spurious wakeup?
    return true;
  }

  ssize_t bytes_read = 0;
  struct msghdr msg = {0};
  struct iovec iov = {msg_buf_, kMAX_MSG_LEN};
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = cmsg_buf_;
  msg.msg_controllen = kMAX_CMSG_LEN;
  bytes_read = HANDLE_EINTR(recvmsg(server_fd_, &msg, MSG_WAITALL));
  if (bytes_read == 0) {
    LOG(ERROR) << "got EOF, aborting";
    return false;
  }
  if (bytes_read == -1) {
    LOG(ERROR) << "got errno " << errno << ", aborting";
    return false;
  }

  // Locate the sole block of sent file descriptors within the list of
  // control messages
  const int* wire_fds = NULL;
  unsigned num_wire_fds = 0;
  if (msg.msg_controllen > 0) {
    struct cmsghdr* cmsg;
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
         cmsg = CMSG_NXTHDR(&msg, cmsg)) {
      if (cmsg->cmsg_level == SOL_SOCKET &&
          cmsg->cmsg_type == SCM_RIGHTS) {
        const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
        assert(payload_len % sizeof(int) == 0);
        wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
        num_wire_fds = payload_len / sizeof(int);
        break;
      }
    }
  }
  DCHECK(!(msg.msg_flags & MSG_CTRUNC));

  // Unpickle/parse message
  Pickle pickle(msg_buf_, bytes_read);
  void* iter = NULL;
  std::string magic;
  if (!pickle.ReadString(&iter, &magic) || magic != std::string(kZMagic)) {
    LOG(ERROR) << "msg didn't start with " << kZMagic << ", got " << magic;
    for (unsigned i=0; i<num_wire_fds; i++)
      close(wire_fds[i]);
    return true;
  }
  pid_t clientpid = (pid_t) -1;
  pickle.ReadInt(&iter, &clientpid);
  int kind;
  pickle.ReadInt(&iter, &kind);

  Pickle reply;
  reply.WriteString(kZMagic);
  reply.WriteInt(clientpid);

  struct msghdr replymsg = {0};
  memset(&replymsg, 0, sizeof(replymsg));

  switch (kind) {
  case ZMPING:
    DCHECK_EQ(0U, num_wire_fds);
    PingHandler(pickle, iter, &reply, newargv);
    break;
  case ZMFORK:
    // TODO(dkegel): real error handling
    (void) LongForkHandler(pickle, iter, &reply, newargv, wire_fds,
                           num_wire_fds);
    if (*newargv != NULL) {
      // Child.  Just return to caller, who will return from SetLongFork.
      return true;
    }
    break;
  case ZMREAP:
    DCHECK_EQ(0U, num_wire_fds);
    EnsureProcessTerminatedHandler(pickle, iter);
    // no reply to this message
    return true;
  case ZMOPEN:
    DCHECK_EQ(0U, num_wire_fds);
    // TODO(dkegel): real error handling
    (void) OpenFileHandler(pickle, iter, &reply, &replymsg);
    break;
  default:
    // TODO(dkegel): real error handling
    LOG(ERROR) << "Unknown message kind " << kind;
    DCHECK_EQ(0U, num_wire_fds);
    break;
  }

  struct iovec riov = {const_cast<void *>(reply.data()), reply.size()};
  replymsg.msg_iov = &riov;
  replymsg.msg_iovlen = 1;

  int bytes_sent;
  bytes_sent = HANDLE_EINTR(sendmsg(server_fd_, &replymsg, MSG_WAITALL));
  if (bytes_sent != static_cast<int>(riov.iov_len)) {
    // TODO(dkegel): real error handling
    LOG(ERROR) << "Can't send reply.";
    return false;
  }
  return true;
}

// Called only by ChromeMain(), forks the zygote manager process.
std::vector<std::string>* ZygoteManager::Start() {
  DCHECK(lockfd_ == -1);
  DCHECK(canary_fd_ == -1);
  DCHECK(server_fd_ == -1);
  DCHECK(client_fd_ == -1);

  int pipe_fds[2];

  // Avoid using the reserved fd slots.
  int reserved_fds[kReservedFds];
  for (int i=0; i < kReservedFds; i++)
    reserved_fds[i] = open("/dev/null", O_RDONLY, 0);

  // Create the main communications pipe.
  int err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipe_fds));
  if (err != 0) {
    // TODO(dkegel): real error handling
    exit(99);
  }
  server_fd_ = pipe_fds[1];
  client_fd_ = pipe_fds[0];

  // Create the pipe used only to relay destruction event server.
  // Must be SOCK_STREAM so close() is sensed by poll().
  err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds));
  if (err != 0) {
    // TODO(dkegel): real error handling
    exit(99);
  }

  // Create lock file.
  // TODO(dkegel): get rid of this
  char lockfile[256];
  strcpy(lockfile, "/tmp/zygote_manager_lock.XXXXXX");
  lockfd_ = mkstemp(lockfile);
  if (lockfd_ == -1) {
    // TODO(dkegel): real error handling
    exit(99);
  }
  lockfile_.assign(lockfile);

  // Fork a fork server.
  pid_t childpid = fork();

  if (childpid) {
    for (int i=0; i < kReservedFds; i++)
      close(reserved_fds[i]);

    // Original parent.  Continues on with the main program
    // and becomes the first client.
    close(server_fd_);
    server_fd_ = -1;

    close(pipe_fds[1]);
    canary_fd_ = pipe_fds[0];

    // Return now to indicate this is the original process.
    return NULL;
  } else {
    close(lockfd_);

    close(pipe_fds[0]);
    canary_fd_ = pipe_fds[1];

    // We need to accept SIGCHLD, even though our handler is a no-op because
    // otherwise we cannot wait on children. (According to POSIX 2001.)
    // (And otherwise poll() might not wake up on SIGCHLD.)
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    action.sa_handler = SIGCHLDHandler;
    CHECK(sigaction(SIGCHLD, &action, NULL) == 0);

    // Original child.  Acts as the server.
    while (true) {
      std::vector<std::string>* newargv = NULL;
      if (!ReadAndHandleMessage(&newargv))
        break;
      if (newargv) {
        // Return new commandline to show caller this is a new child process.
        return newargv;
      }
      // Server process continues around loop.

      // Reap children.
      while (true) {
        int status = -1;
        pid_t reaped = waitpid(-1, &status, WNOHANG);
        if (reaped != -1 && reaped != 0) {
          LOG(INFO) << "Reaped pid " << reaped;
          continue;
        }
        break;
      }
    }
    // Server cleanup after EOF or error reading from the socket.
    // Chrome doesn't seem to get here in practice.
    Delete(FilePath(lockfile_), false);
    // TODO(dkegel): real error handling
    LOG(INFO) << "exiting.  " << cached_fds_.size() << " cached fds.";
    std::map<std::string, int>::iterator i;
    for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) {
      LOG(INFO) << "Closing fd " << i->second << " filename " << i->first;
      close(i->second);
    }
    exit(-1);
  }
}
}
#endif  // defined(OS_LINUX)