| 1 | package activator |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "os" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/registry" |
| 8 | ) |
| 9 | |
| 10 | // canonlink.go keeps a route's canonical socket path (rt.Socket) pointing at |
| 11 | // its current generation, for processes outside the edge and the agent that |
| 12 | // dial it, such as members of the carlos-sockets group (#103; zero-downtime |
| 13 | // spec §7.1, rev 15). Unix connect() follows the symlink. Internal dials |
| 14 | // never use it: they go through AppSocket or a handle, and unixdial.Checked |
| 15 | // would refuse the link anyway (the listener's bound path is the generation |
| 16 | // path, not the dialled one). |
| 17 | // |
| 18 | // Plan 1 creates the link when the first generation becomes current and |
| 19 | // removes it when the entry stops. Plan 3 calls swapCanonicalLink at every |
| 20 | // switch. |
| 21 | |
| 22 | // linksCanonical reports whether g's process is reached through a symlink at |
| 23 | // rt's canonical path: only when the generation bound a path of its own. A |
| 24 | // generation on the canonical socket itself (generations off, a unit tenant, |
| 25 | // a foreign-layout row) is never linked: the link would replace the real |
| 26 | // socket. |
| 27 | func linksCanonical(rt registry.Route, g Gen) bool { |
| 28 | return rt.Socket != "" && g.Socket != "" && g.Socket != rt.Socket |
| 29 | } |
| 30 | |
| 31 | // swapCanonicalLink points canonical at target atomically: a temporary link |
| 32 | // beside it, then rename over it. A dialler sees the old target or the new |
| 33 | // one, never no path. Whatever was at canonical (an earlier link, or a stale |
| 34 | // socket file from before generations) is replaced. |
| 35 | func swapCanonicalLink(canonical, target string) error { |
| 36 | tmp := canonical + ".lnk" |
| 37 | if err := os.Remove(tmp); err != nil && !errors.Is(err, os.ErrNotExist) { |
| 38 | return err |
| 39 | } |
| 40 | if err := os.Symlink(target, tmp); err != nil { |
| 41 | return err |
| 42 | } |
| 43 | if err := os.Rename(tmp, canonical); err != nil { |
| 44 | _ = os.Remove(tmp) |
| 45 | return err |
| 46 | } |
| 47 | return nil |
| 48 | } |
| 49 | |
| 50 | // removeCanonicalLink removes canonical only while it is a symlink to |
| 51 | // target: a link a later generation (or a later entry) has already moved, |
| 52 | // and a real socket, are left alone. |
| 53 | func removeCanonicalLink(canonical, target string) error { |
| 54 | got, err := os.Readlink(canonical) |
| 55 | if err != nil || got != target { |
| 56 | return nil |
| 57 | } |
| 58 | if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) { |
| 59 | return err |
| 60 | } |
| 61 | return nil |
| 62 | } |
| 63 | |
| 64 | // clearLeftoverCanonicalLink removes a symlink at canonical, whatever it |
| 65 | // names, and nothing else. An agent with generations on that died without |
| 66 | // stopping its entries leaves one; an app told to bind canonical with the |
| 67 | // switch off would otherwise fail with "address in use". A canonical path |
| 68 | // that is not a symlink (every box that never ran generations) is not |
| 69 | // touched. |
| 70 | func clearLeftoverCanonicalLink(canonical string) error { |
| 71 | fi, err := os.Lstat(canonical) |
| 72 | if err != nil || fi.Mode()&os.ModeSymlink == 0 { |
| 73 | return nil |
| 74 | } |
| 75 | if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) { |
| 76 | return err |
| 77 | } |
| 78 | return nil |
| 79 | } |
| 80 | |