portage_gid: allow numeric groups
[proj/portage.git] / src / chpathtool.c
1 /* Copyright Gentoo Foundation 2006-2010
2 * Author: Fabian Groffen <grobian@gentoo.org>
3 *
4 * chpathtool replaces a given string (magic) into another (value),
5 * thereby paying attention to the original size of magic in order not
6 * to change offsets in the file changed. To achieve this goal, value
7 * is not greater in size than magic, and the difference in size between
8 * the two is compensated by adding NULL-bytes at the end of a modified
9 * string. The end of a string is considered to be at the first
10 * NULL-byte encountered after magic. If no such NULL-byte is found, as
11 * in a plain text file, the padding NULL-bytes are silently dropped.
12 */
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <strings.h>
18 #include <errno.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <dirent.h>
22 #include <unistd.h>
23 #ifdef HAVE_ALLOCA_H
24 #include <alloca.h>
25 #endif
26 #ifdef HAVE_UTIME_H
27 #include <utime.h>
28 #endif
29 #include <sys/time.h>
30 #include <fcntl.h>
31
32 /* Don't allocate too much, or you'll be paying for waiting on IO,
33 * size -1 to align in memory. */
34 #define BUFSIZE 8095
35 /* POSIX says 256 on this one, but I can hardly believe that really is
36 * the limit of most popular systems. XOPEN says 1024, taking that
37 * value, hoping it is enough */
38 #define MAX_PATH 1024
39
40 /* structure to track and trace hardlinks */
41 typedef struct _file_hlink {
42 dev_t st_dev; /* device inode resides on */
43 ino_t st_ino; /* inode's number */
44 char path[MAX_PATH]; /* the file's path + name */
45 struct _file_hlink* next; /* pointer to the next in the list */
46 } file_hlink;
47
48 static char *magic;
49 static char *value;
50 static size_t magiclen;
51 static size_t valuelen;
52 static char quiet;
53 static file_hlink *hlinks = NULL;
54
55 /**
56 * Writes padding zero-bytes after the first encountered zero-byte.
57 * Returns padding if no zero-byte was seen, or 0 if padding was
58 * applied.
59 */
60 static size_t padonwrite(size_t padding, char *buf, size_t len, FILE *fout) {
61 char *z;
62 if (padding == 0 || (z = memchr(buf, '\0', len)) == NULL) {
63 /* cheap case, nothing complicated to do here */
64 fwrite(buf, len, 1, fout);
65 } else {
66 /* found a zero-byte, insert padding */
67 fwrite(buf, z - buf, 1, fout);
68 /* now pad with zeros so we don't screw up
69 * the positions in the file */
70 buf[0] = '\0';
71 while (padding > 0) {
72 fwrite(buf, 1, 1, fout);
73 padding--;
74 }
75 fwrite(z, len - (z - buf), 1, fout);
76 }
77
78 return(padding);
79 }
80
81 /**
82 * Searches buf for an occurrence of needle, doing a byte-based match,
83 * disregarding end of string markers (zero-bytes), as strstr does.
84 * Returns a pointer to the first occurrence of needle in buf, or NULL
85 * if not found. If a partial match is found at the end of the buffer
86 * (size < strlen(needle)) it is returned as well.
87 */
88 static char *memstr(const char *buf, const char *needle, size_t len) {
89 const char *ret;
90 size_t off;
91 for (ret = buf; ret - buf < len; ret++) {
92 off = 0;
93 while (needle[off] != '\0' &&
94 (ret - buf) + off < len &&
95 needle[off] == ret[off])
96 {
97 off++;
98 }
99 if (needle[off] == '\0' || (ret - buf) + off == len)
100 return((char *)ret);
101 }
102 return(NULL);
103 }
104
105 /**
106 * Copies the given file to the output file with prefix transformations
107 * on the fly.
108 */
109 static int chpath(const char *fi, const char *fo) {
110 FILE *fin;
111 FILE *fout;
112 size_t len;
113 size_t pos;
114 size_t padding;
115 char *tmp;
116 char buf[BUFSIZE + 1];
117 char firstblock;
118 char *lvalue = value;
119 size_t lvaluelen = valuelen;
120
121 /* make sure there is a trailing zero-byte, such that strstr and
122 * strchr won't go out of bounds causing segfaults. */
123 buf[BUFSIZE] = '\0';
124
125 fin = fopen(fi, "r");
126 if (fin == NULL) {
127 fprintf(stderr, "unable to open %s: %s\n", fi, strerror(errno));
128 return(-1);
129 }
130 fout = fopen(fo, "w");
131 if (fout == NULL) {
132 fprintf(stderr, "unable to open %s: %s\n", fo, strerror(errno));
133 return(-1);
134 }
135
136 pos = 0;
137 padding = 0;
138 firstblock = 1;
139 while ((len = fread(buf + pos, 1, BUFSIZE - pos, fin)) != 0 || pos > 0) {
140 if (firstblock == 1) {
141 firstblock = 0;
142 /* examine the bytes we read to judge if they are binary or
143 * text; in case of the latter we can avoid writing ugly
144 * paths with zilions of backslashes */
145 for (pos = 0; pos < len; pos++) {
146 /* this is a very stupid assumption, but I don't know
147 * anything better: if we find a byte that's out of
148 * ASCII character scope, we assume this is binary */
149 if ((buf[pos] < ' ' || buf[pos] > '~') &&
150 strchr("\t\r\n", buf[pos]) == NULL)
151 {
152 /* Make sure we don't mess up code, GCC for instance
153 * hardcodes the result of strlen("string") in
154 * object code. This means we can nicely replace
155 * this string with a shorter NULL-terminated one,
156 * but that won't change the hardcoded output of the
157 * original strlen. Hence, pad the value with
158 * slashes until it is of same size as magic. */
159 lvalue = alloca(sizeof(char) * (magiclen + 1));
160 lvaluelen = magiclen;
161 snprintf(lvalue, lvaluelen + 1, "%*s",
162 (int)magiclen, value);
163 tmp = lvalue;
164 while (*tmp == ' ')
165 *tmp++ = '/';
166 break;
167 }
168 }
169 pos = 0;
170 }
171 len += pos;
172 if ((tmp = memstr(buf, magic, len)) != NULL) {
173 /* if binary : */
174 if (tmp == buf) {
175 if (len < magiclen) {
176 /* must be last piece */
177 padding = padonwrite(padding, buf, len, fout);
178 break;
179 }
180 /* do some magic, overwrite it basically */
181 fwrite(lvalue, lvaluelen, 1, fout);
182 /* store what we need to correct */
183 padding += magiclen - lvaluelen;
184 /* move away the magic */
185 pos = len - magiclen;
186 memmove(buf, buf + magiclen, pos);
187 continue;
188 } else {
189 /* move this bunch to the front */
190 pos = len - (tmp - buf);
191 }
192 } else {
193 /* magic is not in here, since memchr also returns a match
194 * if incomplete but at the end of the string, here we can
195 * always read a new block. */
196 if (len != BUFSIZE) {
197 /* last piece */
198 padding = padonwrite(padding, buf, len, fout);
199 break;
200 } else {
201 pos = 0;
202 tmp = buf + len;
203 }
204 }
205 padding = padonwrite(padding, buf, len - pos, fout);
206 if (pos > 0)
207 memmove(buf, tmp, pos);
208 }
209 fflush(fout);
210 fclose(fout);
211 fclose(fin);
212
213 if (padding != 0 && quiet == 0) {
214 fprintf(stdout, "warning: couldn't find a location to write "
215 "%zd padding bytes in %s\n", padding, fo);
216 }
217
218 return(0);
219 }
220
221 int dirwalk(char *src, char *srcp, char *trg, char *trgp) {
222 DIR *d;
223 struct dirent *de;
224 struct stat s;
225 struct timeval times[2];
226 #ifndef HAVE_UTIMES
227 struct utimbuf ub;
228 #endif
229 char *st;
230 char *tt;
231
232 if (lstat(trg, &s) != 0) {
233 /* initially create directory read/writable by owner, set
234 * permissions like src when we're done processing this
235 * directory. */
236 if (mkdir(trg, S_IRWXU) != 0) {
237 fprintf(stderr, "failed to create directory %s: %s\n",
238 trg, strerror(errno));
239 return(-1);
240 }
241 } else {
242 fprintf(stderr, "directory already exists: %s\n", trg);
243 return(-1);
244 }
245
246 if ((d = opendir(src)) == NULL) {
247 fprintf(stderr, "cannot read directory %s: %s\n", src, strerror(errno));
248 return(-1);
249 }
250 /* store the end of the string pointer */
251 st = srcp;
252 tt = trgp;
253 while ((de = readdir(d)) != NULL) {
254 if (strcmp(de->d_name, "..") == 0 || strcmp(de->d_name, ".") == 0)
255 continue;
256
257 *st = '/';
258 strcpy(st + 1, de->d_name);
259 *tt = '/';
260 strcpy(tt + 1, de->d_name);
261 st += 1 + strlen(de->d_name);
262 tt += 1 + strlen(de->d_name);
263
264 if (lstat(src, &s) != 0) {
265 fprintf(stderr, "cannot stat %s: %s\n", src, strerror(errno));
266 closedir(d);
267 return(-1);
268 }
269 if (
270 S_ISBLK(s.st_mode) ||
271 S_ISCHR(s.st_mode) ||
272 S_ISFIFO(s.st_mode) ||
273 #ifdef HAVE_S_ISWHT
274 S_ISWHT(s.st_mode) ||
275 #endif
276 S_ISSOCK(s.st_mode)
277 )
278 {
279 fprintf(stderr, "missing implementation for copying "
280 "object %s\n", src);
281 closedir(d);
282 return(-1);
283 } else if (
284 S_ISDIR(s.st_mode)
285 )
286 {
287 /* recurse */
288 if (dirwalk(src, st, trg, tt) != 0)
289 return(-1);
290 } else if (
291 S_ISREG(s.st_mode)
292 )
293 {
294 /* handle hard links, match each of them to a list of known
295 * files (with st_nlink > 1), such that we only process each
296 * file once, and are able to restore the hard link. */
297 if (s.st_nlink > 1) {
298 if (hlinks == NULL) {
299 hlinks = malloc(sizeof(file_hlink));
300 hlinks->st_dev = s.st_dev;
301 hlinks->st_ino = s.st_ino;
302 strcpy(hlinks->path, src);
303 hlinks->next = NULL;
304 } else {
305 /* look for this file */
306 file_hlink *hl = NULL;
307 do {
308 hl = (hl == NULL ? hlinks : hl->next);
309 if (hl->st_dev == s.st_dev && hl->st_ino == s.st_ino) {
310 /* this is the same file, make a hard link */
311 if (link(hl->path, trg) != 0) {
312 fprintf(stderr, "failed to create hard link "
313 "%s: %s\n", trg, strerror(errno));
314 return(-1);
315 }
316 hl = NULL;
317 break;
318 }
319 } while (hl->next != NULL);
320 /* we didn't know this one yet, add it */
321 if (hl != NULL) {
322 hl = hl->next = malloc(sizeof(file_hlink));
323 hl->st_dev = s.st_dev;
324 hl->st_ino = s.st_ino;
325 strcpy(hl->path, src);
326 hl->next = NULL;
327 } else {
328 /* don't "copy" the file, we already made a hard
329 * link to it, just restore modified path */
330 st = srcp;
331 tt = trgp;
332 *st = *tt = '\0';
333 continue;
334 }
335 }
336 }
337
338 /* copy */
339 if (chpath(src, trg) != 0) {
340 closedir(d);
341 return(-1);
342 }
343 /* fix permissions */
344 if (chmod(trg, s.st_mode) != 0) {
345 fprintf(stderr, "failed to set permissions of %s: %s\n",
346 trg, strerror(errno));
347 return(-1);
348 }
349 if (chown(trg, s.st_uid, s.st_gid) != 0) {
350 fprintf(stderr, "failed to set ownership of %s: %s\n",
351 trg, strerror(errno));
352 return(-1);
353 }
354 times[0].tv_sec = s.ATIME_SEC;
355 #ifdef ATIME_NSEC
356 times[0].tv_usec = (s.ATIME_NSEC) / 1000;
357 #else
358 times[0].tv_usec = 0;
359 #endif
360 times[1].tv_sec = s.MTIME_SEC;
361 #ifdef MTIME_NSEC
362 times[1].tv_usec = (s.MTIME_NSEC) / 1000;
363 #else
364 times[1].tv_usec = 0;
365 #endif
366 #ifdef HAVE_UTIMES
367 if (utimes(trg, times) != 0) {
368 fprintf(stderr, "failed to set utimes of %s: %s\n",
369 trg, strerror(errno));
370 return(-1);
371 }
372 #else
373 ub.actime = s.ATIME_SEC;
374 ub.modtime = s.MTIME_SEC;
375 if (utime(trg, &ub) != 0) {
376 fprintf(stderr, "failed to set utime of %s: %s\n",
377 trg, strerror(errno));
378 return(-1);
379 }
380 #endif
381 } else if (
382 S_ISLNK(s.st_mode)
383 )
384 {
385 char buf[MAX_PATH];
386 char rep[MAX_PATH];
387 char *pb = buf;
388 char *pr = rep;
389 char *p = NULL;
390 int len = readlink(src, buf, MAX_PATH - 1);
391 buf[len] = '\0';
392 /* replace occurences of magic by value in the string if
393 * absolute */
394 if (buf[0] == '/') while ((p = strstr(pb, magic)) != NULL) {
395 memcpy(pr, pb, p - pb);
396 pr += p - pb;
397 memcpy(pr, value, valuelen);
398 pr += valuelen;
399 pb += magiclen;
400 }
401 len = (&buf[0] + len) - pb;
402 memcpy(pr, pb, len);
403 pr[len] = '\0';
404
405 if (symlink(rep, trg) != 0) {
406 fprintf(stderr, "failed to create symlink %s -> %s: %s\n",
407 trg, rep, strerror(errno));
408 return(-1);
409 }
410
411 #ifdef HAVE_LCHOWN
412 if (lchown(trg, s.st_uid, s.st_gid) != 0) {
413 fprintf(stderr, "failed to set ownership of %s: %s\n",
414 trg, strerror(errno));
415 return(-1);
416 }
417 #endif
418 }
419
420 /* restore modified path */
421 st = srcp;
422 tt = trgp;
423 *st = *tt = '\0';
424 }
425 closedir(d);
426
427 /* fix permissions/ownership etc. */
428 if (lstat(src, &s) != 0) {
429 fprintf(stderr, "cannot stat %s: %s\n", src, strerror(errno));
430 return(-1);
431 }
432 if (chmod(trg, s.st_mode) != 0) {
433 fprintf(stderr, "failed to set permissions of %s: %s\n",
434 trg, strerror(errno));
435 return(-1);
436 }
437 if (chown(trg, s.st_uid, s.st_gid) != 0) {
438 fprintf(stderr, "failed to set ownership of %s: %s\n",
439 trg, strerror(errno));
440 return(-1);
441 }
442 times[0].tv_sec = s.ATIME_SEC;
443 #ifdef ATIME_NSEC
444 times[0].tv_usec = (s.ATIME_NSEC) / 1000;
445 #else
446 times[0].tv_usec = 0;
447 #endif
448 times[1].tv_sec = s.MTIME_SEC;
449 #ifdef MTIME_NSEC
450 times[1].tv_usec = (s.MTIME_NSEC) / 1000;
451 #else
452 times[1].tv_usec = 0;
453 #endif
454 #ifdef HAVE_UTIMES
455 if (utimes(trg, times) != 0) {
456 fprintf(stderr, "failed to set utimes of %s: %s\n",
457 trg, strerror(errno));
458 return(-1);
459 }
460 #else
461 ub.actime = s.ATIME_SEC;
462 ub.modtime = s.MTIME_SEC;
463 if (utime(trg, &ub) != 0) {
464 fprintf(stderr, "failed to set utime of %s: %s\n",
465 trg, strerror(errno));
466 return(-1);
467 }
468 #endif
469
470 return(0);
471 }
472
473 int main(int argc, char **argv) {
474 struct stat file;
475 int o = 0;
476
477 quiet = 0;
478 if (argc >= 2 && strcmp(argv[1], "-q") == 0) {
479 argc--;
480 o++;
481 quiet = 1;
482 }
483
484 if (argc != 5) {
485 fprintf(stderr, "usage: [-q] in-file out-file magic value\n");
486 fprintf(stderr, " if in-file is a directory, out-file is "
487 "treated as one too\n");
488 fprintf(stderr, " -q suppress messages about being unable to "
489 "write padding bytes\n");
490 return(-1);
491 }
492
493 magic = argv[o + 3];
494 value = argv[o + 4];
495 magiclen = strlen(magic);
496 valuelen = strlen(value);
497
498 if (magiclen < valuelen) {
499 fprintf(stderr, "value length (%zd) is bigger than "
500 "the magic length (%zd)\n", valuelen, magiclen);
501 return(-1);
502 }
503 if (magiclen > BUFSIZE) {
504 fprintf(stderr, "magic length (%zd) is bigger than "
505 "BUFSIZE (%d), unable to process\n", magiclen, BUFSIZE);
506 return(-1);
507 }
508
509 if (lstat(argv[o + 1], &file) != 0) {
510 fprintf(stderr, "unable to stat %s: %s\n",
511 argv[o + 1], strerror(errno));
512 return(-1);
513 }
514 if (S_ISDIR(file.st_mode)) {
515 char *src = alloca(sizeof(char) * MAX_PATH);
516 char *trg = alloca(sizeof(char) * MAX_PATH);
517 strcpy(src, argv[o + 1]);
518 strcpy(trg, argv[o + 2]);
519 /* walk this directory and process recursively */
520 return(dirwalk(src, src + strlen(argv[o + 1]),
521 trg, trg + strlen(argv[o + 2])));
522 } else {
523 /* process as normal file */
524 return(chpath(argv[o + 1], argv[o + 2]));
525 }
526 }
527