@@ -845,6 +845,70 @@ func isValidSymlink(symlinkPath, targetPath string) bool {
845
845
return true
846
846
}
847
847
848
+ // isGollamaSymlink checks if a symlink was created by gollama by validating:
849
+ // 1. The symlink target points to Ollama's blob storage
850
+ // 2. The directory structure follows gollama's pattern (author/model/file.gguf)
851
+ func isGollamaSymlink (symlinkPath , lmStudioModelsDir string ) bool {
852
+ // Get the symlink target
853
+ target , err := os .Readlink (symlinkPath )
854
+ if err != nil {
855
+ return false
856
+ }
857
+
858
+ // Check if target points to Ollama blob storage
859
+ // Normalise path separators for cross-platform compatibility
860
+ normalisedTarget := filepath .ToSlash (target )
861
+ isOllamaBlob := strings .Contains (normalisedTarget , "/.ollama/models/blobs/" )
862
+
863
+ if ! isOllamaBlob {
864
+ return false
865
+ }
866
+
867
+ // Check if it follows gollama's directory structure: author/model/file.gguf
868
+ relPath , err := filepath .Rel (lmStudioModelsDir , symlinkPath )
869
+ if err != nil {
870
+ return false
871
+ }
872
+
873
+ pathParts := strings .Split (filepath .ToSlash (relPath ), "/" )
874
+ if len (pathParts ) != 3 {
875
+ return false
876
+ }
877
+
878
+ // Check if the filename follows gollama's pattern
879
+ filename := pathParts [2 ]
880
+ modelDir := pathParts [1 ]
881
+
882
+ // Gollama creates files like: modelname.gguf or mmproj-modelname.gguf
883
+ return strings .HasSuffix (filename , ".gguf" ) &&
884
+ (strings .HasPrefix (filename , modelDir ) || strings .HasPrefix (filename , "mmproj-" + modelDir ))
885
+ }
886
+
887
+ // isBrokenSymlink checks if a symlink is broken (target doesn't exist)
888
+ func isBrokenSymlink (symlinkPath string ) bool {
889
+ target , err := os .Readlink (symlinkPath )
890
+ if err != nil {
891
+ // Only treat as broken if the symlink itself doesn't exist
892
+ if os .IsNotExist (err ) {
893
+ return true
894
+ }
895
+ // For permission errors or other issues, don't consider it broken
896
+ logging .DebugLogger .Printf ("Error reading symlink %s: %v (not considering broken)\n " , symlinkPath , err )
897
+ return false
898
+ }
899
+
900
+ // Check if target exists
901
+ _ , err = os .Stat (target )
902
+ if err == nil {
903
+ return false // Target exists, symlink is not broken
904
+ }
905
+ if os .IsNotExist (err ) {
906
+ return true // Target does not exist, symlink is broken
907
+ }
908
+ // For other stat errors (permission, etc.), don't consider broken
909
+ return false
910
+ }
911
+
848
912
func cleanBrokenSymlinks (lmStudioModelsDir string ) {
849
913
err := filepath .Walk (lmStudioModelsDir , func (path string , info os.FileInfo , err error ) error {
850
914
if err != nil {
@@ -863,12 +927,10 @@ func cleanBrokenSymlinks(lmStudioModelsDir string) {
863
927
}
864
928
}
865
929
} else if info .Mode ()& os .ModeSymlink != 0 {
866
- linkPath , err := os .Readlink (path )
867
- if err != nil {
868
- return err
869
- }
870
- if ! isValidSymlink (path , linkPath ) {
871
- logging .InfoLogger .Printf ("Removing invalid symlink: %s\n " , path )
930
+ // Only remove truly broken symlinks (where target doesn't exist)
931
+ // Don't remove valid symlinks from other tools
932
+ if isBrokenSymlink (path ) {
933
+ logging .InfoLogger .Printf ("Removing broken symlink: %s\n " , path )
872
934
err = os .Remove (path )
873
935
if err != nil {
874
936
return err
@@ -992,10 +1054,28 @@ func cleanupSymlinkedModels(lmStudioModelsDir string) {
992
1054
hasEmptyDir = true
993
1055
}
994
1056
} else if info .Mode ()& os .ModeSymlink != 0 {
995
- logging .InfoLogger .Printf ("Removing symlinked model: %s\n " , path )
996
- err = os .Remove (path )
997
- if err != nil {
998
- return err
1057
+ // Only remove symlinks that are either:
1058
+ // 1. Created by gollama, or
1059
+ // 2. Broken (safe to remove)
1060
+ shouldRemove := false
1061
+ reason := ""
1062
+
1063
+ if isBrokenSymlink (path ) {
1064
+ shouldRemove = true
1065
+ reason = "broken symlink"
1066
+ } else if isGollamaSymlink (path , lmStudioModelsDir ) {
1067
+ shouldRemove = true
1068
+ reason = "gollama-created symlink"
1069
+ }
1070
+
1071
+ if shouldRemove {
1072
+ logging .InfoLogger .Printf ("Removing %s: %s\n " , reason , path )
1073
+ err = os .Remove (path )
1074
+ if err != nil {
1075
+ return err
1076
+ }
1077
+ } else {
1078
+ logging .DebugLogger .Printf ("Preserving non-gollama symlink: %s\n " , path )
999
1079
}
1000
1080
}
1001
1081
return nil
0 commit comments