@@ -207,9 +207,11 @@ func (m *Manager) CreateClone(branchName, cloneName, snapshotID string, revision
207207 }
208208
209209 cloneMountLocation := m .config .Pool .CloneLocation (branchName , cloneName , revision )
210+ cloneDataDir := m .config .Pool .ClonePath (branchName , cloneName , revision )
210211
211- cmd := fmt .Sprintf ("zfs clone -p -o mountpoint=%s %s %s && chown -R %s %s" ,
212- cloneMountLocation , snapshotID , cloneMountName , m .config .OSUsername , cloneMountLocation )
212+ cmd := fmt .Sprintf ("zfs clone -p -o mountpoint=%s %s %s && %s" ,
213+ cloneMountLocation , snapshotID , cloneMountName ,
214+ chownCloneCommand (cloneDataDir , cloneMountLocation , m .config .OSUsername ))
213215
214216 log .Dbg (cmd )
215217
@@ -221,6 +223,49 @@ func (m *Manager) CreateClone(branchName, cloneName, snapshotID string, revision
221223 return nil
222224}
223225
226+ // EnsureDataOwnership makes dataDir and its contents owned by the engine OS user, walking the tree
227+ // only when it is not already owned by that user. Running it once while preparing a snapshot lets
228+ // every clone created from that snapshot inherit correct ownership and skip the recursive walk.
229+ func (m * Manager ) EnsureDataOwnership (dataDir string ) error {
230+ cmd := ensureOwnershipCommand (dataDir , m .config .OSUsername )
231+
232+ log .Dbg (cmd )
233+
234+ out , err := m .runner .Run (cmd )
235+ if err != nil {
236+ return errors .Wrapf (err , "failed to ensure data ownership. Out: %v" , out )
237+ }
238+
239+ return nil
240+ }
241+
242+ // ownedByUserCondition returns a shell test that succeeds when path is owned by osUsername. The
243+ // compared uid derives from osUsername, so the probe never disagrees with the chown that acts on it,
244+ // and it runs through the same runner as the operation it guards. A failed uid lookup falls back to a
245+ // non-numeric sentinel so that an unresolvable user never matches a missing path (empty == empty).
246+ func ownedByUserCondition (path , osUsername string ) string {
247+ return fmt .Sprintf ("[ \" $(stat -c '%%u' '%s' 2>/dev/null)\" = \" $(id -u '%s' 2>/dev/null || echo nouid)\" ]" ,
248+ path , osUsername )
249+ }
250+
251+ // chownCloneCommand builds the ownership step for a freshly created clone. A ZFS clone inherits file
252+ // ownership from its origin snapshot, so when the clone data directory is already owned by the engine
253+ // OS user the expensive recursive walk over the whole data directory is skipped and only the mount
254+ // directory is adjusted; otherwise ownership is applied recursively. The data directory owner is a
255+ // sound probe because every ownership change in the pipeline is applied recursively (uniform owner)
256+ // and PostgreSQL gates startup on the data directory owner.
257+ func chownCloneCommand (dataDir , mountLocation , osUsername string ) string {
258+ return fmt .Sprintf ("if %s; then chown '%s' '%s'; else chown -R '%s' '%s'; fi" ,
259+ ownedByUserCondition (dataDir , osUsername ), osUsername , mountLocation , osUsername , mountLocation )
260+ }
261+
262+ // ensureOwnershipCommand builds a command that recursively assigns ownership of dataDir to osUsername
263+ // only when it is not already owned by that user.
264+ func ensureOwnershipCommand (dataDir , osUsername string ) string {
265+ return fmt .Sprintf ("if ! %s; then chown -R '%s' '%s'; fi" ,
266+ ownedByUserCondition (dataDir , osUsername ), osUsername , dataDir )
267+ }
268+
224269// DestroyClone destroys a ZFS clone.
225270func (m * Manager ) DestroyClone (branchName , cloneName string , revision int ) error {
226271 cloneMountName := m .config .Pool .CloneName (branchName , cloneName , revision )
0 commit comments