checkConflict refuses the install when a CAM-owned symlink for a *different* instruction already occupies the target. When the file belongs to the same instruction or is a user file, it backs it up with a timestamp so the install can proceed.
(ctx context.Context, target string, id int64)
| 133 | // instruction or is a user file, it backs it up with a timestamp so the |
| 134 | // install can proceed. |
| 135 | func (s *Store) checkConflict(ctx context.Context, target string, id int64) error { |
| 136 | info, err := os.Lstat(target) |
| 137 | if err != nil { |
| 138 | return nil // nothing there — clear to install |
| 139 | } |
| 140 | // Hard conflict: another CAM instruction owns this symlink. |
| 141 | if info.Mode()&os.ModeSymlink != 0 { |
| 142 | if resolved, rerr := os.Readlink(target); rerr == nil && isUnderManagedDir(resolved) { |
| 143 | if other, ok := s.instructionForFile(ctx, resolved); ok && other.ID != id { |
| 144 | return &ConflictError{Message: fmt.Sprintf("%s is currently installed at %s; uninstall it first", other.Name, target)} |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | // File exists but is safe to replace — back it up first. |
| 149 | if err := backupExistingFile(target); err != nil { |
| 150 | return &ConflictError{Message: fmt.Sprintf("failed to backup %s: %v", target, err)} |
| 151 | } |
| 152 | return nil |
| 153 | } |
| 154 | |
| 155 | // backupExistingFile renames target to target.backup.<YYYYMMDD-HHMMSS>. |
| 156 | func backupExistingFile(target string) error { |
no test coverage detected