1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <files_links.h>
26
27 #include <actuator.h>
28 #include <promises.h>
29 #include <files_names.h>
30 #include <files_interfaces.h>
31 #include <files_operators.h>
32 #include <files_lib.h>
33 #include <locks.h>
34 #include <string_lib.h>
35 #include <misc_lib.h>
36 #include <eval_context.h>
37 #include <changes_chroot.h> /* PrepareChangesChroot() */
38
39 #if !defined(__MINGW32__)
40 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result);
41 #endif
42 static char *AbsLinkPath(const char *from, const char *relto);
43
44 /*****************************************************************************/
45
46 #ifdef __MINGW32__
47
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)48 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
49 {
50 RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyLink())");
51 return PROMISE_RESULT_FAIL;
52 }
53
54 #else
55
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)56 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
57 {
58 assert(attr != NULL);
59 /* VerifyHardLink() is a separate function */
60 assert(attr->link.link_type != FILE_LINK_TYPE_HARDLINK);
61
62 char to[CF_BUFSIZE], linkbuf[CF_BUFSIZE], absto[CF_BUFSIZE];
63 struct stat sb;
64
65 memset(to, 0, CF_BUFSIZE);
66
67 const bool absolute_source = IsAbsoluteFileName(source);
68
69 if ((!absolute_source) && (*source != '.')) /* links without a directory reference */
70 {
71 snprintf(to, CF_BUFSIZE - 1, "./%s", source);
72 }
73 else
74 {
75 strlcpy(to, source, CF_BUFSIZE);
76 }
77
78 if (!absolute_source) /* relative path, must still check if exists */
79 {
80 Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
81 strcpy(absto, AbsLinkPath(destination, to));
82 Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination);
83 }
84 else
85 {
86 strcpy(absto, to);
87 }
88
89 /* If making changes in a chroot, we need to get the link target into the
90 * chroot. */
91 if (ChrootChanges())
92 {
93 PrepareChangesChroot(absto);
94 }
95
96 const char *changes_absto = absto;
97 if (ChrootChanges())
98 {
99 changes_absto = ToChangesChroot(absto);
100 }
101
102 bool source_file_exists = true;
103
104 if (stat(changes_absto, &sb) == -1)
105 {
106 Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
107 source_file_exists = false;
108 }
109
110 if ((!source_file_exists) && (attr->link.when_no_file != cfa_force) && (attr->link.when_no_file != cfa_delete))
111 {
112 Log(LOG_LEVEL_VERBOSE, "Source '%s' for linking is absent", absto);
113 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s', no source", destination, to);
114 return PROMISE_RESULT_FAIL;
115 }
116
117 const char *changes_destination = destination;
118 if (ChrootChanges())
119 {
120 changes_destination = ToChangesChroot(destination);
121 }
122
123 PromiseResult result = PROMISE_RESULT_NOOP;
124 if ((!source_file_exists) && (attr->link.when_no_file == cfa_delete))
125 {
126 KillGhostLink(ctx, changes_destination, attr, pp, &result);
127 return result;
128 }
129
130 memset(linkbuf, 0, CF_BUFSIZE);
131
132 if (readlink(changes_destination, linkbuf, CF_BUFSIZE - 1) == -1)
133 {
134 if (!MakingChanges(ctx, pp, attr, &result, "create link '%s'", destination))
135 {
136 return result;
137 }
138
139 bool dir_created = false;
140 if (MakeParentDirectoryForPromise(ctx, pp, attr, &result,
141 destination, attr->move_obstructions,
142 &dir_created))
143 {
144 if (dir_created)
145 {
146 RecordChange(ctx, pp, attr, "Created parent directory for link '%s'", destination);
147 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
148 }
149 if (!MoveObstruction(ctx, destination, attr, pp, &result))
150 {
151 RecordFailure(ctx, pp, attr,
152 "Unable to create link '%s' -> '%s', failed to move obstruction",
153 destination, to);
154 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
155 return result;
156 }
157
158 if (!MakeLink(ctx, destination, source, attr, pp, &result))
159 {
160 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s'", destination, to);
161 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
162 }
163 }
164 return result;
165 }
166 else
167 {
168 /* to == "./$source" */
169 bool link_correct = (StringEqual(linkbuf, source) || StringEqual(linkbuf, to));
170
171 /* If making changes in chroot, the existing symlink can also be
172 * pointing to the changes chroot (if it's absolute). */
173 if (!link_correct && absolute_source && ChrootChanges())
174 {
175 link_correct = StringEqual(linkbuf, ToChangesChroot(source));
176 }
177 if (link_correct)
178 {
179 RecordNoChange(ctx, pp, attr, "Link '%s' points to '%s', promise kept", destination, source);
180 return PROMISE_RESULT_NOOP;
181 }
182 /* else */
183 if (attr->move_obstructions)
184 {
185 if (MakingChanges(ctx, pp, attr, &result, "remove incorrect link '%s'", destination))
186 {
187 if (unlink(ToChangesPath(destination)) == -1)
188 {
189 RecordFailure(ctx, pp, attr, "Error removing link '%s' (points to '%s' not '%s')",
190 destination, linkbuf, to);
191 return PROMISE_RESULT_FAIL;
192 }
193 RecordChange(ctx, pp, attr, "Overrode incorrect link '%s'", destination);
194 result = PROMISE_RESULT_CHANGE;
195
196 MakeLink(ctx, destination, source, attr, pp, &result);
197 return result;
198 }
199 else
200 {
201 return result;
202 }
203 }
204 else
205 {
206 RecordFailure(ctx, pp, attr,
207 "Link '%s' points to '%s' not '%s', but not moving obstructions",
208 destination, linkbuf, to);
209 return PROMISE_RESULT_FAIL;
210 }
211 }
212 }
213 #endif /* !__MINGW32__ */
214
215 /*****************************************************************************/
216
217 #ifdef __MINGW32__
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)218 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
219 {
220 RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyAbsoluteLink())");
221 return PROMISE_RESULT_FAIL;
222 }
223
224 #else
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)225 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
226 {
227 assert(attr != NULL);
228
229 char absto[CF_BUFSIZE];
230 char expand[CF_BUFSIZE];
231 char linkto[CF_BUFSIZE];
232
233 if (*source == '.')
234 {
235 strcpy(linkto, destination);
236 ChopLastNode(linkto);
237 JoinPaths(linkto, sizeof(linkto), source);
238 }
239 else
240 {
241 strcpy(linkto, source);
242 }
243
244 CompressPath(absto, sizeof(absto), linkto);
245
246 expand[0] = '\0';
247
248 if (attr->link.when_no_file == cfa_force)
249 {
250 bool expanded;
251 struct stat sb;
252 if (ChrootChanges() && (lstat(ToChangesChroot(absto), &sb) != -1))
253 {
254 char chrooted_expand[sizeof(expand)];
255 chrooted_expand[0] = '\0';
256 expanded = ExpandLinks(chrooted_expand, ToChangesChroot(absto), 0, CF_MAXLINKLEVEL);
257 strlcpy(expand, ToNormalRoot(chrooted_expand), sizeof(expand));
258 }
259 else
260 {
261 expanded = ExpandLinks(expand, absto, 0, CF_MAXLINKLEVEL);
262 }
263 if (expanded)
264 {
265 Log(LOG_LEVEL_DEBUG, "ExpandLinks returned '%s'", expand);
266 }
267 else
268 {
269 RecordFailure(ctx, pp, attr, "Failed to expand absolute link to '%s'", source);
270 PromiseRef(LOG_LEVEL_ERR, pp);
271 return PROMISE_RESULT_FAIL;
272 }
273 }
274 else
275 {
276 strcpy(expand, absto);
277 }
278
279 CompressPath(linkto, sizeof(linkto), expand);
280
281 return VerifyLink(ctx, destination, linkto, attr, pp);
282 }
283 #endif /* __MINGW32__ */
284
285 /*****************************************************************************/
286
VerifyRelativeLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)287 PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
288 {
289 char *sp, *commonto, *commonfrom;
290 char buff[CF_BUFSIZE], linkto[CF_BUFSIZE];
291 int levels = 0;
292
293 if (*source == '.')
294 {
295 return VerifyLink(ctx, destination, source, attr, pp);
296 }
297
298 if (!CompressPath(linkto, sizeof(linkto), source))
299 {
300 RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s'", destination, source);
301 return PROMISE_RESULT_INTERRUPTED;
302 }
303
304 commonto = linkto;
305 commonfrom = destination;
306
307 if (strcmp(commonto, commonfrom) == 0)
308 {
309 RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s', can't link file '%s' to itself",
310 destination, source, commonto);
311 return PROMISE_RESULT_INTERRUPTED;
312 }
313
314 while (*commonto == *commonfrom)
315 {
316 commonto++;
317 commonfrom++;
318 }
319
320 while (!((IsAbsoluteFileName(commonto)) && (IsAbsoluteFileName(commonfrom))))
321 {
322 commonto--;
323 commonfrom--;
324 }
325
326 commonto++;
327
328 for (sp = commonfrom; *sp != '\0'; sp++)
329 {
330 if (IsFileSep(*sp))
331 {
332 levels++;
333 }
334 }
335
336 memset(buff, 0, CF_BUFSIZE);
337
338 strcat(buff, ".");
339 strcat(buff, FILE_SEPARATOR_STR);
340
341 while (--levels > 0)
342 {
343 const char add[] = ".." FILE_SEPARATOR_STR;
344
345 if (!PathAppend(buff, sizeof(buff), add, FILE_SEPARATOR))
346 {
347 RecordFailure(ctx, pp, attr,
348 "Internal limit reached in VerifyRelativeLink(),"
349 " path too long: '%s' + '%s'",
350 buff, add);
351 return PROMISE_RESULT_FAIL;
352 }
353 }
354
355 if (!PathAppend(buff, sizeof(buff), commonto, FILE_SEPARATOR))
356 {
357 RecordFailure(ctx, pp, attr,
358 "Internal limit reached in VerifyRelativeLink() end,"
359 " path too long: '%s' + '%s'",
360 buff, commonto);
361 return PROMISE_RESULT_FAIL;
362 }
363
364 return VerifyLink(ctx, destination, buff, attr, pp);
365 }
366
367 /*****************************************************************************/
368
VerifyHardLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)369 PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
370 {
371 char to[CF_BUFSIZE], absto[CF_BUFSIZE];
372 struct stat ssb, dsb;
373
374 memset(to, 0, CF_BUFSIZE);
375
376 const bool absolute_source = IsAbsoluteFileName(source);
377
378 if ((!absolute_source) && (*source != '.')) /* links without a directory reference */
379 {
380 snprintf(to, CF_BUFSIZE - 1, ".%c%s", FILE_SEPARATOR, source);
381 }
382 else
383 {
384 strlcpy(to, source, CF_BUFSIZE);
385 }
386
387 if (!absolute_source) /* relative path, must still check if exists */
388 {
389 Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
390 strcpy(absto, AbsLinkPath(destination, to));
391 Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination);
392 }
393 else
394 {
395 strcpy(absto, to);
396 }
397
398 /* If making changes in a chroot, we need to get the link target into the
399 * chroot. */
400 if (ChrootChanges())
401 {
402 PrepareChangesChroot(absto);
403 }
404
405 const char *changes_absto = absto;
406 if (ChrootChanges())
407 {
408 changes_absto = ToChangesChroot(absto);
409 }
410
411 if (stat(changes_absto, &ssb) == -1)
412 {
413 Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
414 }
415
416 if (!S_ISREG(ssb.st_mode))
417 {
418 RecordFailure(ctx, pp, attr,
419 "Source file '%s' is not a regular file, not appropriate to hard-link", to);
420 return PROMISE_RESULT_FAIL;
421 }
422
423 Log(LOG_LEVEL_DEBUG, "Trying to hard link '%s' -> '%s'", destination, to);
424
425 if (stat(ChrootChanges()? ToChangesChroot(destination) : destination, &dsb) == -1)
426 {
427 PromiseResult result = PROMISE_RESULT_NOOP;
428 MakeHardLink(ctx, destination, to, attr, pp, &result);
429 return result;
430 }
431
432 /* both files exist, but are they the same file? POSIX says */
433 /* the files could be on different devices, but unix doesn't */
434 /* allow this behaviour so the tests below are theoretical... */
435
436 if ((dsb.st_ino != ssb.st_ino) && (dsb.st_dev != ssb.st_dev))
437 {
438 Log(LOG_LEVEL_VERBOSE,
439 "If this is POSIX, unable to determine if %s is hard link is correct"
440 " since it points to a different filesystem",
441 destination);
442
443 if ((dsb.st_mode == ssb.st_mode) && (dsb.st_size == ssb.st_size))
444 {
445 RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' on different device appears okay",
446 destination, to);
447 return PROMISE_RESULT_NOOP;
448 }
449 }
450
451 if ((dsb.st_ino == ssb.st_ino) && (dsb.st_dev == ssb.st_dev))
452 {
453 RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' exists and is okay",
454 destination, to);
455 return PROMISE_RESULT_NOOP;
456 }
457
458 const char *chroot_msg = "";
459 if (ChrootChanges())
460 {
461 chroot_msg = " (but hardlinks are always replicated to the changes chroot)";
462 }
463 Log(LOG_LEVEL_INFO, "'%s' does not appear to be a hard link to '%s'%s", destination, to, chroot_msg);
464
465 PromiseResult result = PROMISE_RESULT_NOOP;
466 if (!MakingChanges(ctx, pp, attr, &result, "hard link '%s' -> '%s'", destination, to))
467 {
468 return result;
469 }
470
471 if (!MoveObstruction(ctx, destination, attr, pp, &result))
472 {
473 RecordFailure(ctx, pp, attr,
474 "Unable to create hard link '%s' -> '%s', failed to move obstruction",
475 destination, to);
476 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
477 return result;
478 }
479
480 MakeHardLink(ctx, destination, to, attr, pp, &result);
481 return result;
482 }
483
484 /*****************************************************************************/
485 /* Level */
486 /*****************************************************************************/
487
488 #ifdef __MINGW32__
489
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)490 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result)
491 {
492 RecordFailure(ctx, pp, attr, "Cannot remove dead link '%s' (Windows does not support symbolic links)", name);
493 PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
494 return false;
495 }
496
497 #else /* !__MINGW32__ */
498
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)499 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp,
500 PromiseResult *result)
501 {
502 char linkbuf[CF_BUFSIZE], tmp[CF_BUFSIZE];
503 char linkpath[CF_BUFSIZE], *sp;
504 struct stat statbuf;
505
506 memset(linkbuf, 0, CF_BUFSIZE);
507 memset(linkpath, 0, CF_BUFSIZE);
508
509 const char *changes_name = name;
510 if (ChrootChanges())
511 {
512 changes_name = ToChangesChroot(name);
513 }
514
515 if (readlink(changes_name, linkbuf, CF_BUFSIZE - 1) == -1)
516 {
517 Log(LOG_LEVEL_VERBOSE, "Can't read link '%s' while checking for deadlinks", name);
518 return true; /* ignore */
519 }
520
521 if (!IsAbsoluteFileName(linkbuf))
522 {
523 strcpy(linkpath, changes_name); /* Get path to link */
524
525 for (sp = linkpath + strlen(linkpath); (*sp != FILE_SEPARATOR) && (sp >= linkpath); sp--)
526 {
527 *sp = '\0';
528 }
529 }
530
531 strcat(linkpath, linkbuf);
532 CompressPath(tmp, sizeof(tmp), linkpath);
533
534 if (stat(tmp, &statbuf) == -1) /* link points nowhere */
535 {
536 if ((attr->link.when_no_file == cfa_delete) || (attr->recursion.rmdeadlinks))
537 {
538 Log(LOG_LEVEL_VERBOSE,
539 "'%s' is a link which points to '%s', but the target doesn't seem to exist",
540 name, linkbuf);
541
542 if (MakingChanges(ctx, pp, attr, result, "remove dead link '%s'", name))
543 {
544 unlink(changes_name); /* May not work on a client-mounted system ! */
545 RecordChange(ctx, pp, attr, "Removed dead link '%s'", name);
546 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
547 return true;
548 }
549 else
550 {
551 return true;
552 }
553 }
554 }
555
556 return false;
557 }
558 #endif /* !__MINGW32__ */
559
560 /*****************************************************************************/
561
562 #if !defined(__MINGW32__)
MakeLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)563 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
564 PromiseResult *result)
565 {
566 if (MakingChanges(ctx, pp, attr, result, "link files '%s' -> '%s'", from, to))
567 {
568 const char *changes_to = to;
569 char *chroot_to = NULL;
570 if (ChrootChanges())
571 {
572 /* Create a copy because the next call to ToChangesChroot() will
573 * overwrite the value. */
574 chroot_to = xstrdup(ToChangesChroot(to));
575 changes_to = chroot_to;
576 }
577 const char *changes_from = from;
578 if (ChrootChanges())
579 {
580 changes_from = ToChangesChroot(from);
581 }
582
583 if (symlink(changes_to, changes_from) == -1)
584 {
585 RecordFailure(ctx, pp, attr, "Couldn't link '%s' to '%s'. (symlink: %s)",
586 to, from, GetErrorStr());
587 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
588 free(chroot_to);
589 return false;
590 }
591 else
592 {
593 RecordChange(ctx, pp, attr, "Linked files '%s' -> '%s'", from, to);
594 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
595 free(chroot_to);
596 return true;
597 }
598 }
599 else
600 {
601 return false;
602 }
603 }
604 #endif /* !__MINGW32__ */
605
606 /*****************************************************************************/
607
608 #ifdef __MINGW32__
609
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)610 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
611 PromiseResult *result)
612 { // TODO: Implement ?
613 RecordFailure(ctx, pp, attr,
614 "Couldn't hard link '%s' to '%s' (Hard links are not yet supported on Windows)",
615 to, from);
616 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
617 return false;
618 }
619
620 #else /* !__MINGW32__ */
621
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)622 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
623 PromiseResult *result)
624 {
625 if (MakingChanges(ctx, pp, attr, result, "hard link files '%s' -> '%s'", from, to))
626 {
627 const char *changes_to = to;
628 char *chroot_to = NULL;
629 if (ChrootChanges())
630 {
631 /* Create a copy because the next call to ToChangesChroot() will
632 * overwrite the value. */
633 chroot_to = xstrdup(ToChangesChroot(to));
634 changes_to = chroot_to;
635 }
636 const char *changes_from = from;
637 if (ChrootChanges())
638 {
639 changes_from = ToChangesChroot(from);
640 }
641
642 if (link(changes_to, changes_from) == -1)
643 {
644 RecordFailure(ctx, pp, attr, "Failed to hard link '%s' to '%s'. (link: %s)",
645 to, from, GetErrorStr());
646 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
647 return false;
648 }
649 else
650 {
651 RecordChange(ctx, pp, attr, "Hard linked file '%s' -> '%s'", from, to);
652 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
653 return true;
654 }
655 }
656 else
657 {
658 return false;
659 }
660 }
661
662 #endif /* !__MINGW32__ */
663
664 /*********************************************************************/
665
666 /* Expand a path contaning symbolic links, up to 4 levels */
667 /* of symbolic links and then beam out in a hurry ! */
668
669 #ifdef __MINGW32__
670
ExpandLinks(char * dest,const char * from,int level,int max_level)671 bool ExpandLinks(char *dest, const char *from, int level, int max_level)
672 {
673 Log(LOG_LEVEL_ERR, "Windows does not support symbolic links (at ExpandLinks(%s,%s))", dest, from);
674 return false;
675 }
676
677 #else /* !__MINGW32__ */
678
ExpandLinks(char * dest,const char * from,int level,int max_level)679 bool ExpandLinks(char *dest, const char *from, int level, int max_level)
680 {
681 char buff[CF_BUFSIZE];
682 char node[CF_MAXLINKSIZE];
683 struct stat statbuf;
684 int lastnode = false;
685
686 memset(dest, 0, CF_BUFSIZE);
687
688 if (level >= CF_MAXLINKLEVEL)
689 {
690 Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path");
691 return false;
692 }
693
694 if (level >= max_level)
695 {
696 Log(LOG_LEVEL_DEBUG, "Reached maximum level of symbolic link resolution");
697 return true;
698 }
699
700 const char *sp = from;
701
702 while (*sp != '\0')
703 {
704 if (*sp == FILE_SEPARATOR)
705 {
706 sp++;
707 continue;
708 }
709
710 sscanf(sp, "%[^/]", node);
711 sp += strlen(node);
712
713 if (*sp == '\0')
714 {
715 lastnode = true;
716 }
717
718 if (strcmp(node, ".") == 0)
719 {
720 continue;
721 }
722
723 if (strcmp(node, "..") == 0)
724 {
725 strcat(dest, "/..");
726 continue;
727 }
728 else
729 {
730 strcat(dest, "/");
731 }
732
733 strcat(dest, node);
734
735 if (lstat(dest, &statbuf) == -1) /* File doesn't exist so we can stop here */
736 {
737 Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr());
738 return false;
739 }
740
741 if (S_ISLNK(statbuf.st_mode))
742 {
743 memset(buff, 0, CF_BUFSIZE);
744
745 if (readlink(dest, buff, CF_BUFSIZE - 1) == -1)
746 {
747 Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr());
748 return false;
749 }
750 else
751 {
752 if (buff[0] == '.')
753 {
754 ChopLastNode(dest);
755 AddSlash(dest);
756
757 /* TODO pass and use parameter dest_size. */
758 size_t ret = strlcat(dest, buff, CF_BUFSIZE);
759 if (ret >= CF_BUFSIZE)
760 {
761 Log(LOG_LEVEL_ERR,
762 "Internal limit reached in ExpandLinks(),"
763 " path too long: '%s' + '%s'",
764 dest, buff);
765 return false;
766 }
767 }
768 else if (IsAbsoluteFileName(buff))
769 {
770 strcpy(dest, buff);
771 DeleteSlash(dest);
772
773 if (strcmp(dest, from) == 0)
774 {
775 Log(LOG_LEVEL_DEBUG, "No links to be expanded");
776 return true;
777 }
778
779 if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level)))
780 {
781 return false;
782 }
783 }
784 else
785 {
786 ChopLastNode(dest);
787 AddSlash(dest);
788
789 /* TODO use param dest_size. */
790 size_t ret = strlcat(dest, buff, CF_BUFSIZE);
791 if (ret >= CF_BUFSIZE)
792 {
793 Log(LOG_LEVEL_ERR,
794 "Internal limit reached in ExpandLinks end,"
795 " path too long: '%s' + '%s'", dest, buff);
796 return false;
797 }
798
799 DeleteSlash(dest);
800
801 if (strcmp(dest, from) == 0)
802 {
803 Log(LOG_LEVEL_DEBUG, "No links to be expanded");
804 return true;
805 }
806
807 memset(buff, 0, CF_BUFSIZE);
808
809 if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level)))
810 {
811 return false;
812 }
813 }
814 }
815 }
816 }
817
818 return true;
819 }
820 #endif /* !__MINGW32__ */
821
822 /*********************************************************************/
823
AbsLinkPath(const char * from,const char * relto)824 static char *AbsLinkPath(const char *from, const char *relto)
825 /* Take an abolute source and a relative destination object
826 and find the absolute name of the to object */
827 {
828 int pop = 1;
829 static char destination[CF_BUFSIZE]; /* GLOBAL_R, no need to initialize */
830
831 if (IsAbsoluteFileName(relto))
832 {
833 ProgrammingError("Call to AbsLInkPath with absolute pathname");
834 }
835
836 strcpy(destination, from); /* reuse to save stack space */
837
838 const char *sp = NULL;
839 for (sp = relto; *sp != '\0'; sp++)
840 {
841 if (strncmp(sp, "../", 3) == 0)
842 {
843 pop++;
844 sp += 2;
845 continue;
846 }
847
848 if (strncmp(sp, "./", 2) == 0)
849 {
850 sp += 1;
851 continue;
852 }
853
854 break; /* real link */
855 }
856
857 while (pop > 0)
858 {
859 ChopLastNode(destination);
860 pop--;
861 }
862
863 if (strlen(destination) == 0)
864 {
865 strcpy(destination, "/");
866 }
867 else
868 {
869 AddSlash(destination);
870 }
871
872 strcat(destination, sp);
873 Log(LOG_LEVEL_DEBUG, "Reconstructed absolute linkname '%s'", destination);
874 return destination;
875 }
876