diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2014-01-13 22:16:18 (JST) |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2014-01-14 10:00:07 (JST) |
commit | e83b51b4f6bd53efea0c772e6ecdf1c5605ca611 (patch) | |
tree | 695797e4de7c7e413730f7ab66246327e6fa0046 | |
parent | d750c7a2c9bb83cfc47c0d74fcee61f0a5042aa9 (diff) | |
download | cgit-e83b51b4f6bd53efea0c772e6ecdf1c5605ca611.zip cgit-e83b51b4f6bd53efea0c772e6ecdf1c5605ca611.tar.gz |
filter: basic write hooking infrastructure
Filters can now call hook_write and unhook_write if they want to
redirect writing to stdout to a different function. This saves us from
potential file descriptor pipes and other less efficient mechanisms.
We do this instead of replacing the call in html_raw because some places
stdlib's printf functions are used (ui-patch or within git itself),
which has its own internal buffering, which makes it difficult to
interlace our function calls. So, we dlsym libc's write and then
override it in the link stage.
While we're at it, we move considerations of argument count into the
generic new filter handler.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | cgit.c | 2 | ||||
-rw-r--r-- | cgit.h | 3 | ||||
-rw-r--r-- | cgit.mk | 4 | ||||
-rw-r--r-- | filter.c | 81 |
4 files changed, 67 insertions, 23 deletions
@@ -904,6 +904,8 @@ int main(int argc, const char **argv) | |||
904 | const char *path; | 904 | const char *path; |
905 | int err, ttl; | 905 | int err, ttl; |
906 | 906 | ||
907 | cgit_init_filters(); | ||
908 | |||
907 | prepare_context(&ctx); | 909 | prepare_context(&ctx); |
908 | cgit_repolist.length = 0; | 910 | cgit_repolist.length = 0; |
909 | cgit_repolist.count = 0; | 911 | cgit_repolist.count = 0; |
@@ -61,13 +61,13 @@ struct cgit_filter { | |||
61 | int (*close)(struct cgit_filter *); | 61 | int (*close)(struct cgit_filter *); |
62 | void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix); | 62 | void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix); |
63 | void (*cleanup)(struct cgit_filter *); | 63 | void (*cleanup)(struct cgit_filter *); |
64 | int argument_count; | ||
64 | }; | 65 | }; |
65 | 66 | ||
66 | struct cgit_exec_filter { | 67 | struct cgit_exec_filter { |
67 | struct cgit_filter base; | 68 | struct cgit_filter base; |
68 | char *cmd; | 69 | char *cmd; |
69 | char **argv; | 70 | char **argv; |
70 | int extra_args; | ||
71 | int old_stdout; | 71 | int old_stdout; |
72 | int pipe_fh[2]; | 72 | int pipe_fh[2]; |
73 | int pid; | 73 | int pid; |
@@ -357,6 +357,7 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char | |||
357 | extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv); | 357 | extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv); |
358 | extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype); | 358 | extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype); |
359 | extern void cgit_cleanup_filters(void); | 359 | extern void cgit_cleanup_filters(void); |
360 | extern void cgit_init_filters(void); | ||
360 | 361 | ||
361 | extern void cgit_prepare_repo_env(struct cgit_repo * repo); | 362 | extern void cgit_prepare_repo_env(struct cgit_repo * repo); |
362 | 363 | ||
@@ -61,6 +61,8 @@ $(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION | |||
61 | $(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \ | 61 | $(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \ |
62 | -DCGIT_VERSION='"$(CGIT_VERSION)"' | 62 | -DCGIT_VERSION='"$(CGIT_VERSION)"' |
63 | 63 | ||
64 | CGIT_LIBS += -ldl | ||
65 | |||
64 | 66 | ||
65 | # Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not | 67 | # Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not |
66 | # handled by that and we must handle them ourselves. | 68 | # handled by that and we must handle them ourselves. |
@@ -88,4 +90,4 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) | |||
88 | $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< | 90 | $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< |
89 | 91 | ||
90 | $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) | 92 | $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) |
91 | $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) | 93 | $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) |
@@ -12,6 +12,11 @@ | |||
12 | #include <unistd.h> | 12 | #include <unistd.h> |
13 | #include <string.h> | 13 | #include <string.h> |
14 | #include <stdlib.h> | 14 | #include <stdlib.h> |
15 | #include <dlfcn.h> | ||
16 | |||
17 | static ssize_t (*libc_write)(int fd, const void *buf, size_t count); | ||
18 | static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL; | ||
19 | static struct cgit_filter *current_write_filter = NULL; | ||
15 | 20 | ||
16 | static inline void reap_filter(struct cgit_filter *filter) | 21 | static inline void reap_filter(struct cgit_filter *filter) |
17 | { | 22 | { |
@@ -32,12 +37,43 @@ void cgit_cleanup_filters(void) | |||
32 | } | 37 | } |
33 | } | 38 | } |
34 | 39 | ||
40 | void cgit_init_filters(void) | ||
41 | { | ||
42 | libc_write = dlsym(RTLD_NEXT, "write"); | ||
43 | if (!libc_write) | ||
44 | die("Could not locate libc's write function"); | ||
45 | } | ||
46 | |||
47 | ssize_t write(int fd, const void *buf, size_t count) | ||
48 | { | ||
49 | if (fd != STDOUT_FILENO || !filter_write) | ||
50 | return libc_write(fd, buf, count); | ||
51 | return filter_write(current_write_filter, buf, count); | ||
52 | } | ||
53 | |||
54 | static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count)) | ||
55 | { | ||
56 | /* We want to avoid buggy nested patterns. */ | ||
57 | assert(filter_write == NULL); | ||
58 | assert(current_write_filter == NULL); | ||
59 | current_write_filter = filter; | ||
60 | filter_write = new_write; | ||
61 | } | ||
62 | |||
63 | static inline void unhook_write() | ||
64 | { | ||
65 | assert(filter_write != NULL); | ||
66 | assert(current_write_filter != NULL); | ||
67 | filter_write = NULL; | ||
68 | current_write_filter = NULL; | ||
69 | } | ||
70 | |||
35 | static int open_exec_filter(struct cgit_filter *base, va_list ap) | 71 | static int open_exec_filter(struct cgit_filter *base, va_list ap) |
36 | { | 72 | { |
37 | struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base; | 73 | struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base; |
38 | int i; | 74 | int i; |
39 | 75 | ||
40 | for (i = 0; i < filter->extra_args; i++) | 76 | for (i = 0; i < filter->base.argument_count; i++) |
41 | filter->argv[i+1] = va_arg(ap, char *); | 77 | filter->argv[i+1] = va_arg(ap, char *); |
42 | 78 | ||
43 | filter->old_stdout = chk_positive(dup(STDOUT_FILENO), | 79 | filter->old_stdout = chk_positive(dup(STDOUT_FILENO), |
@@ -74,7 +110,7 @@ static int close_exec_filter(struct cgit_filter *base) | |||
74 | die("Subprocess %s exited abnormally", filter->cmd); | 110 | die("Subprocess %s exited abnormally", filter->cmd); |
75 | 111 | ||
76 | done: | 112 | done: |
77 | for (i = 0; i < filter->extra_args; i++) | 113 | for (i = 0; i < filter->base.argument_count; i++) |
78 | filter->argv[i+1] = NULL; | 114 | filter->argv[i+1] = NULL; |
79 | return 0; | 115 | return 0; |
80 | 116 | ||
@@ -99,7 +135,7 @@ static void cleanup_exec_filter(struct cgit_filter *base) | |||
99 | } | 135 | } |
100 | } | 136 | } |
101 | 137 | ||
102 | static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filtertype) | 138 | static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count) |
103 | { | 139 | { |
104 | struct cgit_exec_filter *f; | 140 | struct cgit_exec_filter *f; |
105 | int args_size = 0; | 141 | int args_size = 0; |
@@ -107,20 +143,8 @@ static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filterty | |||
107 | f = xmalloc(sizeof(*f)); | 143 | f = xmalloc(sizeof(*f)); |
108 | /* We leave argv for now and assign it below. */ | 144 | /* We leave argv for now and assign it below. */ |
109 | cgit_exec_filter_init(f, xstrdup(cmd), NULL); | 145 | cgit_exec_filter_init(f, xstrdup(cmd), NULL); |
110 | 146 | f->base.argument_count = argument_count; | |
111 | switch (filtertype) { | 147 | args_size = (2 + argument_count) * sizeof(char *); |
112 | case SOURCE: | ||
113 | case ABOUT: | ||
114 | f->extra_args = 1; | ||
115 | break; | ||
116 | |||
117 | case COMMIT: | ||
118 | default: | ||
119 | f->extra_args = 0; | ||
120 | break; | ||
121 | } | ||
122 | |||
123 | args_size = (2 + f->extra_args) * sizeof(char *); | ||
124 | f->argv = xmalloc(args_size); | 148 | f->argv = xmalloc(args_size); |
125 | memset(f->argv, 0, args_size); | 149 | memset(f->argv, 0, args_size); |
126 | f->argv[0] = f->cmd; | 150 | f->argv[0] = f->cmd; |
@@ -136,6 +160,8 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar | |||
136 | filter->base.cleanup = cleanup_exec_filter; | 160 | filter->base.cleanup = cleanup_exec_filter; |
137 | filter->cmd = cmd; | 161 | filter->cmd = cmd; |
138 | filter->argv = argv; | 162 | filter->argv = argv; |
163 | /* The argument count for open_filter is zero by default, unless called from new_filter, above. */ | ||
164 | filter->base.argument_count = 0; | ||
139 | } | 165 | } |
140 | 166 | ||
141 | int cgit_open_filter(struct cgit_filter *filter, ...) | 167 | int cgit_open_filter(struct cgit_filter *filter, ...) |
@@ -162,7 +188,7 @@ void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix | |||
162 | 188 | ||
163 | static const struct { | 189 | static const struct { |
164 | const char *prefix; | 190 | const char *prefix; |
165 | struct cgit_filter *(*ctor)(const char *cmd, filter_type filtertype); | 191 | struct cgit_filter *(*ctor)(const char *cmd, int argument_count); |
166 | } filter_specs[] = { | 192 | } filter_specs[] = { |
167 | { "exec", new_exec_filter }, | 193 | { "exec", new_exec_filter }, |
168 | }; | 194 | }; |
@@ -172,6 +198,8 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) | |||
172 | char *colon; | 198 | char *colon; |
173 | int i; | 199 | int i; |
174 | size_t len; | 200 | size_t len; |
201 | int argument_count; | ||
202 | |||
175 | if (!cmd || !cmd[0]) | 203 | if (!cmd || !cmd[0]) |
176 | return NULL; | 204 | return NULL; |
177 | 205 | ||
@@ -184,16 +212,27 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) | |||
184 | if (len == 1) | 212 | if (len == 1) |
185 | colon = NULL; | 213 | colon = NULL; |
186 | 214 | ||
215 | switch (filtertype) { | ||
216 | case SOURCE: | ||
217 | case ABOUT: | ||
218 | argument_count = 1; | ||
219 | break; | ||
220 | |||
221 | case COMMIT: | ||
222 | default: | ||
223 | argument_count = 0; | ||
224 | break; | ||
225 | } | ||
226 | |||
187 | /* If no prefix is given, exec filter is the default. */ | 227 | /* If no prefix is given, exec filter is the default. */ |
188 | if (!colon) | 228 | if (!colon) |
189 | return new_exec_filter(cmd, filtertype); | 229 | return new_exec_filter(cmd, argument_count); |
190 | 230 | ||
191 | for (i = 0; i < ARRAY_SIZE(filter_specs); i++) { | 231 | for (i = 0; i < ARRAY_SIZE(filter_specs); i++) { |
192 | if (len == strlen(filter_specs[i].prefix) && | 232 | if (len == strlen(filter_specs[i].prefix) && |
193 | !strncmp(filter_specs[i].prefix, cmd, len)) | 233 | !strncmp(filter_specs[i].prefix, cmd, len)) |
194 | return filter_specs[i].ctor(colon + 1, filtertype); | 234 | return filter_specs[i].ctor(colon + 1, argument_count); |
195 | } | 235 | } |
196 | 236 | ||
197 | die("Invalid filter type: %.*s", (int) len, cmd); | 237 | die("Invalid filter type: %.*s", (int) len, cmd); |
198 | } | 238 | } |
199 | |||