chiark / gitweb /
finalise changelog
[bash.git] / debian / bash.preinst.c
1 /*
2  * This file is in the public domain.
3  * You may freely use, modify, distribute, and relicense it.
4  */
5
6 #include "bash.preinst.h"
7 #include <stdio.h>
8 #include <string.h>
9 #include <errno.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14
15 static void backup(const char *file, const char *dest)
16 {
17         const char * const cmd[] = {"cp", "-dp", file, dest, NULL};
18         if (exists(file))
19                 run(cmd);
20 }
21
22 static void force_symlink(const char *target, const char *link,
23                                                 const char *temp)
24 {
25         /*
26          * Forcibly create a symlink to "target" from "link".
27          * This is performed in two stages with an
28          * intermediate temporary file because symlink(2) cannot
29          * atomically replace an existing file.
30          */
31         if ((unlink(temp) && errno != ENOENT) ||
32             symlink(target, temp) ||
33             rename(temp, link))
34                 die_errno("cannot create symlink %s -> %s", link, target);
35 }
36
37 static void reset_diversion(const char *package, const char *file,
38                                                 const char *distrib)
39 {
40         const char * const remove_old_diversion[] =
41                 {"dpkg-divert", "--package", "bash", "--remove", file, NULL};
42         const char * const new_diversion[] =
43                 {"dpkg-divert", "--package", package,
44                 "--divert", distrib, "--add", file, NULL};
45         run(remove_old_diversion);
46         run(new_diversion);
47 }
48
49 static int has_binsh_line(FILE *file)
50 {
51         char item[sizeof("/bin/sh\n")];
52
53         while (fgets(item, sizeof(item), file)) {
54                 int ch;
55
56                 if (!memcmp(item, "/bin/sh\n", strlen("/bin/sh\n") + 1))
57                         return 1;
58                 if (strchr(item, '\n'))
59                         continue;
60
61                 /* Finish the line. */
62                 for (ch = 0; ch != '\n' && ch != EOF; ch = fgetc(file))
63                         ; /* just reading */
64                 if (ch == EOF)
65                         break;
66         }
67         if (ferror(file))
68                 die_errno("cannot read pipe");
69         return 0;
70 }
71
72 static int binsh_in_filelist(const char *package)
73 {
74         const char * const cmd[] = {"dpkg-query", "-L", package, NULL};
75         pid_t child;
76         int sink;
77         FILE *in;
78         int found;
79
80         /*
81          * dpkg -L $package 2>/dev/null | ...
82          *
83          * Redirection of stderr is for quieter output
84          * when $package is not installed.  If opening /dev/null
85          * fails, no problem; leave stderr alone in that case.
86          */
87         sink = open("/dev/null", O_WRONLY);
88         if (sink >= 0)
89                 set_cloexec(sink);
90         in = spawn_pipe(&child, cmd, sink);
91
92         /* ... | grep "^/bin/sh\$" */
93         found = has_binsh_line(in);
94         if (fclose(in))
95                 die_errno("cannot close read end of pipe");
96
97         /*
98          * dpkg -L will error out if $package is not already installed.
99          *
100          * We stopped reading early if we found a match, so
101          * tolerate SIGPIPE in that case.
102          */
103         wait_or_die(child, "dpkg-query -L", ERROR_OK |
104                                                 (found ? SIGPIPE_OK : 0));
105         return found;
106 }
107
108 static int undiverted(const char *path)
109 {
110         const char * const cmd[] =
111                 {"dpkg-divert", "--listpackage", path, NULL};
112         pid_t child;
113         char packagename[sizeof("bash\n")];
114         size_t len;
115         FILE *in = spawn_pipe(&child, cmd, -1);
116         int diverted = 1;
117
118         /* Is $path diverted by someone other than bash? */
119
120         len = fread(packagename, 1, sizeof(packagename), in);
121         if (ferror(in))
122                 die_errno("cannot read from dpkg-divert");
123         if (len == 0)
124                 diverted = 0;   /* No diversion. */
125         if (len == strlen("bash\n") && !memcmp(packagename, "bash\n", len))
126                 diverted = 0;   /* Diverted by bash. */
127
128         if (fclose(in))
129                 die_errno("cannot close read end of pipe");
130         wait_or_die(child, "dpkg-divert", ERROR_OK | SIGPIPE_OK);
131         return !diverted;
132 }
133
134 int main(int argc, char *argv[])
135 {
136         /* /bin/sh needs to point to a valid target. */
137
138         if (access("/bin/sh", X_OK)) {
139                 backup("/bin/sh", "/bin/sh.distrib");
140                 backup("/usr/share/man/man1/sh.1.gz",
141                         "/usr/share/man/man1/sh.distrib.1.gz");
142
143                 force_symlink("bash", "/bin/sh", "/bin/sh.temp");
144                 force_symlink("bash.1.gz", "/usr/share/man/man1/sh.1.gz",
145                         "/usr/share/man/man1/sh.1.gz.temp");
146         }
147         if (!binsh_in_filelist("bash"))
148                 /* Ready. */
149                 return 0;
150
151         /*
152          * In bash (<= 4.1-3), the bash package included symlinks for
153          * /bin/sh and the sh(1) manpage in its data.tar.
154          *
155          * Unless we are careful, unpacking the new version of bash
156          * will remove them.  So we tell dpkg that the files from bash
157          * to be removed are elsewhere, using a diversion on behalf of
158          * another package.
159          *
160          * Based on an idea by Michael Stone.
161          * “You're one sick individual.” -- Anthony Towns
162          * http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=85;bug=34717
163          */
164         if (undiverted("/bin/sh"))
165                 reset_diversion("dash", "/bin/sh", "/bin/sh.distrib");
166         if (undiverted("/usr/share/man/man1/sh.1.gz"))
167                 reset_diversion("dash", "/usr/share/man/man1/sh.1.gz",
168                                 "/usr/share/man/man1/sh.distrib.1.gz");
169         return 0;
170 }