@@ -161,14 +161,44 @@ pub fn mount_share_sync(
161161 let cf_user = username. map ( CFString :: new) ;
162162 let cf_pass = password. map ( CFString :: new) ;
163163
164+ // Check if the default mount path is already taken by a different server.
165+ // If so, pick a disambiguated path (public-1, public-2, ...) like Finder does.
166+ let explicit_mount_path = disambiguated_mount_path ( server, share, port) ;
167+
168+ // When disambiguating, force a new SMB session so macOS doesn't reuse
169+ // the existing session to the same hostname (different port = different server).
170+ let open_options = if explicit_mount_path. is_some ( ) {
171+ unsafe {
172+ let dict = core_foundation:: dictionary:: CFDictionaryCreateMutable (
173+ ptr:: null ( ) ,
174+ 1 ,
175+ & core_foundation:: dictionary:: kCFTypeDictionaryKeyCallBacks,
176+ & core_foundation:: dictionary:: kCFTypeDictionaryValueCallBacks,
177+ ) ;
178+ let key = CFString :: new ( "ForceNewSession" ) ;
179+ let value = core_foundation:: boolean:: kCFBooleanTrue;
180+ core_foundation:: dictionary:: CFDictionarySetValue (
181+ dict,
182+ key. as_concrete_TypeRef ( ) as * const c_void ,
183+ value as * const c_void ,
184+ ) ;
185+ dict as * const c_void
186+ }
187+ } else {
188+ ptr:: null ( )
189+ } ;
190+
164191 // Prepare output array for mount points
165192 let mut mountpoints: * const c_void = ptr:: null ( ) ;
166193
167- // Call NetFSMountURLSync
194+ // Call NetFSMountURLSync. Mount path is NULL even when disambiguating —
195+ // NetFS auto-creates the mount point in /Volumes/ (we can't mkdir there).
196+ // With ForceNewSession, NetFS treats this as a separate server and picks
197+ // a disambiguated name (public-1, public-2, etc.) automatically.
168198 let result = unsafe {
169199 NetFSMountURLSync (
170200 cf_url. as_concrete_TypeRef ( ) as * const c_void ,
171- ptr:: null ( ) , // NULL for auto mount path
201+ ptr:: null ( ) , // Let NetFS choose/create the mount point
172202 cf_user
173203 . as_ref ( )
174204 . map ( |s| s. as_concrete_TypeRef ( ) as * const c_void )
@@ -177,12 +207,17 @@ pub fn mount_share_sync(
177207 . as_ref ( )
178208 . map ( |s| s. as_concrete_TypeRef ( ) as * const c_void )
179209 . unwrap_or ( ptr:: null ( ) ) ,
180- ptr :: null ( ) , // No special open options
210+ open_options ,
181211 ptr:: null ( ) , // No special mount options
182212 & mut mountpoints,
183213 )
184214 } ;
185215
216+ // Release open options dictionary if we created one
217+ if !open_options. is_null ( ) {
218+ unsafe { core_foundation:: base:: CFRelease ( open_options) } ;
219+ }
220+
186221 // Check result
187222 if result != 0 && result != EEXIST {
188223 return Err ( error_from_code ( result, share, server) ) ;
@@ -194,7 +229,10 @@ pub fn mount_share_sync(
194229 // EEXIST (17), macOS may return the actual path (which can be disambiguated,
195230 // for example `/Volumes/public-1` when `/Volumes/public` is already taken by
196231 // a different server). Fall back to scanning /Volumes/ for the mount.
197- let mount_path = extract_mount_path ( mountpoints)
232+ // Prefer: explicit path we chose → NetFS output → /Volumes/ scan → hardcoded fallback.
233+ // The explicit path is most reliable because we already validated it.
234+ let mount_path = explicit_mount_path
235+ . or_else ( || extract_mount_path ( mountpoints) )
198236 . or_else ( || find_mount_path_for_share ( server, share) )
199237 . unwrap_or_else ( || format ! ( "/Volumes/{}" , share) ) ;
200238
@@ -260,6 +298,52 @@ fn extract_mount_path(mountpoints: *const c_void) -> Option<String> {
260298 }
261299}
262300
301+ /// Returns a disambiguated mount path if `/Volumes/{share}` is already taken by a
302+ /// different server. Returns `None` if the default path is available or already
303+ /// belongs to this server (EEXIST case).
304+ ///
305+ /// Follows Finder's convention: `public-1`, `public-2`, etc.
306+ fn disambiguated_mount_path ( server : & str , share : & str , port : u16 ) -> Option < String > {
307+ use crate :: volumes:: get_smb_mount_info;
308+
309+ let default_path = format ! ( "/Volumes/{}" , share) ;
310+ if !std:: path:: Path :: new ( & default_path) . exists ( ) {
311+ return None ; // Default path is free
312+ }
313+
314+ // Check if the existing mount is from the same server+port
315+ if let Some ( info) = get_smb_mount_info ( & default_path)
316+ && info. server . to_lowercase ( ) == server. to_lowercase ( )
317+ && info. share == share
318+ && info. port == port
319+ {
320+ return None ; // Same server — let NetFS handle EEXIST
321+ }
322+
323+ // Collision: find the next available suffix
324+ for n in 1 ..100 {
325+ let candidate = format ! ( "/Volumes/{}-{}" , share, n) ;
326+ if !std:: path:: Path :: new ( & candidate) . exists ( ) {
327+ log:: info!(
328+ "Mount path /Volumes/{} taken by another server, using {}" ,
329+ share,
330+ candidate
331+ ) ;
332+ return Some ( candidate) ;
333+ }
334+ // If this suffixed path exists and belongs to this server, reuse it
335+ if let Some ( info) = get_smb_mount_info ( & candidate)
336+ && info. server . to_lowercase ( ) == server. to_lowercase ( )
337+ && info. share == share
338+ && info. port == port
339+ {
340+ return Some ( candidate) ; // Already mounted here
341+ }
342+ }
343+
344+ None // Give up after 100 attempts, let NetFS handle it
345+ }
346+
263347/// Finds the mount path for a server+share by scanning `/Volumes/` with `statfs`.
264348///
265349/// Handles disambiguated paths: if `server` has share `public` but `/Volumes/public`
0 commit comments