1 /* Copyright Gentoo Foundation 2006-2010
2 * Author: Fabian Groffen <grobian@gentoo.org>
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.
19 #include <sys/types.h>
32 /* Don't allocate too much, or you'll be paying for waiting on IO,
33 * size -1 to align in memory. */
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 */
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 */
50 static size_t magiclen
;
51 static size_t valuelen
;
53 static file_hlink
*hlinks
= NULL
;
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
60 static size_t padonwrite(size_t padding
, char *buf
, size_t len
, FILE *fout
) {
62 if (padding
== 0 || (z
= memchr(buf
, '\0', len
)) == NULL
) {
63 /* cheap case, nothing complicated to do here */
64 fwrite(buf
, len
, 1, fout
);
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 */
72 fwrite(buf
, 1, 1, fout
);
75 fwrite(z
, len
- (z
- buf
), 1, fout
);
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.
88 static char *memstr(const char *buf
, const char *needle
, size_t len
) {
91 for (ret
= buf
; ret
- buf
< len
; ret
++) {
93 while (needle
[off
] != '\0' &&
94 (ret
- buf
) + off
< len
&&
95 needle
[off
] == ret
[off
])
99 if (needle
[off
] == '\0' || (ret
- buf
) + off
== len
)
106 * Copies the given file to the output file with prefix transformations
109 static int chpath(const char *fi
, const char *fo
) {
116 char buf
[BUFSIZE
+ 1];
118 char *lvalue
= value
;
119 size_t lvaluelen
= valuelen
;
121 /* make sure there is a trailing zero-byte, such that strstr and
122 * strchr won't go out of bounds causing segfaults. */
125 fin
= fopen(fi
, "r");
127 fprintf(stderr
, "unable to open %s: %s\n", fi
, strerror(errno
));
130 fout
= fopen(fo
, "w");
132 fprintf(stderr
, "unable to open %s: %s\n", fo
, strerror(errno
));
139 while ((len
= fread(buf
+ pos
, 1, BUFSIZE
- pos
, fin
)) != 0 || pos
> 0) {
140 if (firstblock
== 1) {
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
)
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
);
172 if ((tmp
= memstr(buf
, magic
, len
)) != NULL
) {
175 if (len
< magiclen
) {
176 /* must be last piece */
177 padding
= padonwrite(padding
, buf
, len
, fout
);
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
);
189 /* move this bunch to the front */
190 pos
= len
- (tmp
- buf
);
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
) {
198 padding
= padonwrite(padding
, buf
, len
, fout
);
205 padding
= padonwrite(padding
, buf
, len
- pos
, fout
);
207 memmove(buf
, tmp
, pos
);
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
);
221 int dirwalk(char *src
, char *srcp
, char *trg
, char *trgp
) {
225 struct timeval times
[2];
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
236 if (mkdir(trg
, S_IRWXU
) != 0) {
237 fprintf(stderr
, "failed to create directory %s: %s\n",
238 trg
, strerror(errno
));
242 fprintf(stderr
, "directory already exists: %s\n", trg
);
246 if ((d
= opendir(src
)) == NULL
) {
247 fprintf(stderr
, "cannot read directory %s: %s\n", src
, strerror(errno
));
250 /* store the end of the string pointer */
253 while ((de
= readdir(d
)) != NULL
) {
254 if (strcmp(de
->d_name
, "..") == 0 || strcmp(de
->d_name
, ".") == 0)
258 strcpy(st
+ 1, de
->d_name
);
260 strcpy(tt
+ 1, de
->d_name
);
261 st
+= 1 + strlen(de
->d_name
);
262 tt
+= 1 + strlen(de
->d_name
);
264 if (lstat(src
, &s
) != 0) {
265 fprintf(stderr
, "cannot stat %s: %s\n", src
, strerror(errno
));
270 S_ISBLK(s
.st_mode
) ||
271 S_ISCHR(s
.st_mode
) ||
272 S_ISFIFO(s
.st_mode
) ||
274 S_ISWHT(s
.st_mode
) ||
279 fprintf(stderr
, "missing implementation for copying "
288 if (dirwalk(src
, st
, trg
, tt
) != 0)
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
);
305 /* look for this file */
306 file_hlink
*hl
= NULL
;
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
));
319 } while (hl
->next
!= NULL
);
320 /* we didn't know this one yet, add it */
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
);
328 /* don't "copy" the file, we already made a hard
329 * link to it, just restore modified path */
339 if (chpath(src
, trg
) != 0) {
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
));
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
));
354 times
[0].tv_sec
= s
.ATIME_SEC
;
356 times
[0].tv_usec
= (s
.ATIME_NSEC
) / 1000;
358 times
[0].tv_usec
= 0;
360 times
[1].tv_sec
= s
.MTIME_SEC
;
362 times
[1].tv_usec
= (s
.MTIME_NSEC
) / 1000;
364 times
[1].tv_usec
= 0;
367 if (utimes(trg
, times
) != 0) {
368 fprintf(stderr
, "failed to set utimes of %s: %s\n",
369 trg
, strerror(errno
));
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
));
390 int len
= readlink(src
, buf
, MAX_PATH
- 1);
392 /* replace occurences of magic by value in the string if
394 if (buf
[0] == '/') while ((p
= strstr(pb
, magic
)) != NULL
) {
395 memcpy(pr
, pb
, p
- pb
);
397 memcpy(pr
, value
, valuelen
);
401 len
= (&buf
[0] + len
) - pb
;
405 if (symlink(rep
, trg
) != 0) {
406 fprintf(stderr
, "failed to create symlink %s -> %s: %s\n",
407 trg
, rep
, strerror(errno
));
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
));
420 /* restore modified path */
427 /* fix permissions/ownership etc. */
428 if (lstat(src
, &s
) != 0) {
429 fprintf(stderr
, "cannot stat %s: %s\n", src
, strerror(errno
));
432 if (chmod(trg
, s
.st_mode
) != 0) {
433 fprintf(stderr
, "failed to set permissions of %s: %s\n",
434 trg
, strerror(errno
));
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
));
442 times
[0].tv_sec
= s
.ATIME_SEC
;
444 times
[0].tv_usec
= (s
.ATIME_NSEC
) / 1000;
446 times
[0].tv_usec
= 0;
448 times
[1].tv_sec
= s
.MTIME_SEC
;
450 times
[1].tv_usec
= (s
.MTIME_NSEC
) / 1000;
452 times
[1].tv_usec
= 0;
455 if (utimes(trg
, times
) != 0) {
456 fprintf(stderr
, "failed to set utimes of %s: %s\n",
457 trg
, strerror(errno
));
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
));
473 int main(int argc
, char **argv
) {
478 if (argc
>= 2 && strcmp(argv
[1], "-q") == 0) {
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");
495 magiclen
= strlen(magic
);
496 valuelen
= strlen(value
);
498 if (magiclen
< valuelen
) {
499 fprintf(stderr
, "value length (%zd) is bigger than "
500 "the magic length (%zd)\n", valuelen
, magiclen
);
503 if (magiclen
> BUFSIZE
) {
504 fprintf(stderr
, "magic length (%zd) is bigger than "
505 "BUFSIZE (%d), unable to process\n", magiclen
, BUFSIZE
);
509 if (lstat(argv
[o
+ 1], &file
) != 0) {
510 fprintf(stderr
, "unable to stat %s: %s\n",
511 argv
[o
+ 1], strerror(errno
));
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])));
523 /* process as normal file */
524 return(chpath(argv
[o
+ 1], argv
[o
+ 2]));