1 /*
2 Copyright 2020 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
38 #define CF_MAXLINKLEVEL 4
39
40 #if !defined(__MINGW32__)
41 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result);
42 #endif
43 static char *AbsLinkPath(const char *from, const char *relto);
44
EnforcePromise(enum cfopaction action)45 static bool EnforcePromise(enum cfopaction action)
46 {
47 return ((!DONTDO) && (action != cfa_warn));
48 }
49
50 /*****************************************************************************/
51
52 #ifdef __MINGW32__
53
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)54 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
55 {
56 RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyLink())");
57 return PROMISE_RESULT_FAIL;
58 }
59
60 #else
61
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)62 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
63 {
64 char to[CF_BUFSIZE], linkbuf[CF_BUFSIZE], absto[CF_BUFSIZE];
65 struct stat sb;
66
67 memset(to, 0, CF_BUFSIZE);
68
69 if ((!IsAbsoluteFileName(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 (!IsAbsoluteFileName(to)) /* 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 bool source_file_exists = true;
90
91 if (stat(absto, &sb) == -1)
92 {
93 Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
94 source_file_exists = false;
95 }
96
97 if ((!source_file_exists) && (attr->link.when_no_file != cfa_force) && (attr->link.when_no_file != cfa_delete))
98 {
99 Log(LOG_LEVEL_VERBOSE, "Source '%s' for linking is absent", absto);
100 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s', no source", destination, to);
101 return PROMISE_RESULT_FAIL;
102 }
103
104 if ((!source_file_exists) && (attr->link.when_no_file == cfa_delete))
105 {
106 PromiseResult result = PROMISE_RESULT_NOOP;
107 KillGhostLink(ctx, destination, attr, pp, &result);
108 return result;
109 }
110
111 memset(linkbuf, 0, CF_BUFSIZE);
112
113 if (readlink(destination, linkbuf, CF_BUFSIZE - 1) == -1)
114 {
115 if (!EnforcePromise(attr->transaction.action))
116 {
117 RecordWarning(ctx, pp, attr, "Link '%s' should be created", destination);
118 return PROMISE_RESULT_WARN;
119 }
120
121 bool dir_created = false;
122 if (!MakeParentDirectory2(destination, attr->move_obstructions,
123 EnforcePromise(attr->transaction.action), &dir_created))
124 {
125 RecordFailure(ctx, pp, attr, "Unable to create parent directory of link '%s' -> '%s' (enforce %d)",
126 destination, to, EnforcePromise(attr->transaction.action));
127 return PROMISE_RESULT_FAIL;
128 }
129 else
130 {
131 PromiseResult result = PROMISE_RESULT_NOOP;
132 if (dir_created)
133 {
134 RecordChange(ctx, pp, attr, "Created parent directory for link '%s'", destination);
135 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
136 }
137 if (!MoveObstruction(ctx, destination, attr, pp, &result))
138 {
139 RecordFailure(ctx, pp, attr,
140 "Unable to create link '%s' -> '%s', failed to move obstruction",
141 destination, to);
142 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
143 return result;
144 }
145
146 if (!MakeLink(ctx, destination, source, attr, pp, &result))
147 {
148 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s'", destination, to);
149 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
150 }
151
152 return result;
153 }
154 }
155 else
156 {
157 int ok = false;
158
159 if ((attr->link.link_type == FILE_LINK_TYPE_SYMLINK) && (strcmp(linkbuf, to) != 0) && (strcmp(linkbuf, source) != 0))
160 {
161 ok = true;
162 }
163 else if (strcmp(linkbuf, source) != 0)
164 {
165 ok = true;
166 }
167
168 if (ok)
169 {
170 if (attr->move_obstructions)
171 {
172 if (EnforcePromise(attr->transaction.action))
173 {
174 if (unlink(destination) == -1)
175 {
176 RecordFailure(ctx, pp, attr, "Error removing link '%s' (points to '%s' not '%s')",
177 destination, linkbuf, to);
178 return PROMISE_RESULT_FAIL;
179 }
180 RecordChange(ctx, pp, attr, "Overrode incorrect link '%s'", destination);
181 PromiseResult result = PROMISE_RESULT_CHANGE;
182
183 MakeLink(ctx, destination, source, attr, pp, &result);
184 return result;
185 }
186 else
187 {
188 RecordWarning(ctx, pp, attr, "Should remove incorrect link '%s'", destination);
189 return PROMISE_RESULT_WARN;
190 }
191 }
192 else
193 {
194 RecordFailure(ctx, pp, attr,
195 "Link '%s' points to '%s' not '%s', but not moving obstructions",
196 destination, linkbuf, to);
197 return PROMISE_RESULT_FAIL;
198 }
199 }
200 else
201 {
202 RecordNoChange(ctx, pp, attr, "Link '%s' points to '%s', promise kept", destination, source);
203 return PROMISE_RESULT_NOOP;
204 }
205 }
206 }
207 #endif /* !__MINGW32__ */
208
209 /*****************************************************************************/
210
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)211 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
212 {
213 char absto[CF_BUFSIZE];
214 char expand[CF_BUFSIZE];
215 char linkto[CF_BUFSIZE];
216
217 if (*source == '.')
218 {
219 strcpy(linkto, destination);
220 ChopLastNode(linkto);
221 AddSlash(linkto);
222 strcat(linkto, source);
223 }
224 else
225 {
226 strcpy(linkto, source);
227 }
228
229 CompressPath(absto, sizeof(absto), linkto);
230
231 expand[0] = '\0';
232
233 if (attr->link.when_no_file == cfa_force)
234 {
235 if (!ExpandLinks(expand, absto, 0)) /* begin at level 1 and beam out at 15 */
236 {
237 RecordFailure(ctx, pp, attr, "Failed to expand absolute link to '%s'", source);
238 PromiseRef(LOG_LEVEL_ERR, pp);
239 return PROMISE_RESULT_FAIL;
240 }
241 else
242 {
243 Log(LOG_LEVEL_DEBUG, "ExpandLinks returned '%s'", expand);
244 }
245 }
246 else
247 {
248 strcpy(expand, absto);
249 }
250
251 CompressPath(linkto, sizeof(linkto), expand);
252
253 return VerifyLink(ctx, destination, linkto, attr, pp);
254 }
255
256 /*****************************************************************************/
257
VerifyRelativeLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)258 PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
259 {
260 char *sp, *commonto, *commonfrom;
261 char buff[CF_BUFSIZE], linkto[CF_BUFSIZE];
262 int levels = 0;
263
264 if (*source == '.')
265 {
266 return VerifyLink(ctx, destination, source, attr, pp);
267 }
268
269 if (!CompressPath(linkto, sizeof(linkto), source))
270 {
271 RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s'", destination, source);
272 return PROMISE_RESULT_INTERRUPTED;
273 }
274
275 commonto = linkto;
276 commonfrom = destination;
277
278 if (strcmp(commonto, commonfrom) == 0)
279 {
280 RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s', can't link file '%s' to itself",
281 destination, source, commonto);
282 return PROMISE_RESULT_INTERRUPTED;
283 }
284
285 while (*commonto == *commonfrom)
286 {
287 commonto++;
288 commonfrom++;
289 }
290
291 while (!((IsAbsoluteFileName(commonto)) && (IsAbsoluteFileName(commonfrom))))
292 {
293 commonto--;
294 commonfrom--;
295 }
296
297 commonto++;
298
299 for (sp = commonfrom; *sp != '\0'; sp++)
300 {
301 if (IsFileSep(*sp))
302 {
303 levels++;
304 }
305 }
306
307 memset(buff, 0, CF_BUFSIZE);
308
309 strcat(buff, ".");
310 strcat(buff, FILE_SEPARATOR_STR);
311
312 while (--levels > 0)
313 {
314 const char add[] = ".." FILE_SEPARATOR_STR;
315
316 if (!PathAppend(buff, sizeof(buff), add, FILE_SEPARATOR))
317 {
318 RecordFailure(ctx, pp, attr,
319 "Internal limit reached in VerifyRelativeLink(),"
320 " path too long: '%s' + '%s'",
321 buff, add);
322 return PROMISE_RESULT_FAIL;
323 }
324 }
325
326 if (!PathAppend(buff, sizeof(buff), commonto, FILE_SEPARATOR))
327 {
328 RecordFailure(ctx, pp, attr,
329 "Internal limit reached in VerifyRelativeLink() end,"
330 " path too long: '%s' + '%s'",
331 buff, commonto);
332 return PROMISE_RESULT_FAIL;
333 }
334
335 return VerifyLink(ctx, destination, buff, attr, pp);
336 }
337
338 /*****************************************************************************/
339
VerifyHardLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)340 PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
341 {
342 char to[CF_BUFSIZE], absto[CF_BUFSIZE];
343 struct stat ssb, dsb;
344
345 memset(to, 0, CF_BUFSIZE);
346
347 if ((!IsAbsoluteFileName(source)) && (*source != '.')) /* links without a directory reference */
348 {
349 snprintf(to, CF_BUFSIZE - 1, ".%c%s", FILE_SEPARATOR, source);
350 }
351 else
352 {
353 strlcpy(to, source, CF_BUFSIZE);
354 }
355
356 if (!IsAbsoluteFileName(to)) /* relative path, must still check if exists */
357 {
358 Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
359 strcpy(absto, AbsLinkPath(destination, to));
360 Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', destination '%s'", absto, destination);
361 }
362 else
363 {
364 strcpy(absto, to);
365 }
366
367 if (stat(absto, &ssb) == -1)
368 {
369 RecordInterruption(ctx, pp, attr, "Source file '%s' doesn't exist", source);
370 return PROMISE_RESULT_INTERRUPTED;
371 }
372
373 if (!S_ISREG(ssb.st_mode))
374 {
375 RecordFailure(ctx, pp, attr,
376 "Source file '%s' is not a regular file, not appropriate to hard-link", to);
377 return PROMISE_RESULT_FAIL;
378 }
379
380 Log(LOG_LEVEL_DEBUG, "Trying to hard link '%s' -> '%s'", destination, to);
381
382 if (stat(destination, &dsb) == -1)
383 {
384 PromiseResult result = PROMISE_RESULT_NOOP;
385 MakeHardLink(ctx, destination, to, attr, pp, &result);
386 return result;
387 }
388
389 /* both files exist, but are they the same file? POSIX says */
390 /* the files could be on different devices, but unix doesn't */
391 /* allow this behaviour so the tests below are theoretical... */
392
393 if ((dsb.st_ino != ssb.st_ino) && (dsb.st_dev != ssb.st_dev))
394 {
395 Log(LOG_LEVEL_VERBOSE,
396 "If this is POSIX, unable to determine if %s is hard link is correct"
397 " since it points to a different filesystem",
398 destination);
399
400 if ((dsb.st_mode == ssb.st_mode) && (dsb.st_size == ssb.st_size))
401 {
402 RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' on different device appears okay",
403 destination, to);
404 return PROMISE_RESULT_NOOP;
405 }
406 }
407
408 if ((dsb.st_ino == ssb.st_ino) && (dsb.st_dev == ssb.st_dev))
409 {
410 RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' exists and is okay",
411 destination, to);
412 return PROMISE_RESULT_NOOP;
413 }
414
415 Log(LOG_LEVEL_INFO, "'%s' does not appear to be a hard link to '%s'", destination, to);
416
417 if (!EnforcePromise(attr->transaction.action))
418 {
419 RecordWarning(ctx, pp, attr, "Hard link '%s' -> '%s' should be created", destination, to);
420 return PROMISE_RESULT_WARN;
421 }
422
423 PromiseResult result = PROMISE_RESULT_NOOP;
424 if (!MoveObstruction(ctx, destination, attr, pp, &result))
425 {
426 RecordFailure(ctx, pp, attr,
427 "Unable to create hard link '%s' -> '%s', failed to move obstruction",
428 destination, to);
429 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
430 return result;
431 }
432
433 MakeHardLink(ctx, destination, to, attr, pp, &result);
434 return result;
435 }
436
437 /*****************************************************************************/
438 /* Level */
439 /*****************************************************************************/
440
441 #ifdef __MINGW32__
442
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)443 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result)
444 {
445 RecordFailure(ctx, pp, attr, "Cannot remove dead link '%s' (Windows does not support symbolic links)", name);
446 PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
447 return false;
448 }
449
450 #else /* !__MINGW32__ */
451
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)452 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp,
453 PromiseResult *result)
454 {
455 char linkbuf[CF_BUFSIZE], tmp[CF_BUFSIZE];
456 char linkpath[CF_BUFSIZE], *sp;
457 struct stat statbuf;
458
459 memset(linkbuf, 0, CF_BUFSIZE);
460 memset(linkpath, 0, CF_BUFSIZE);
461
462 if (readlink(name, linkbuf, CF_BUFSIZE - 1) == -1)
463 {
464 Log(LOG_LEVEL_VERBOSE, "Can't read link '%s' while checking for deadlinks", name);
465 return true; /* ignore */
466 }
467
468 if (!IsAbsoluteFileName(linkbuf))
469 {
470 strcpy(linkpath, name); /* Get path to link */
471
472 for (sp = linkpath + strlen(linkpath); (*sp != FILE_SEPARATOR) && (sp >= linkpath); sp--)
473 {
474 *sp = '\0';
475 }
476 }
477
478 strcat(linkpath, linkbuf);
479 CompressPath(tmp, sizeof(tmp), linkpath);
480
481 if (stat(tmp, &statbuf) == -1) /* link points nowhere */
482 {
483 if ((attr->link.when_no_file == cfa_delete) || (attr->recursion.rmdeadlinks))
484 {
485 Log(LOG_LEVEL_VERBOSE, "'%s' is a link which points to '%s', but that file doesn't seem to exist", name,
486 linkbuf);
487
488 if (DONTDO)
489 {
490 RecordWarning(ctx, pp, attr, "Dead link '%s' should be removed", name);
491 *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
492 return true;
493 }
494 else
495 {
496 unlink(name); /* May not work on a client-mounted system ! */
497 RecordChange(ctx, pp, attr, "Removing ghost '%s', reference to something that is not there",
498 name);
499 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
500 return true;
501 }
502 }
503 }
504
505 return false;
506 }
507 #endif /* !__MINGW32__ */
508
509 /*****************************************************************************/
510
511 #if !defined(__MINGW32__)
MakeLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)512 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
513 PromiseResult *result)
514 {
515 if (DONTDO || (attr->transaction.action == cfa_warn))
516 {
517 RecordWarning(ctx, pp, attr, "Should link files '%s' -> '%s'", from, to);
518 *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
519 return false;
520 }
521 else
522 {
523 if (symlink(to, from) == -1)
524 {
525 RecordFailure(ctx, pp, attr, "Couldn't link '%s' to '%s'. (symlink: %s)",
526 to, from, GetErrorStr());
527 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
528 return false;
529 }
530 else
531 {
532 RecordChange(ctx, pp, attr, "Linked files '%s' -> '%s'", from, to);
533 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
534 return true;
535 }
536 }
537 }
538 #endif /* !__MINGW32__ */
539
540 /*****************************************************************************/
541
542 #ifdef __MINGW32__
543
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)544 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
545 PromiseResult *result)
546 { // TODO: Implement ?
547 RecordFailure(ctx, pp, attr,
548 "Couldn't hard link '%s' to '%s' (Hard links are not yet supported on Windows)",
549 to, from);
550 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
551 return false;
552 }
553
554 #else /* !__MINGW32__ */
555
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)556 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
557 PromiseResult *result)
558 {
559 if (DONTDO)
560 {
561 RecordWarning(ctx, pp, attr, "Should hard link files '%s' -> '%s'", from, to);
562 *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
563 return false;
564 }
565 else
566 {
567 if (link(to, from) == -1)
568 {
569 RecordFailure(ctx, pp, attr, "Failed to hard link '%s' to '%s'. (link: %s)",
570 to, from, GetErrorStr());
571 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
572 return false;
573 }
574 else
575 {
576 RecordChange(ctx, pp, attr, "Hard linked file '%s' -> '%s'", from, to);
577 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
578 return true;
579 }
580 }
581 }
582
583 #endif /* !__MINGW32__ */
584
585 /*********************************************************************/
586
587 /* Expand a path contaning symbolic links, up to 4 levels */
588 /* of symbolic links and then beam out in a hurry ! */
589
590 #ifdef __MINGW32__
591
ExpandLinks(char * dest,const char * from,int level)592 bool ExpandLinks(char *dest, const char *from, int level)
593 {
594 Log(LOG_LEVEL_ERR, "Windows does not support symbolic links (at ExpandLinks(%s,%s))", dest, from);
595 return false;
596 }
597
598 #else /* !__MINGW32__ */
599
ExpandLinks(char * dest,const char * from,int level)600 bool ExpandLinks(char *dest, const char *from, int level)
601 {
602 char buff[CF_BUFSIZE];
603 char node[CF_MAXLINKSIZE];
604 struct stat statbuf;
605 int lastnode = false;
606
607 memset(dest, 0, CF_BUFSIZE);
608
609 if (level >= CF_MAXLINKLEVEL)
610 {
611 Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path");
612 return false;
613 }
614
615 const char *sp = from;
616
617 while (*sp != '\0')
618 {
619 if (*sp == FILE_SEPARATOR)
620 {
621 sp++;
622 continue;
623 }
624
625 sscanf(sp, "%[^/]", node);
626 sp += strlen(node);
627
628 if (*sp == '\0')
629 {
630 lastnode = true;
631 }
632
633 if (strcmp(node, ".") == 0)
634 {
635 continue;
636 }
637
638 if (strcmp(node, "..") == 0)
639 {
640 strcat(dest, "/..");
641 continue;
642 }
643 else
644 {
645 strcat(dest, "/");
646 }
647
648 strcat(dest, node);
649
650 if (lstat(dest, &statbuf) == -1) /* File doesn't exist so we can stop here */
651 {
652 Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr());
653 return false;
654 }
655
656 if (S_ISLNK(statbuf.st_mode))
657 {
658 memset(buff, 0, CF_BUFSIZE);
659
660 if (readlink(dest, buff, CF_BUFSIZE - 1) == -1)
661 {
662 Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr());
663 return false;
664 }
665 else
666 {
667 if (buff[0] == '.')
668 {
669 ChopLastNode(dest);
670 AddSlash(dest);
671
672 /* TODO pass and use parameter dest_size. */
673 size_t ret = strlcat(dest, buff, CF_BUFSIZE);
674 if (ret >= CF_BUFSIZE)
675 {
676 Log(LOG_LEVEL_ERR,
677 "Internal limit reached in ExpandLinks(),"
678 " path too long: '%s' + '%s'",
679 dest, buff);
680 return false;
681 }
682 }
683 else if (IsAbsoluteFileName(buff))
684 {
685 strcpy(dest, buff);
686 DeleteSlash(dest);
687
688 if (strcmp(dest, from) == 0)
689 {
690 Log(LOG_LEVEL_DEBUG, "No links to be expanded");
691 return true;
692 }
693
694 if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1)))
695 {
696 return false;
697 }
698 }
699 else
700 {
701 ChopLastNode(dest);
702 AddSlash(dest);
703
704 /* TODO use param dest_size. */
705 size_t ret = strlcat(dest, buff, CF_BUFSIZE);
706 if (ret >= CF_BUFSIZE)
707 {
708 Log(LOG_LEVEL_ERR,
709 "Internal limit reached in ExpandLinks end,"
710 " path too long: '%s' + '%s'", dest, buff);
711 return false;
712 }
713
714 DeleteSlash(dest);
715
716 if (strcmp(dest, from) == 0)
717 {
718 Log(LOG_LEVEL_DEBUG, "No links to be expanded");
719 return true;
720 }
721
722 memset(buff, 0, CF_BUFSIZE);
723
724 if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1)))
725 {
726 return false;
727 }
728 }
729 }
730 }
731 }
732
733 return true;
734 }
735 #endif /* !__MINGW32__ */
736
737 /*********************************************************************/
738
AbsLinkPath(const char * from,const char * relto)739 static char *AbsLinkPath(const char *from, const char *relto)
740 /* Take an abolute source and a relative destination object
741 and find the absolute name of the to object */
742 {
743 int pop = 1;
744 static char destination[CF_BUFSIZE]; /* GLOBAL_R, no need to initialize */
745
746 if (IsAbsoluteFileName(relto))
747 {
748 ProgrammingError("Call to AbsLInkPath with absolute pathname");
749 }
750
751 strcpy(destination, from); /* reuse to save stack space */
752
753 const char *sp = NULL;
754 for (sp = relto; *sp != '\0'; sp++)
755 {
756 if (strncmp(sp, "../", 3) == 0)
757 {
758 pop++;
759 sp += 2;
760 continue;
761 }
762
763 if (strncmp(sp, "./", 2) == 0)
764 {
765 sp += 1;
766 continue;
767 }
768
769 break; /* real link */
770 }
771
772 while (pop > 0)
773 {
774 ChopLastNode(destination);
775 pop--;
776 }
777
778 if (strlen(destination) == 0)
779 {
780 strcpy(destination, "/");
781 }
782 else
783 {
784 AddSlash(destination);
785 }
786
787 strcat(destination, sp);
788 Log(LOG_LEVEL_DEBUG, "Reconstructed absolute linkname '%s'", destination);
789 return destination;
790 }
791