1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 *  firmwaresyncd.c
25 *  bless
26 *
27 *  Created by Shantonu Sen on 10/13/07.
28 *  Copyright 2007 Apple Inc. All rights reserved.
29 *
30 */
31
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <stdarg.h>
36#include <unistd.h>
37#include <getopt.h>
38#include <err.h>
39#include <spawn.h>
40#include <sys/stat.h>
41#include <sys/mount.h>
42#include <sys/fcntl.h>
43#include <sys/time.h>
44#include <syslog.h>
45#include <sysexits.h>
46#include <copyfile.h>
47#include <sys/sysctl.h>
48
49#include <DiskArbitration/DiskArbitration.h>
50#include <DiskArbitration/DiskArbitrationPrivate.h>
51
52#include <IOKit/kext/kextmanager_types.h>
53#include <IOKit/kext/kextmanager_mig.h>
54
55#include <mach/mach.h>
56#include <servers/bootstrap.h>
57#include <sys/resource.h>
58
59#include "bless.h"
60#include "bless_private.h"
61
62#define kFirmwareFileOSPath "/usr/standalone/i386/Firmware.scap"
63#define kFirmwareFileEFIDir "/EFI/APPLE/EXTENSIONS"
64#define kFirmwareFileEFIPath "/EFI/APPLE/EXTENSIONS/Firmware.scap"
65#define kTimeDelay (4*60)
66#define kTSCacheDir         "/System/Library/Caches/com.apple.bootstamps"
67
68extern char **environ;
69
70void usage(void);
71void catch_sigterm(int sig);
72
73static bool gSIGTERM = false;
74
75/* Should we even run? If not, exit out. If so, use the volume UUID for / */
76bool should_run(void);
77bool get_uuid(CFUUIDRef *uuid);
78bool allocate_mach_ports(mach_port_t *kextdport, mach_port_t *vollock);
79bool deallocate_mach_ports(mach_port_t kextdport, mach_port_t vollock);
80bool lock_volume(CFUUIDRef uuid, mach_port_t kextdport, mach_port_t vollock);
81bool unlock_volume(CFUUIDRef uuid, mach_port_t kextdport, mach_port_t vollock);
82bool generate_timestamp_path(CFUUIDRef uuid, char *path);
83bool check_if_uptodate(CFUUIDRef uuid);
84bool update_esp(void);
85bool update_timestamp(CFUUIDRef uuid);
86bool run_tool(char *argv[], CFDataRef *output);
87
88int main(int argc, char *argv[]) {
89
90    int ch;
91    int opt_d = 0;
92    CFUUIDRef uuid = NULL;
93    mach_port_t vollock = MACH_PORT_NULL, kextdport = MACH_PORT_NULL;
94    bool needunlock = false, immediately = false;
95    unsigned int sleepleft;
96
97    signal(SIGTERM, catch_sigterm);
98
99    while ((ch = getopt(argc, argv, "di")) != -1) {
100        switch (ch) {
101            case 'd':
102                opt_d = 1;
103                break;
104            case 'i':
105                immediately = true;
106                break;
107            case '?':
108            default:
109                usage();
110                break;
111        }
112    }
113
114    argc -= optind;
115    argv += optind;
116
117    openlog(getprogname(), LOG_PID | (opt_d ? LOG_PERROR : 0), LOG_DAEMON);
118    setlogmask(opt_d ? LOG_UPTO(LOG_DEBUG) : LOG_UPTO(LOG_ERR));
119
120//    syslog(LOG_INFO, "This is informational");
121//    syslog(LOG_DEBUG, "This is debuggingational");
122
123    // In general we try exit on failures without complaining
124    if (!should_run()) {
125        goto done;
126    }
127
128    syslog(LOG_DEBUG, "Preflight passed");
129
130    if (!get_uuid(&uuid)) {
131        goto done;
132    }
133
134    if (!check_if_uptodate(uuid)) {
135        goto done;
136    }
137
138    sleepleft = (immediately ? 0 : kTimeDelay);
139    syslog(LOG_DEBUG, "Sleeping for %u seconds", sleepleft);
140    do {
141        if (gSIGTERM) {
142            syslog(LOG_DEBUG, "Caught SIGTERM and exiting");
143            goto done;
144        }
145        sleepleft = sleep(sleepleft);
146    } while (sleepleft > 0);
147    syslog(LOG_DEBUG, "Done sleeping");
148
149    if (!allocate_mach_ports(&kextdport, &vollock)) {
150        goto done;
151    }
152
153    // relock, in case the state changed while we were asleep
154    if (!lock_volume(uuid, kextdport, vollock)) {
155        goto done;
156    }
157    needunlock = true;
158
159    if (!check_if_uptodate(uuid)) {
160        goto done;
161    }
162
163    if (!update_esp()) {
164        goto done;
165    }
166
167    if (!update_timestamp(uuid)) {
168        goto done;
169    }
170
171    if (!unlock_volume(uuid, kextdport, vollock)) {
172        goto done;
173    }
174    needunlock = false;
175
176
177done:
178    if (needunlock) {
179        unlock_volume(uuid, kextdport, vollock);
180    }
181    if (kextdport != MACH_PORT_NULL || vollock != MACH_PORT_NULL) {
182        deallocate_mach_ports(kextdport, vollock);
183    }
184    if (uuid) {
185        CFRelease(uuid);
186    }
187    closelog();
188    return 0;
189}
190
191void usage(void)
192{
193    fprintf(stderr, "Usage: %s [-d]\n", getprogname());
194    exit(EX_USAGE);
195}
196
197void catch_sigterm(int sig)
198{
199    gSIGTERM = true;
200}
201
202bool should_run(void)
203{
204    bool result = false;
205    BLPreBootEnvType preBootType;
206    struct stat sb;
207    uint32_t safeboot = 0;
208    size_t safebootsize = sizeof(safeboot);
209    int ret;
210
211    ret = sysctlbyname("kern.safeboot", &safeboot, &safebootsize, NULL, 0);
212    if (ret) {
213        syslog(LOG_DEBUG, "Could not determine safeboot status: %s", strerror(errno));
214        return result;
215    }
216    syslog(LOG_DEBUG, "Safeboot status: %u", safeboot);
217    if (safeboot) {
218        return result;
219    }
220
221    if (0 != BLGetPreBootEnvironmentType(NULL, &preBootType)) {
222        syslog(LOG_DEBUG, "Could not determine preboot environment type");
223        return result;
224    }
225    if (preBootType != kBLPreBootEnvType_EFI) {
226        syslog(LOG_DEBUG, "Preboot environment type is not EFI");
227        return result;
228    }
229
230    if (0 != lstat(kFirmwareFileOSPath, &sb) || !S_ISREG(sb.st_mode)) {
231        syslog(LOG_DEBUG, "Font file %s is not accessible or not a regular file", kFirmwareFileOSPath);
232        return result;
233    }
234
235    result = true;
236
237    return result;
238}
239
240
241static void _DADiskAppearedCallback( DADiskRef disk, void * context );
242
243static void _DADiskAppearedCallback( DADiskRef disk, void * context )
244{
245    CFUUIDRef *uuid = (CFUUIDRef *)context;
246    CFUUIDRef dauuid = NULL;
247    CFDictionaryRef dadescription;
248
249    dadescription = DADiskCopyDescription(disk);
250    if (dadescription) {
251        dauuid = CFDictionaryGetValue(dadescription, kDADiskDescriptionVolumeUUIDKey);
252        if (dauuid) {
253            if (*uuid) {
254                CFRelease(*uuid);
255            }
256            *uuid = CFRetain(dauuid);
257        }
258        CFRelease(dadescription);
259    }
260
261}
262
263bool get_uuid(CFUUIDRef *uuid)
264{
265    DASessionRef dasession;
266    CFURLRef rootpath;
267    CFMutableDictionaryRef matchdict;
268    bool result = false;
269
270    *uuid = NULL;
271
272    rootpath = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)"/", 1, true);
273    if (rootpath) {
274        dasession = DASessionCreate(kCFAllocatorDefault);
275        if (dasession) {
276            DASessionScheduleWithRunLoop(dasession, CFRunLoopGetCurrent(), CFSTR("FIRMWARESYNCD"));
277
278            matchdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
279            CFDictionaryAddValue(matchdict, kDADiskDescriptionVolumePathKey, rootpath);
280            DARegisterDiskAppearedCallback(dasession, matchdict, _DADiskAppearedCallback, uuid);
281            CFRelease(matchdict);
282
283            while (!gSIGTERM && !*uuid) {
284                CFRunLoopRunInMode(CFSTR("FIRMWARESYNCD"), 1.0, true);
285            }
286            DASessionUnscheduleFromRunLoop(dasession, CFRunLoopGetCurrent(), CFSTR("FIRMWARESYNCD"));
287            CFRelease(dasession);
288        }
289        CFRelease(rootpath);
290    }
291    if (*uuid) {
292        result = true;
293        syslog(LOG_DEBUG, "Determined volume UUID for /");
294    } else {
295        syslog(LOG_DEBUG, "Could not determine volume UUID for /");
296    }
297
298    return result;
299}
300
301bool lock_volume(CFUUIDRef uuid, mach_port_t kextdport, mach_port_t vollock)
302{
303    bool result = false;
304    kern_return_t kret;
305
306    uuid_t s_vol_uuid;
307    CFUUIDBytes uuidbytes;
308    int lckres = 0;
309
310    uuidbytes = CFUUIDGetUUIDBytes(uuid);
311    memcpy(&s_vol_uuid, &uuidbytes, sizeof(uuidbytes));
312
313    syslog(LOG_DEBUG, "Locking volume");
314
315    kret = kextmanager_lock_volume(kextdport, vollock, s_vol_uuid,
316                                      1 /* block */, &lckres);
317	if (kret || lckres) {
318        syslog(LOG_DEBUG, "Failed to obtain lock: %s/%s", mach_error_string(kret), strerror(lckres));
319        goto done;
320    }
321
322    result = true;
323
324done:
325    return result;
326}
327
328bool unlock_volume(CFUUIDRef uuid, mach_port_t kextdport, mach_port_t vollock)
329{
330    bool result = false;
331    kern_return_t kret;
332
333    uuid_t s_vol_uuid;
334    CFUUIDBytes uuidbytes;
335
336    uuidbytes = CFUUIDGetUUIDBytes(uuid);
337    memcpy(&s_vol_uuid, &uuidbytes, sizeof(uuidbytes));
338
339    syslog(LOG_DEBUG, "Unlocking volume");
340
341    kret = kextmanager_unlock_volume(kextdport, vollock, s_vol_uuid,
342                                   0);
343	if (kret) {
344        syslog(LOG_DEBUG, "Failed to unlock: %s", mach_error_string(kret));
345        goto done;
346    }
347
348    result = true;
349
350done:
351    return result;
352}
353
354bool allocate_mach_ports(mach_port_t *kextdport, mach_port_t *vollock)
355{
356    bool result = false;
357    kern_return_t kret;
358
359    mach_port_t sLockPort = MACH_PORT_NULL;
360    mach_port_t sKextdPort = MACH_PORT_NULL;
361
362    syslog(LOG_DEBUG, "Obtaining mach ports");
363
364    if (sKextdPort == MACH_PORT_NULL) {
365        kret = bootstrap_look_up(bootstrap_port, KEXTD_SERVER_NAME, &sKextdPort);
366        if (kret) {
367            syslog(LOG_DEBUG, "Failed to look up port for %s: %s", KEXTD_SERVER_NAME, mach_error_string(kret));
368            goto done;
369        }
370    }
371
372    if (sLockPort == MACH_PORT_NULL) {
373        kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sLockPort);
374        if (kret) {
375            syslog(LOG_DEBUG, "Failed to look up port for %s: %s", KEXTD_SERVER_NAME, mach_error_string(kret));
376            goto done;
377        }
378    }
379
380
381    result = true;
382    *kextdport = sKextdPort;
383    *vollock = sLockPort;
384
385done:
386    return result;
387}
388
389bool deallocate_mach_ports(mach_port_t kextdport, mach_port_t vollock)
390{
391    bool result = false;
392    kern_return_t kret;
393
394    syslog(LOG_DEBUG, "Deallocating mach ports");
395
396    if (kextdport != MACH_PORT_NULL) {
397        kret = mach_port_mod_refs(mach_task_self(), kextdport, MACH_PORT_RIGHT_SEND, -1);
398        if (kret) {
399            syslog(LOG_DEBUG, "Failed to drop reference for %s: %s", KEXTD_SERVER_NAME, mach_error_string(kret));
400            goto done;
401        }
402    }
403
404    if (vollock != MACH_PORT_NULL) {
405        kret = mach_port_mod_refs(mach_task_self(), vollock, MACH_PORT_RIGHT_RECEIVE, -1);
406        if (kret) {
407            syslog(LOG_DEBUG, "Failed to drop reference for lock %s", mach_error_string(kret));
408            goto done;
409        }
410    }
411
412    result = true;
413
414done:
415    return result;
416}
417
418bool generate_timestamp_path(CFUUIDRef uuid, char *path)
419{
420    char timepath[MAXPATHLEN];
421    char colonpath[MAXPATHLEN], *cptr;
422    char uuidcomponent[NAME_MAX];
423    CFStringRef uuidstr;
424    bool result = false;
425
426    uuidstr = CFUUIDCreateString(kCFAllocatorDefault, uuid);
427    if (!CFStringGetCString(uuidstr, uuidcomponent, sizeof(uuidcomponent), kCFStringEncodingUTF8)) {
428        CFRelease(uuidstr);
429        goto done;
430    }
431    CFRelease(uuidstr);
432
433    strlcpy(colonpath, kFirmwareFileOSPath, sizeof(colonpath));
434    cptr = strchr(colonpath, '/');
435    while (cptr) {
436        *cptr++ = ':';
437        cptr = strchr(cptr, '/');
438    }
439
440    // we check for overflow on the last operation, since it should be cumulative
441    strlcpy(timepath, kTSCacheDir, sizeof(timepath));
442    strlcat(timepath, "/", sizeof(timepath));
443    strlcat(timepath, uuidcomponent, sizeof(timepath));
444    strlcat(timepath, "/", sizeof(timepath));
445    if (strlcat(timepath, colonpath, sizeof(timepath)) >= sizeof(timepath)) {
446        goto done;
447    }
448
449    strlcpy(path, timepath, MAXPATHLEN);
450    result = true;
451
452done:
453    return result;
454}
455
456
457/* Check in /S/L/C if we have a cookie file newer than the font file */
458bool check_if_uptodate(CFUUIDRef uuid)
459{
460    bool result = false, needupdate = false;
461    struct stat sb, sb2;
462    int ret;
463    char timepath[MAXPATHLEN];
464
465    // ...
466    if (0 != lstat(kFirmwareFileOSPath, &sb)) {
467        syslog(LOG_DEBUG, "Could not access %s: %s", kFirmwareFileOSPath, strerror(errno));
468        goto done;
469    }
470
471    if (!S_ISREG(sb.st_mode)) {
472        syslog(LOG_DEBUG, "%s is not a regular file", kFirmwareFileOSPath);
473        goto done;
474    }
475
476    if (!generate_timestamp_path(uuid, timepath)) {
477        syslog(LOG_DEBUG, "Could not generate path (%s)", timepath);
478        goto done;
479    }
480
481    syslog(LOG_DEBUG, "Timestamp file is %s", timepath);
482    ret = lstat(timepath, &sb2);
483    if (ret) {
484        if (errno == ENOENT) {
485            syslog(LOG_DEBUG, "Timestamp does not exist");
486            needupdate = true;
487        } else {
488            ret = unlink(timepath);
489            if (ret) {
490                syslog(LOG_DEBUG, "Could not remove %s", timepath);
491                goto done;
492            }
493            needupdate = true;
494        }
495    } else {
496        struct timeval filetime, stamptime;
497
498        if (!S_ISREG(sb2.st_mode)) {
499            syslog(LOG_DEBUG, "%s is not a regular file", timepath);
500            goto done;
501        }
502
503        TIMESPEC_TO_TIMEVAL(&filetime, &sb.st_mtimespec);
504        TIMESPEC_TO_TIMEVAL(&stamptime, &sb2.st_mtimespec);
505
506        if (timercmp(&filetime, &stamptime, >)) {
507            syslog(LOG_DEBUG, "File newer than timestamp");
508            needupdate = true;
509        } else {
510            syslog(LOG_DEBUG, "File matches timestamp");
511            needupdate = false;
512        }
513    }
514
515    if (needupdate) {
516        result = true;
517    }
518
519done:
520    return result;
521}
522
523bool update_esp(void)
524{
525    bool result = false, needunmount = false, needrmdir = false;
526    struct statfs sb;
527    int ret;
528    CFDictionaryRef dict = NULL;
529    CFArrayRef array = NULL;
530    CFStringRef esp = NULL;
531    char *newargv[10];
532    int newargc;
533    char *slash;
534    char espname[MAXPATHLEN], espdev[MAXPATHLEN], mntpath[MAXPATHLEN], espfontpath[MAXPATHLEN];
535    CFDataRef output = NULL;
536
537    ret = statfs("/", &sb);
538    if (ret) {
539        syslog(LOG_DEBUG, "Failed to statfs /: %s", strerror(errno));
540        goto done;
541    }
542
543    if (0 != strncmp(sb.f_mntfromname, "/dev/", 5)) {
544        goto done;
545    }
546
547    ret = BLCreateBooterInformationDictionary(NULL, sb.f_mntfromname + 5, &dict);
548    if (ret) {
549        syslog(LOG_DEBUG, "Failed to obtain EFI System Partition information: %d", ret);
550        goto done;
551    }
552
553    array = CFDictionaryGetValue(dict, kBLSystemPartitionsKey);
554    if(array) {
555        if(CFArrayGetCount(array) > 0) {
556            esp = CFArrayGetValueAtIndex(array, 0);
557
558            if (!BLIsEFIRecoveryAccessibleDevice(NULL, esp)) {
559                syslog(LOG_DEBUG, "ESP %s is not accessible as a recovery device\n" , BLGetCStringDescription(esp));
560                goto done;
561            }
562        }
563    }
564
565    if(!esp) {
566        // needed ESP, but could not find it
567        syslog(LOG_DEBUG, "No appropriate ESP for %s\n" , "/");
568        goto done;
569    }
570
571    if (!CFStringGetCString(esp, espname, sizeof(espname), kCFStringEncodingUTF8)) {
572        goto done;
573    }
574
575    CFRelease(dict);
576    dict = NULL;
577    esp = NULL;
578
579    strlcpy(espdev, "/dev/", sizeof(espdev));
580    strlcat(espdev, espname, sizeof(espdev));
581    syslog(LOG_DEBUG, "ESP partition is %s", espdev);
582
583
584    syslog(LOG_DEBUG, "Verifying %s", espdev);
585
586    newargc = 0;
587    newargv[newargc++] = "/sbin/fsck_msdos";
588    newargv[newargc++] = "-fn";
589    newargv[newargc++] = espdev;
590    newargv[newargc++] = NULL;
591
592    if (!run_tool(newargv, &output)) {
593        if (output) {
594            syslog(LOG_ERR, "Command %s output: %.*s", newargv[0], (int)CFDataGetLength(output), CFDataGetBytePtr(output));
595            CFRelease(output);
596        }
597        goto done;
598    }
599    if (output) {
600        CFRelease(output);
601    }
602
603    strlcpy(mntpath, "/Volumes/firmwaresyncd.XXXXXX", sizeof(mntpath));
604    if(!mkdtemp(mntpath)) {
605        syslog(LOG_DEBUG, "Could not make temporary directory %s", mntpath);
606        goto done;
607    }
608    needrmdir = true;
609    syslog(LOG_DEBUG, "Temporary mount point is %s", mntpath);
610
611    newargc = 0;
612    newargv[newargc++] = "/sbin/mount";
613    newargv[newargc++] = "-t";
614    newargv[newargc++] = "msdos";
615    newargv[newargc++] = "-o";
616    newargv[newargc++] = "perm";
617    newargv[newargc++] = "-o";
618    newargv[newargc++] = "nobrowse";
619    newargv[newargc++] = espdev;
620    newargv[newargc++] = mntpath;
621    newargv[newargc++] = NULL;
622
623    if (!run_tool(newargv, &output)) {
624        if (output) {
625            syslog(LOG_ERR, "Command %s output: %.*s", newargv[0], (int)CFDataGetLength(output), CFDataGetBytePtr(output));
626            CFRelease(output);
627        }
628        goto done;
629    }
630    if (output) {
631        CFRelease(output);
632    }
633
634    needunmount = true;
635
636    strlcpy(espfontpath, mntpath, sizeof(espfontpath));
637    strlcat(espfontpath, kFirmwareFileEFIDir, sizeof(espfontpath));
638
639    // make parent directories if they are not present
640    slash = espfontpath + strlen(mntpath);
641    while (strsep(&slash, "/")) {
642        syslog(LOG_DEBUG, "Checking %s", espfontpath);
643        ret = mkdir(espfontpath, S_IRWXU);
644        if (ret && errno != EEXIST) {
645            syslog(LOG_DEBUG, "Failed to create %s: %s", espfontpath, strerror(errno));
646            break;
647        }
648        if (slash == NULL) {
649            break;
650        }
651        *(slash - 1) = '/';
652    }
653
654    strlcpy(espfontpath, mntpath, sizeof(espfontpath));
655    strlcat(espfontpath, kFirmwareFileEFIPath, sizeof(espfontpath));
656
657    ret = copyfile(kFirmwareFileOSPath, espfontpath, NULL, COPYFILE_DATA);
658    if (ret) {
659        syslog(LOG_DEBUG, "Could not copy %s to %s: %d", kFirmwareFileOSPath, espfontpath, ret);
660        goto done;
661    }
662    syslog(LOG_DEBUG, "Copied %s to %s", kFirmwareFileOSPath, espfontpath);
663
664    result = true;
665
666done:
667    if (needunmount) {
668        newargc = 0;
669        newargv[newargc++] = "/sbin/umount";
670        newargv[newargc++] = mntpath;
671        newargv[newargc++] = NULL;
672
673        if (!run_tool(newargv, &output)) {
674            if (output) {
675                syslog(LOG_ERR, "Command %s output: %.*s", newargv[0], (int)CFDataGetLength(output), CFDataGetBytePtr(output));
676                CFRelease(output);
677            }
678            goto done2;
679        }
680        if (output) {
681            CFRelease(output);
682        }
683
684    }
685
686done2:
687    if (needrmdir) {
688        syslog(LOG_DEBUG, "Removing %s\n", mntpath);
689
690        ret = rmdir(mntpath);
691    }
692
693    if (dict) {
694        CFRelease(dict);
695    }
696    return result;
697}
698
699bool update_timestamp(CFUUIDRef uuid)
700{
701    bool result = false, closefd = false;;
702    char timepath[MAXPATHLEN];
703    int ret, tfd;
704    struct stat sb;
705    struct timeval times[2];
706
707    if (0 != lstat(kFirmwareFileOSPath, &sb)) {
708        syslog(LOG_DEBUG, "Could not access %s: %s", kFirmwareFileOSPath, strerror(errno));
709        goto done;
710    }
711
712    if (!S_ISREG(sb.st_mode)) {
713        syslog(LOG_DEBUG, "%s is not a regular file", kFirmwareFileOSPath);
714        goto done;
715    }
716
717    if (!generate_timestamp_path(uuid, timepath)) {
718        syslog(LOG_DEBUG, "Could not generate path (%s)", timepath);
719        goto done;
720    }
721
722    syslog(LOG_DEBUG, "Updating timestamp file %s", timepath);
723    ret = unlink(timepath);
724    if (ret && errno != ENOENT) {
725        syslog(LOG_DEBUG, "Could not remove %s: %s", timepath, strerror(errno));
726        goto done;
727    }
728
729    tfd = open(timepath, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
730    if (tfd < 0) {
731        syslog(LOG_DEBUG, "Could not create %s: %s", timepath, strerror(errno));
732        goto done;
733    }
734    closefd = true;
735
736    TIMESPEC_TO_TIMEVAL(&times[0], &sb.st_atimespec);
737    TIMESPEC_TO_TIMEVAL(&times[1], &sb.st_mtimespec);
738
739    ret = futimes(tfd, times);
740    if (ret) {
741        syslog(LOG_DEBUG, "Could not set time for %s: %s", timepath, strerror(errno));
742        goto done;
743    }
744
745    result = true;
746
747done:
748    if (closefd) {
749        ret = close(tfd);
750        if (ret) {
751            syslog(LOG_DEBUG, "Could not close %s: %s", timepath, strerror(errno));
752        }
753    }
754
755    return result;
756
757}
758
759bool run_tool(char *argv[], CFDataRef *output)
760{
761    bool result = false, destroyFileActions = false, closeFDs = false;
762    pid_t p, p2;
763    int ret;
764    CFMutableDataRef dataRef;
765    int fds[2];
766    posix_spawn_file_actions_t file_actions;
767    char buffer[100];
768    ssize_t readBytes;
769
770    dataRef = CFDataCreateMutable(kCFAllocatorDefault, 0);
771
772    ret = pipe(fds);
773    if (ret) {
774        syslog(LOG_DEBUG, "Could not call posix_spawn: %d", errno);
775        goto done;
776    }
777    closeFDs = true;
778
779    ret = posix_spawn_file_actions_init(&file_actions);
780    if (ret < 0) {
781        syslog(LOG_DEBUG, "Could not call posix_spawn_file_actions_init: %d", ret);
782        goto done;
783    }
784    destroyFileActions = true;
785
786    posix_spawn_file_actions_addclose(&file_actions, fds[0]);
787    if (fds[1] != STDOUT_FILENO) {
788        (void)posix_spawn_file_actions_adddup2(&file_actions, fds[1], STDOUT_FILENO);
789        if (fds[1] != STDERR_FILENO) {
790            (void)posix_spawn_file_actions_adddup2(&file_actions, fds[1], STDERR_FILENO);
791        }
792        (void)posix_spawn_file_actions_addclose(&file_actions, fds[1]);
793    }
794
795    syslog(LOG_DEBUG, "Calling %s\n", argv[0]);
796    ret = posix_spawn(&p, argv[0], &file_actions, NULL, argv, environ);
797    if (ret) {
798        syslog(LOG_DEBUG, "Could not call posix_spawn: %d", ret);
799        goto done;
800    }
801
802    // read 100 bytes at a time until we get EOF (child closed pipe);
803    close(fds[1]);
804    fds[1] = -1;
805    do {
806        readBytes = read(fds[0], buffer, sizeof(buffer));
807        syslog(LOG_DEBUG, "Read %ld from pipe", readBytes);
808        if (readBytes > 0) {
809            CFDataAppendBytes(dataRef, (UInt8 *)buffer, readBytes);
810        }
811    } while (readBytes > 0);
812
813    if (readBytes < 0) {
814        syslog(LOG_DEBUG, "read returned error: %d\n", errno );
815        goto done;
816    }
817
818    do {
819        p2 = waitpid(p, &ret, 0);
820    } while (p2 == -1 && errno == EINTR);
821
822    syslog(LOG_DEBUG, "Returned %d\n", ret);
823    if(p2 == -1) {
824        syslog(LOG_DEBUG, "%s failed to return: %d\n", argv[0], errno );
825        goto done;
826    }
827    if(ret) {
828        if (WIFEXITED(ret)) {
829            syslog(LOG_ERR, "%s exited with %d\n", argv[0], WEXITSTATUS(ret) );
830        } else {
831            syslog(LOG_ERR, "%s signaled with %d\n", argv[0], WTERMSIG(ret) );
832        }
833        goto done;
834    }
835
836    result = true;
837
838done:
839
840    if (destroyFileActions) {
841        posix_spawn_file_actions_destroy(&file_actions);
842    }
843
844    if (closeFDs) {
845        close(fds[0]);
846        close(fds[1]);
847    }
848
849    *output = dataRef;
850
851    return result;
852}
853
854