package activator import ( "errors" "os" "github.com/carlosframework/platform/internal/registry" ) // canonlink.go keeps a route's canonical socket path (rt.Socket) pointing at // its current generation, for processes outside the edge and the agent that // dial it, such as members of the carlos-sockets group (#103; zero-downtime // spec ยง7.1, rev 15). Unix connect() follows the symlink. Internal dials // never use it: they go through AppSocket or a handle, and unixdial.Checked // would refuse the link anyway (the listener's bound path is the generation // path, not the dialled one). // // Plan 1 creates the link when the first generation becomes current and // removes it when the entry stops. Plan 3 calls swapCanonicalLink at every // switch. // linksCanonical reports whether g's process is reached through a symlink at // rt's canonical path: only when the generation bound a path of its own. A // generation on the canonical socket itself (generations off, a unit tenant, // a foreign-layout row) is never linked: the link would replace the real // socket. func linksCanonical(rt registry.Route, g Gen) bool { return rt.Socket != "" && g.Socket != "" && g.Socket != rt.Socket } // swapCanonicalLink points canonical at target atomically: a temporary link // beside it, then rename over it. A dialler sees the old target or the new // one, never no path. Whatever was at canonical (an earlier link, or a stale // socket file from before generations) is replaced. func swapCanonicalLink(canonical, target string) error { tmp := canonical + ".lnk" if err := os.Remove(tmp); err != nil && !errors.Is(err, os.ErrNotExist) { return err } if err := os.Symlink(target, tmp); err != nil { return err } if err := os.Rename(tmp, canonical); err != nil { _ = os.Remove(tmp) return err } return nil } // removeCanonicalLink removes canonical only while it is a symlink to // target: a link a later generation (or a later entry) has already moved, // and a real socket, are left alone. func removeCanonicalLink(canonical, target string) error { got, err := os.Readlink(canonical) if err != nil || got != target { return nil } if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil } // clearLeftoverCanonicalLink removes a symlink at canonical, whatever it // names, and nothing else. An agent with generations on that died without // stopping its entries leaves one; an app told to bind canonical with the // switch off would otherwise fail with "address in use". A canonical path // that is not a symlink (every box that never ran generations) is not // touched. func clearLeftoverCanonicalLink(canonical string) error { fi, err := os.Lstat(canonical) if err != nil || fi.Mode()&os.ModeSymlink == 0 { return nil } if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil }