1#include <stdlib.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <strings.h>
5#include <errno.h>
6#include <err.h>
7#include <fcntl.h>
8#include <sysexits.h>
9#include <sys/types.h>
10#include <sys/param.h>
11#include <sys/wait.h>
12#include <pwd.h>
13#include <sys/stat.h>
14#include <sys/mman.h>
15#include <mach-o/fat.h>
16#include <mach-o/loader.h>
17#include <mach-o/arch.h>
18
19#include "dumpemacs.h"
20#include "bo.h"			/* generated during build */
21#include "src/version.h"
22
23void usage(void);
24int dumpemacs(int debugflag, char *output);
25int copythintemacs(int debugflag, const char *src, const char *dst);
26void *mmaparch(const char *file, size_t *psize);
27char *verfind(void *, size_t, char, const char *);
28
29int main(int argc, char *argv[]) {
30
31  int debugopt = 0, verboseopt = 0, testopt = 0, forceopt = 0;
32  char output[MAXPATHLEN];
33  int ch;
34  int ret, fd;
35
36  umask(022);
37  while ((ch = getopt(argc, argv, "Vdfnv")) != -1) {
38    switch (ch) {
39    case 'd':
40      debugopt = 1;
41      verboseopt = 1;
42      break;
43    case 'v':
44      verboseopt = 1;
45      break;
46    case 'n':
47      testopt = 1;
48      break;
49    case 'f':
50      forceopt = 1;
51      break;
52    case 'V':
53      puts(kEmacsVersion);
54      exit(0);
55      break;
56    default:
57      usage();
58      break;
59    }
60  }
61
62  if(!testopt) {
63    // must be run as root unless we're not planning on writing
64    if(geteuid() != 0)
65      errx(1, "Must be run as root unless -n is used");
66  }
67
68
69
70  if(!verboseopt) {
71    fd = open("/dev/null", O_RDWR, 0600);
72    if(fd < 0)
73      err(1, "open(/dev/null)");
74
75    ret = dup2(fd, STDIN_FILENO);
76    if(ret == -1)
77      err(1, "dup2(/dev/null, stdin)");
78
79    ret = dup2(fd, STDOUT_FILENO);
80    if(ret == -1)
81      err(1, "dup2(/dev/null, stdout)");
82
83    ret = dup2(fd, STDERR_FILENO);
84    if(ret == -1)
85      err(1, "dup2(/dev/null, stderr)");
86
87    ret = close(fd);
88    if(ret == -1)
89      err(1, "close(/dev/null)");
90  }
91
92  if (!forceopt) {
93    int dumpit = 1;
94    char *dumpedVersion = NULL;
95    char *undumpedVersion = NULL;
96    size_t dumpedSize, undumpedSize;
97
98    void *dumpedMem = mmaparch(kEmacsDumpedPath, &dumpedSize);
99    if (dumpedMem) {
100      /* Break up @(#) to avoid false tags in this binary */
101      dumpedVersion = verfind(dumpedMem, dumpedSize, '@', "(#) emacs");
102      munmap(dumpedMem, dumpedSize);
103    }
104    if (dumpedVersion) {
105      void *undumpedMem = mmaparch(kEmacsUndumpedPath, &undumpedSize);
106      if (undumpedMem) {
107	undumpedVersion = verfind(undumpedMem, undumpedSize, '@', "(#) emacs");
108	munmap(undumpedMem, undumpedSize);
109      }
110    }
111    if (dumpedVersion != NULL && undumpedVersion != NULL &&
112	(0 == strcmp(dumpedVersion, undumpedVersion))) {
113      dumpit = 0;
114    }
115    if (dumpedVersion) free(dumpedVersion);
116    if (undumpedVersion) free(undumpedVersion);
117    if (!dumpit)
118      return 0;
119  }
120  ret = dumpemacs(debugopt, output);
121  if(ret != 0)
122    errx(1, "Failed to dump native emacs");
123
124  if(testopt) {
125    printf("emacs successfully dumped. Test mode successful.\n");
126    return 0;
127  }
128
129  ret = chown(output, 0, 0); // reset to root:wheel
130  if(ret)
131    err(1, "chown(%s)", output);
132
133  const char * newargs[5];
134  newargs[0] = "/bin/cp";
135  newargs[1] = "-p";
136  newargs[2] = output;
137  newargs[3] = kEmacsDumpedPath;
138  newargs[4] = NULL;
139
140  if(debugopt) printf("Installing dumped emacs\n");
141  ret = runit(newargs, 0);
142  if(ret)
143    errx(1, "Failed to install dumped emacs");
144
145  return 0;
146
147}
148
149void usage(void)
150{
151  fprintf(stderr, "Usage: %s [-d] [-f] [-n] [-v] [-V]\n", getprogname());
152  exit(EX_USAGE);
153}
154
155
156
157int dumpemacs(int debugflag, char *output)
158{
159  char tempdir[MAXPATHLEN], newpath[MAXPATHLEN];
160  char *tmp = NULL;
161  int ret, fd;
162  struct passwd *nobody = NULL;
163  uid_t nobodyUID = 0;
164
165  nobody = getpwnam("nobody");
166  if(nobody == NULL)
167 	err(1, "Don't know about nobody");
168
169  nobodyUID = nobody->pw_uid;
170
171  tmp = "/tmp";
172
173  snprintf(tempdir, sizeof(tempdir), "%s/emacs.XXXXXX", tmp);
174
175  if(debugflag) printf("Generating random directory with template %s\n", tempdir);
176  if(NULL == mkdtemp(tempdir))
177    err(1, "mkdtemp(%s) failed", tempdir);
178  chown(tempdir, nobodyUID, 0);
179  if(debugflag) printf("Directory is %s\n", tempdir);
180
181  snprintf(newpath, sizeof(newpath), "%s/etc", tempdir);
182  if(debugflag) printf("Making directory %s\n", newpath);
183  ret = mkdir(newpath,  S_IRWXU);
184  if(ret)
185    err(1, "mkdir(%s)", newpath);
186  chown(newpath, nobodyUID, 0);
187
188  snprintf(newpath, sizeof(newpath), "%s/lib-src", tempdir);
189  if(debugflag) printf("Making directory %s\n", newpath);
190  ret = mkdir(newpath,  S_IRWXU);
191  if(ret)
192    err(1, "mkdir(%s)", newpath);
193  chown(newpath, nobodyUID, 0);
194
195  snprintf(newpath, sizeof(newpath), "%s/src", tempdir);
196  if(debugflag) printf("Making directory %s\n", newpath);
197  ret = mkdir(newpath,  S_IRWXU);
198  if(ret)
199    err(1, "mkdir(%s)", newpath);
200  chown(newpath, nobodyUID, 0);
201
202  snprintf(newpath, sizeof(newpath), "%s/etc/GNU", tempdir);
203  if(debugflag) printf("Making symlink %s -> %s\n",
204		       newpath, kEmacsShareDir "/" kEmacsVersion "/etc/GNU");
205  ret = symlink(kEmacsShareDir "/" kEmacsVersion "/etc/GNU", newpath);
206  if(ret)
207    err(1, "symlink(%s)", newpath);
208
209  snprintf(newpath, sizeof(newpath), "%s/etc/DOC", tempdir);
210  if(debugflag) printf("Making symlink %s -> %s\n",
211		       newpath, kEmacsShareDir "/" kEmacsVersion "/etc/DOC-" kEmacsVersion "." kEmacsVersionMinor);
212  ret = symlink(kEmacsShareDir "/" kEmacsVersion "/etc/DOC-" kEmacsVersion "." kEmacsVersionMinor, newpath);
213  if(ret)
214    err(1, "symlink(%s)", newpath);
215
216  snprintf(newpath, sizeof(newpath), "%s/src/temacs", tempdir);
217  ret = copythintemacs(debugflag, kEmacsUndumpedPath, newpath);
218  if(ret)
219    errx(1, "copythintemacs() failed");
220
221  snprintf(newpath, sizeof(newpath), "%s/src", tempdir);
222  ret = chdir(newpath);
223  if(ret)
224    err(1, "chdir(%s)", newpath);
225  /* see emacs/src/doc.c */
226  fd = open("buildobj.lst", O_CREAT|O_WRONLY, 0444);
227  if(fd < 0)
228    err(1, "open(buildobj.lst)");
229  if (-1 == write(fd, bo, sizeof(bo)))
230    err(1, "write to buildobj.lst");
231  close(fd);
232
233  ret = setenv("LC_ALL", "C", 1);
234  if(ret)
235    err(1, "setenv(LC_ALL, C)");
236
237  const char *newargs[6];
238  newargs[0] = "./temacs";
239  newargs[1] = "-batch";
240  newargs[2] = "-l";
241  newargs[3] = "loadup";
242  newargs[4] = "dump";
243  newargs[5] = NULL;
244
245  if(debugflag) printf("Attempting to dump emacs\n");
246  ret = runit(newargs, 1);
247  if(ret)
248    errx(1, "Failed to dump emacs");
249
250  snprintf(output, MAXPATHLEN, "%s/src/emacs", tempdir);
251  if(debugflag) printf("emacs dumped as %s\n", output);
252
253
254  return 0;
255}
256
257int copythintemacs(int debugflag, const char *src, const char *dst)
258{
259  int fd;
260  int ret;
261  char buffer[4096];
262  struct fat_header *fh = (struct fat_header *)buffer;
263  struct fat_arch fakearch;
264  struct fat_arch *archs = NULL, *bestArch = NULL;
265  int archCount = 0;
266  ssize_t readBytes;
267  int isFat = 0;
268  const NXArchInfo *thisArch = NULL;
269
270  bzero(&fakearch, sizeof(fakearch));
271
272  fd = open(src, O_RDONLY, 0400);
273  if(fd < 0)
274    err(1, "open(%s)", src);
275
276  readBytes = read(fd, buffer, sizeof(buffer));
277  if(readBytes != sizeof(buffer))
278    err(1, "read failed");
279
280  ret = close(fd);
281  if(ret)
282    err(1, "close(%s)", src);
283
284  if(fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM) {
285    int i;
286
287    archs = (struct fat_arch *)(fh + 1);
288
289    fh->magic = OSSwapBigToHostInt32(fh->magic);
290    fh->nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
291    if(fh->nfat_arch >= 0x10000)
292      errx(1, "Illegal fat header");
293
294    for(i=0; i < fh->nfat_arch; i++) {
295      archs[i].cputype = OSSwapBigToHostInt32(archs[i].cputype);
296      archs[i].cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
297      archs[i].offset = OSSwapBigToHostInt32(archs[i].offset);
298      archs[i].size = OSSwapBigToHostInt32(archs[i].size);
299      archs[i].align = OSSwapBigToHostInt32(archs[i].align);
300    }
301    isFat = 1;
302    archCount = fh->nfat_arch;
303  } else if(fh->magic == MH_MAGIC) {
304    struct mach_header *mh = (struct mach_header *)buffer;
305    fakearch.cputype = mh->cputype;
306    fakearch.cpusubtype = mh->cpusubtype;
307    fakearch.offset = 0;
308    fakearch.size = 0;
309    fakearch.align = 0;
310    archs = &fakearch;
311    archCount = 1;
312  } else if(fh->magic == MH_CIGAM) {
313    struct mach_header *mh = (struct mach_header *)buffer;
314    fakearch.cputype = OSSwapInt32(mh->cputype);
315    fakearch.cpusubtype = OSSwapInt32(mh->cpusubtype);
316    fakearch.offset = 0;
317    fakearch.size = 0;
318    fakearch.align = 0;
319    archs = &fakearch;
320    archCount = 1;
321  } else if(fh->magic == MH_MAGIC_64) {
322    struct mach_header_64 *mh = (struct mach_header_64 *)buffer;
323    fakearch.cputype = mh->cputype;
324    fakearch.cpusubtype = mh->cpusubtype;
325    fakearch.offset = 0;
326    fakearch.size = 0;
327    fakearch.align = 0;
328    archs = &fakearch;
329    archCount = 1;
330  } else if(fh->magic == MH_CIGAM_64) {
331    struct mach_header_64 *mh = (struct mach_header_64 *)buffer;
332    fakearch.cputype = OSSwapInt32(mh->cputype);
333    fakearch.cpusubtype = OSSwapInt32(mh->cpusubtype);
334    fakearch.offset = 0;
335    fakearch.size = 0;
336    fakearch.align = 0;
337    archs = &fakearch;
338    archCount = 1;
339  }
340
341  thisArch = NXGetArchInfoFromName(kEmacsArch);
342  if(thisArch == NULL)
343    errx(1, "Unknown architecture: %s", kEmacsArch);
344
345  bestArch = NXFindBestFatArch(thisArch->cputype,
346			       thisArch->cpusubtype,
347			       archs, archCount);
348  if(bestArch == NULL)
349    errx(1, "No appropriate architecture in %s", src);
350  else
351    thisArch = NXGetArchInfoFromCpuType(bestArch->cputype, bestArch->cpusubtype);
352
353  // we need to copy it to dst, either as-is, or thinning
354  if(!isFat) {
355    const char * newargs[5];
356    newargs[0] = "/bin/cp";
357    newargs[1] = "-p";
358    newargs[2] = src;
359    newargs[3] = dst;
360    newargs[4] = NULL;
361
362    if(debugflag) printf("Copying %s to %s\n", src, dst);
363    ret = runit(newargs, 0);
364    if(ret)
365      errx(1, "copying failed");
366
367  } else {
368    const char * newargs[7];
369    newargs[0] = "/usr/bin/lipo";
370    newargs[1] = src;
371    newargs[2] = "-thin";
372    newargs[3] = thisArch->name;
373    newargs[4] = "-output";
374    newargs[5] = dst;
375    newargs[6] = NULL;
376
377    if(debugflag) printf("Thinning %s to %s\n", src, dst);
378    ret = runit(newargs, 0);
379    if(ret)
380      errx(1, "thinning failed");
381
382  }
383
384  return 0;
385}
386
387void *mmaparch(const char *filename, size_t *psize) {
388  int fd;
389  int ret;
390  char buffer[4096];
391  struct fat_header *fh = (struct fat_header *)buffer;
392  struct fat_arch fakearch;
393  struct fat_arch *archs = NULL, *bestArch = NULL;
394  int archCount = 0;
395  ssize_t readBytes;
396  int isFat = 0;
397  const NXArchInfo *thisArch = NULL;
398  off_t offset;
399
400  bzero(&fakearch, sizeof(fakearch));
401
402  fd = open(filename, O_RDONLY, 0400);
403  if (fd < 0)
404    return NULL;
405
406  readBytes = read(fd, buffer, sizeof(buffer));
407  if(readBytes != sizeof(buffer)) {
408    close(fd);
409    return NULL;
410  }
411
412
413  if(fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM) {
414    int i;
415
416    archs = (struct fat_arch *)(fh + 1);
417
418    fh->magic = OSSwapBigToHostInt32(fh->magic);
419    fh->nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
420    if(fh->nfat_arch >= 0x10000)
421      errx(1, "Illegal fat header");
422
423    for(i=0; i < fh->nfat_arch; i++) {
424      archs[i].cputype = OSSwapBigToHostInt32(archs[i].cputype);
425      archs[i].cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
426      archs[i].offset = OSSwapBigToHostInt32(archs[i].offset);
427      archs[i].size = OSSwapBigToHostInt32(archs[i].size);
428      archs[i].align = OSSwapBigToHostInt32(archs[i].align);
429    }
430    isFat = 1;
431    archCount = fh->nfat_arch;
432  } else if(fh->magic == MH_MAGIC) {
433    struct mach_header *mh = (struct mach_header *)buffer;
434    fakearch.cputype = mh->cputype;
435    fakearch.cpusubtype = mh->cpusubtype;
436    fakearch.offset = 0;
437    fakearch.size = 0;
438    fakearch.align = 0;
439    archs = &fakearch;
440    archCount = 1;
441  } else if(fh->magic == MH_CIGAM) {
442    struct mach_header *mh = (struct mach_header *)buffer;
443    fakearch.cputype = OSSwapInt32(mh->cputype);
444    fakearch.cpusubtype = OSSwapInt32(mh->cpusubtype);
445    fakearch.offset = 0;
446    fakearch.size = 0;
447    fakearch.align = 0;
448    archs = &fakearch;
449    archCount = 1;
450  } else if(fh->magic == MH_MAGIC_64) {
451    struct mach_header_64 *mh = (struct mach_header_64 *)buffer;
452    fakearch.cputype = mh->cputype;
453    fakearch.cpusubtype = mh->cpusubtype;
454    fakearch.offset = 0;
455    fakearch.size = 0;
456    fakearch.align = 0;
457    archs = &fakearch;
458    archCount = 1;
459  } else if(fh->magic == MH_CIGAM_64) {
460    struct mach_header_64 *mh = (struct mach_header_64 *)buffer;
461    fakearch.cputype = OSSwapInt32(mh->cputype);
462    fakearch.cpusubtype = OSSwapInt32(mh->cpusubtype);
463    fakearch.offset = 0;
464    fakearch.size = 0;
465    fakearch.align = 0;
466    archs = &fakearch;
467    archCount = 1;
468  }
469
470  thisArch = NXGetArchInfoFromName(kEmacsArch);
471  if(thisArch == NULL)
472    errx(1, "Unknown architecture: %s", kEmacsArch);
473
474  bestArch = NXFindBestFatArch(thisArch->cputype,
475			       thisArch->cpusubtype,
476			       archs,
477			       archCount);
478  if(bestArch == NULL)
479    errx(1, "No appropriate architecture in %s", filename);
480
481  if(!isFat) {
482    /* mmap the whole file */
483    struct stat statbuf;
484    fstat(fd, &statbuf);
485    *psize = statbuf.st_size;
486    offset = 0;
487  } else {
488    *psize = bestArch->size;
489    offset = bestArch->offset;
490  }
491  void *rc = mmap(NULL, *psize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, offset);
492  close(fd);
493  if (rc == (void *)-1)
494    return NULL;
495  return rc;
496}
497
498char *verfind(void *mem, size_t size, char marker, const char *search) {
499  char *first = (char *)mem;
500  char *last = first + size;
501  size_t search_size = strlen(search);
502  /* avoid searching past end of mmap region */
503  void *found = memchr(mem, marker, size - (search_size+1));
504
505  while (found != NULL) {
506    char *here = (char *)found;
507    if (0 == strncmp(here+1, search, search_size)) {
508      return strdup(here);
509    } else {
510      found = memchr(here+1, marker, last - (here+1) - (search_size+1));
511    }
512  }
513  return NULL;
514}
515