| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 3 | * Licensed under the GPL | 
|  | 4 | */ | 
|  | 5 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 6 | #include <stdio.h> | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 7 | #include <unistd.h> | 
|  | 8 | #include <stdarg.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 9 | #include <errno.h> | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 10 | #include <stddef.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 11 | #include <string.h> | 
|  | 12 | #include <sys/socket.h> | 
|  | 13 | #include <sys/wait.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 14 | #include "net_user.h" | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 15 | #include "kern_constants.h" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 16 | #include "os.h" | 
| Paolo 'Blaisorblade' Giarrusso | c13e569 | 2006-10-19 23:28:20 -0700 | [diff] [blame] | 17 | #include "um_malloc.h" | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 18 | #include "user.h" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 19 |  | 
|  | 20 | int tap_open_common(void *dev, char *gate_addr) | 
|  | 21 | { | 
|  | 22 | int tap_addr[4]; | 
|  | 23 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 24 | if (gate_addr == NULL) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 25 | return 0; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 26 | if (sscanf(gate_addr, "%d.%d.%d.%d", &tap_addr[0], | 
|  | 27 | &tap_addr[1], &tap_addr[2], &tap_addr[3]) != 4) { | 
|  | 28 | printk(UM_KERN_ERR "Invalid tap IP address - '%s'\n", | 
|  | 29 | gate_addr); | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 30 | return -EINVAL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 31 | } | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 32 | return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 33 | } | 
|  | 34 |  | 
| Jeff Dike | da00d9a | 2005-06-08 15:48:01 -0700 | [diff] [blame] | 35 | void tap_check_ips(char *gate_addr, unsigned char *eth_addr) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 36 | { | 
|  | 37 | int tap_addr[4]; | 
|  | 38 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 39 | if ((gate_addr != NULL) && | 
|  | 40 | (sscanf(gate_addr, "%d.%d.%d.%d", &tap_addr[0], | 
|  | 41 | &tap_addr[1], &tap_addr[2], &tap_addr[3]) == 4) && | 
|  | 42 | (eth_addr[0] == tap_addr[0]) && | 
|  | 43 | (eth_addr[1] == tap_addr[1]) && | 
|  | 44 | (eth_addr[2] == tap_addr[2]) && | 
|  | 45 | (eth_addr[3] == tap_addr[3])) { | 
|  | 46 | printk(UM_KERN_ERR "The tap IP address and the UML eth IP " | 
|  | 47 | "address must be different\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 48 | } | 
|  | 49 | } | 
|  | 50 |  | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 51 | /* Do reliable error handling as this fails frequently enough. */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 52 | void read_output(int fd, char *output, int len) | 
|  | 53 | { | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 54 | int remain, ret, expected; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 55 | char c; | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 56 | char *str; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 57 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 58 | if (output == NULL) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 59 | output = &c; | 
|  | 60 | len = sizeof(c); | 
|  | 61 | } | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 62 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 63 | *output = '\0'; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 64 | ret = read(fd, &remain, sizeof(remain)); | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 65 |  | 
|  | 66 | if (ret != sizeof(remain)) { | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 67 | if (ret < 0) | 
|  | 68 | ret = -errno; | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 69 | expected = sizeof(remain); | 
|  | 70 | str = "length"; | 
|  | 71 | goto err; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 72 | } | 
|  | 73 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 74 | while (remain != 0) { | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 75 | expected = (remain < len) ? remain : len; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 76 | ret = read(fd, output, expected); | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 77 | if (ret != expected) { | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 78 | if (ret < 0) | 
|  | 79 | ret = -errno; | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 80 | str = "data"; | 
|  | 81 | goto err; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 82 | } | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 83 | remain -= ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 84 | } | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 85 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 86 | return; | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 87 |  | 
|  | 88 | err: | 
|  | 89 | if (ret < 0) | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 90 | printk(UM_KERN_ERR "read_output - read of %s failed, " | 
|  | 91 | "errno = %d\n", str, -ret); | 
| Paolo 'Blaisorblade' Giarrusso | f462e8f | 2006-02-24 13:03:57 -0800 | [diff] [blame] | 92 | else | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 93 | printk(UM_KERN_ERR "read_output - read of %s failed, read only " | 
|  | 94 | "%d of %d bytes\n", str, ret, expected); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 95 | } | 
|  | 96 |  | 
|  | 97 | int net_read(int fd, void *buf, int len) | 
|  | 98 | { | 
|  | 99 | int n; | 
|  | 100 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 101 | n = read(fd,  buf,  len); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 102 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 103 | if ((n < 0) && (errno == EAGAIN)) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 104 | return 0; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 105 | else if (n == 0) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 106 | return -ENOTCONN; | 
|  | 107 | return n; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 108 | } | 
|  | 109 |  | 
|  | 110 | int net_recvfrom(int fd, void *buf, int len) | 
|  | 111 | { | 
|  | 112 | int n; | 
|  | 113 |  | 
| Jeff Dike | 9ead6fe | 2006-07-10 04:45:15 -0700 | [diff] [blame] | 114 | CATCH_EINTR(n = recvfrom(fd,  buf,  len, 0, NULL, NULL)); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 115 | if (n < 0) { | 
|  | 116 | if (errno == EAGAIN) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 117 | return 0; | 
|  | 118 | return -errno; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 119 | } | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 120 | else if (n == 0) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 121 | return -ENOTCONN; | 
|  | 122 | return n; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 123 | } | 
|  | 124 |  | 
|  | 125 | int net_write(int fd, void *buf, int len) | 
|  | 126 | { | 
|  | 127 | int n; | 
|  | 128 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 129 | n = write(fd, buf, len); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 130 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 131 | if ((n < 0) && (errno == EAGAIN)) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 132 | return 0; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 133 | else if (n == 0) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 134 | return -ENOTCONN; | 
|  | 135 | return n; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 136 | } | 
|  | 137 |  | 
|  | 138 | int net_send(int fd, void *buf, int len) | 
|  | 139 | { | 
|  | 140 | int n; | 
|  | 141 |  | 
| Jeff Dike | 9ead6fe | 2006-07-10 04:45:15 -0700 | [diff] [blame] | 142 | CATCH_EINTR(n = send(fd, buf, len, 0)); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 143 | if (n < 0) { | 
|  | 144 | if (errno == EAGAIN) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 145 | return 0; | 
|  | 146 | return -errno; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 147 | } | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 148 | else if (n == 0) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 149 | return -ENOTCONN; | 
|  | 150 | return n; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 151 | } | 
|  | 152 |  | 
|  | 153 | int net_sendto(int fd, void *buf, int len, void *to, int sock_len) | 
|  | 154 | { | 
|  | 155 | int n; | 
|  | 156 |  | 
| Jeff Dike | 9ead6fe | 2006-07-10 04:45:15 -0700 | [diff] [blame] | 157 | CATCH_EINTR(n = sendto(fd, buf, len, 0, (struct sockaddr *) to, | 
|  | 158 | sock_len)); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 159 | if (n < 0) { | 
|  | 160 | if (errno == EAGAIN) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 161 | return 0; | 
|  | 162 | return -errno; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 163 | } | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 164 | else if (n == 0) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 165 | return -ENOTCONN; | 
|  | 166 | return n; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 167 | } | 
|  | 168 |  | 
|  | 169 | struct change_pre_exec_data { | 
|  | 170 | int close_me; | 
|  | 171 | int stdout; | 
|  | 172 | }; | 
|  | 173 |  | 
|  | 174 | static void change_pre_exec(void *arg) | 
|  | 175 | { | 
|  | 176 | struct change_pre_exec_data *data = arg; | 
|  | 177 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 178 | close(data->close_me); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 179 | dup2(data->stdout, 1); | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | static int change_tramp(char **argv, char *output, int output_len) | 
|  | 183 | { | 
|  | 184 | int pid, fds[2], err; | 
|  | 185 | struct change_pre_exec_data pe_data; | 
|  | 186 |  | 
|  | 187 | err = os_pipe(fds, 1, 0); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 188 | if (err < 0) { | 
|  | 189 | printk(UM_KERN_ERR "change_tramp - pipe failed, err = %d\n", | 
|  | 190 | -err); | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 191 | return err; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 192 | } | 
|  | 193 | pe_data.close_me = fds[0]; | 
|  | 194 | pe_data.stdout = fds[1]; | 
| Jeff Dike | c439901 | 2007-07-15 23:38:56 -0700 | [diff] [blame] | 195 | pid = run_helper(change_pre_exec, &pe_data, argv); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 |  | 
| Paolo 'Blaisorblade' Giarrusso | b1c332c | 2006-04-10 22:53:37 -0700 | [diff] [blame] | 197 | if (pid > 0)	/* Avoid hang as we won't get data in failure case. */ | 
|  | 198 | read_output(fds[0], output, output_len); | 
|  | 199 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 200 | close(fds[0]); | 
|  | 201 | close(fds[1]); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 202 |  | 
|  | 203 | if (pid > 0) | 
| Jeff Dike | 1aa351a | 2008-02-04 22:31:10 -0800 | [diff] [blame] | 204 | helper_wait(pid); | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 205 | return pid; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 206 | } | 
|  | 207 |  | 
|  | 208 | static void change(char *dev, char *what, unsigned char *addr, | 
|  | 209 | unsigned char *netmask) | 
|  | 210 | { | 
|  | 211 | char addr_buf[sizeof("255.255.255.255\0")]; | 
|  | 212 | char netmask_buf[sizeof("255.255.255.255\0")]; | 
|  | 213 | char version[sizeof("nnnnn\0")]; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 214 | char *argv[] = { "uml_net", version, what, dev, addr_buf, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 215 | netmask_buf, NULL }; | 
|  | 216 | char *output; | 
|  | 217 | int output_len, pid; | 
|  | 218 |  | 
|  | 219 | sprintf(version, "%d", UML_NET_VERSION); | 
|  | 220 | sprintf(addr_buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 221 | sprintf(netmask_buf, "%d.%d.%d.%d", netmask[0], netmask[1], | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 222 | netmask[2], netmask[3]); | 
|  | 223 |  | 
| Jeff Dike | 1ffb916 | 2007-05-06 14:51:22 -0700 | [diff] [blame] | 224 | output_len = UM_KERN_PAGE_SIZE; | 
| Jeff Dike | e4c4bf9 | 2007-07-15 23:38:56 -0700 | [diff] [blame] | 225 | output = kmalloc(output_len, UM_GFP_KERNEL); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 226 | if (output == NULL) | 
|  | 227 | printk(UM_KERN_ERR "change : failed to allocate output " | 
|  | 228 | "buffer\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 229 |  | 
|  | 230 | pid = change_tramp(argv, output, output_len); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 231 | if (pid < 0) return; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 232 |  | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 233 | if (output != NULL) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 234 | printk("%s", output); | 
|  | 235 | kfree(output); | 
|  | 236 | } | 
|  | 237 | } | 
|  | 238 |  | 
|  | 239 | void open_addr(unsigned char *addr, unsigned char *netmask, void *arg) | 
|  | 240 | { | 
|  | 241 | change(arg, "add", addr, netmask); | 
|  | 242 | } | 
|  | 243 |  | 
|  | 244 | void close_addr(unsigned char *addr, unsigned char *netmask, void *arg) | 
|  | 245 | { | 
|  | 246 | change(arg, "del", addr, netmask); | 
|  | 247 | } | 
|  | 248 |  | 
|  | 249 | char *split_if_spec(char *str, ...) | 
|  | 250 | { | 
|  | 251 | char **arg, *end; | 
|  | 252 | va_list ap; | 
|  | 253 |  | 
|  | 254 | va_start(ap, str); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 255 | while ((arg = va_arg(ap, char **)) != NULL) { | 
|  | 256 | if (*str == '\0') | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 257 | return NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 258 | end = strchr(str, ','); | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 259 | if (end != str) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 260 | *arg = str; | 
| Jeff Dike | cd1ae0e | 2007-10-16 01:27:29 -0700 | [diff] [blame] | 261 | if (end == NULL) | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 262 | return NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 263 | *end++ = '\0'; | 
|  | 264 | str = end; | 
|  | 265 | } | 
|  | 266 | va_end(ap); | 
| Jeff Dike | 108ffa8 | 2006-07-10 04:45:14 -0700 | [diff] [blame] | 267 | return str; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 268 | } |