github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/wc.go (about) 1 // 2 // nazuna :: wc.go 3 // 4 // Copyright (c) 2013-2021 Akinori Hattori <hattya@gmail.com> 5 // 6 // SPDX-License-Identifier: MIT 7 // 8 9 package nazuna 10 11 import ( 12 "errors" 13 "fmt" 14 "os" 15 "path/filepath" 16 "strings" 17 ) 18 19 var ( 20 ErrLink = errors.New("path is link") 21 ErrNotLink = errors.New("path is not link") 22 ) 23 24 type WC struct { 25 State State 26 27 ui UI 28 repo *Repository 29 } 30 31 func openWC(repo *Repository) (*WC, error) { 32 wc := &WC{ 33 ui: repo.ui, 34 repo: repo, 35 } 36 if err := unmarshal(repo, filepath.Join(repo.nzndir, "state.json"), &wc.State); err != nil { 37 return nil, err 38 } 39 if wc.State.WC == nil { 40 wc.State.WC = []*Entry{} 41 } 42 return wc, nil 43 } 44 45 func (wc *WC) Flush() error { 46 return marshal(wc.repo, filepath.Join(wc.repo.nzndir, "state.json"), &wc.State) 47 } 48 49 func (wc *WC) PathFor(path string) string { 50 return filepath.Join(wc.repo.root, path) 51 } 52 53 func (wc *WC) Rel(base rune, path string) (string, error) { 54 if strings.HasPrefix(path, "$") { 55 return filepath.ToSlash(path), nil 56 } 57 58 var abs string 59 var err error 60 switch base { 61 case '/': 62 if filepath.IsAbs(path) { 63 abs = path 64 } else { 65 abs = filepath.Join(wc.repo.root, path) 66 } 67 case '.': 68 if abs, err = filepath.Abs(path); err != nil { 69 return "", err 70 } 71 default: 72 return "", fmt.Errorf("unknown base '%c'", base) 73 } 74 rel, err := filepath.Rel(wc.repo.root, abs) 75 if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { 76 return "", fmt.Errorf("'%v' is not under root", path) 77 } 78 return filepath.ToSlash(rel), nil 79 } 80 81 func (wc *WC) Exists(path string) bool { 82 _, err := os.Lstat(wc.PathFor(path)) 83 return err == nil 84 } 85 86 func (wc *WC) IsLink(path string) bool { 87 return IsLink(wc.PathFor(path)) 88 } 89 90 func (wc *WC) LinksTo(path, origin string) bool { 91 return LinksTo(wc.PathFor(path), origin) 92 } 93 94 func (wc *WC) Link(src, dst string) error { 95 dst = wc.PathFor(dst) 96 for p := filepath.Dir(dst); p != wc.repo.root; p = filepath.Dir(p) { 97 if IsLink(p) { 98 return &os.PathError{ 99 Op: "link", 100 Path: p, 101 Err: ErrLink, 102 } 103 } 104 } 105 dir := filepath.Dir(dst) 106 if _, err := os.Lstat(dir); err != nil { 107 if err := os.MkdirAll(dir, 0o777); err != nil { 108 return err 109 } 110 } 111 return CreateLink(src, dst) 112 } 113 114 func (wc *WC) Unlink(path string) error { 115 path = wc.PathFor(path) 116 if err := Unlink(path); err != nil { 117 return err 118 } 119 for p := filepath.Dir(path); p != wc.repo.root; p = filepath.Dir(p) { 120 if IsLink(p) || !IsEmptyDir(p) { 121 break 122 } 123 if err := os.Remove(p); err != nil { 124 return err 125 } 126 } 127 return nil 128 } 129 130 func (wc *WC) SelectLayer(name string) error { 131 l, err := wc.repo.LayerOf(name) 132 switch { 133 case err != nil: 134 return err 135 case len(l.Layers) != 0: 136 return fmt.Errorf("layer '%v' is abstract", name) 137 case l.abst == nil: 138 return fmt.Errorf("layer '%v' is not abstract", name) 139 } 140 for k, v := range wc.State.Layers { 141 if k == l.abst.Name { 142 if v == l.Name { 143 return fmt.Errorf("layer '%v' is already '%v'", k, v) 144 } 145 wc.State.Layers[k] = l.Name 146 return nil 147 } 148 } 149 if wc.State.Layers == nil { 150 wc.State.Layers = make(map[string]string) 151 } 152 wc.State.Layers[l.abst.Name] = l.Name 153 return nil 154 } 155 156 func (wc *WC) LayerFor(name string) (*Layer, error) { 157 for k, v := range wc.State.Layers { 158 if name == k { 159 return wc.repo.LayerOf(k + "/" + v) 160 } 161 } 162 return nil, ResolveError{Name: name} 163 } 164 165 func (wc *WC) Layers() ([]*Layer, error) { 166 list := make([]*Layer, len(wc.repo.Layers)) 167 for i, l := range wc.repo.Layers { 168 if len(l.Layers) != 0 { 169 wl, err := wc.LayerFor(l.Name) 170 if err != nil { 171 list := make([]string, len(l.Layers)) 172 for i, ll := range l.Layers { 173 list[i] = ll.Name 174 } 175 return nil, ResolveError{ 176 Name: l.Name, 177 List: list, 178 } 179 } 180 l = wl 181 } 182 list[i] = l 183 } 184 return list, nil 185 } 186 187 func (wc *WC) MergeLayers() ([]*Entry, error) { 188 b := wcBuilder{ 189 ui: wc.ui, 190 wc: wc, 191 } 192 if err := b.build(); err != nil { 193 return nil, err 194 } 195 196 wc.State.WC = wc.State.WC[:0] 197 dir := "" 198 for _, p := range sortKeys(b.WC) { 199 switch { 200 case dir != "" && strings.HasPrefix(p, dir): 201 case len(b.WC[p]) == 1: 202 e := b.WC[p][0] 203 if e.Type == unlinkable { 204 continue 205 } 206 wc.State.WC = append(wc.State.WC, e) 207 if e.IsDir { 208 dir = p + "/" 209 } else { 210 dir = "" 211 } 212 if c, ok := b.State[p]; ok { 213 if c.Layer == e.Layer && c.IsDir == e.IsDir { 214 delete(b.State, p) 215 } 216 } 217 } 218 } 219 220 ul := make([]*Entry, len(b.State)) 221 for i, p := range sortKeys(b.State) { 222 ul[i] = b.State[p] 223 } 224 return ul, nil 225 } 226 227 func (wc *WC) Errorf(err error) error { 228 switch v := err.(type) { 229 case *os.LinkError: 230 if r, err := wc.Rel('/', v.New); err == nil { 231 v.New = filepath.ToSlash(r) 232 } 233 return fmt.Errorf("%v: %v", v.New, v.Err) 234 case *os.PathError: 235 if r, err := wc.Rel('/', v.Path); err == nil { 236 v.Path = filepath.ToSlash(r) 237 } 238 return fmt.Errorf("%v: %v", v.Path, v.Err) 239 } 240 return err 241 } 242 243 type State struct { 244 Layers map[string]string `json:"layers,omitempty"` 245 WC []*Entry `json:"wc,omitempty"` 246 } 247 248 type Entry struct { 249 Layer string `json:"layer"` 250 Path string `json:"path"` 251 Origin string `json:"origin,omitempty"` 252 IsDir bool `json:"dir,omitempty"` 253 Type string `json:"type,omitempty"` 254 } 255 256 func (e *Entry) Format(format string) string { 257 var sep, lhs, rhs string 258 if e.IsDir { 259 sep = "/" 260 } 261 if e.Path != "" { 262 lhs = e.Path + sep 263 } 264 switch { 265 case e.Origin == "": 266 rhs = e.Layer 267 case e.Type == "link": 268 rhs = filepath.FromSlash(e.Origin + sep) 269 case e.Type == "subrepo": 270 rhs = e.Origin 271 default: 272 rhs = e.Layer + ":" + e.Origin + sep 273 } 274 return fmt.Sprintf(format, lhs, rhs) 275 } 276 277 type ResolveError struct { 278 Name string 279 List []string 280 } 281 282 func (e ResolveError) Error() string { 283 s := fmt.Sprintf("cannot resolve layer '%v'", e.Name) 284 if len(e.List) == 0 { 285 return s 286 } 287 return fmt.Sprintf("%v:\n %v", s, strings.Join(e.List, "\n ")) 288 } 289 290 const unlinkable = "_" 291 292 type wcBuilder struct { 293 State map[string]*Entry 294 WC map[string][]*Entry 295 296 ui UI 297 wc *WC 298 l *Layer 299 layer string 300 aliases map[string]string 301 } 302 303 func (b *wcBuilder) build() error { 304 layers, err := b.wc.Layers() 305 if err != nil { 306 return err 307 } 308 b.State = make(map[string]*Entry) 309 for _, e := range b.wc.State.WC { 310 b.State[e.Path] = e 311 } 312 b.WC = make(map[string][]*Entry) 313 b.aliases = make(map[string]string) 314 for _, l := range layers { 315 b.l = l 316 b.layer = l.Path() 317 if err := b.repo(); err != nil { 318 return err 319 } 320 if err := b.link(); err != nil { 321 return err 322 } 323 if err := b.subrepo(); err != nil { 324 return err 325 } 326 for src, dst := range b.l.Aliases { 327 if _, ok := b.aliases[src]; !ok { 328 b.aliases[src] = dst 329 } 330 } 331 } 332 return nil 333 } 334 335 func (b *wcBuilder) repo() error { 336 return b.wc.repo.Walk(b.layer, func(path string, fi os.FileInfo, err error) error { 337 if err != nil { 338 return err 339 } 340 origin := path[len(b.layer)+1:] 341 path, err = b.alias(origin) 342 if err != nil { 343 return err 344 } 345 if _, ok := b.WC[path]; !ok { 346 b.parents(path, true) 347 e := &Entry{ 348 Layer: b.layer, 349 Path: path, 350 IsDir: fi.IsDir(), 351 } 352 b.WC[path] = append(b.WC[path], e) 353 if path != origin { 354 e.Origin = origin 355 for p, o := filepath.Dir(path), filepath.Dir(origin); p != "."; p = filepath.Dir(p) { 356 e := b.find(filepath.ToSlash(p)) 357 if o != "." { 358 e.Origin = filepath.ToSlash(o) 359 o = filepath.Dir(o) 360 } else { 361 e.Type = unlinkable 362 } 363 } 364 } 365 } 366 return nil 367 }) 368 } 369 370 func (b *wcBuilder) link() error { 371 link := func(src, dst string) (bool, error) { 372 fi, err := os.Stat(src) 373 if err != nil { 374 return false, nil 375 } 376 dst, err = b.alias(dst) 377 if err != nil { 378 return false, fmt.Errorf("link %v", err) 379 } 380 switch list, ok := b.WC[dst]; { 381 case !ok: 382 b.parents(dst, false) 383 b.WC[dst] = append(b.WC[dst], &Entry{ 384 Layer: b.layer, 385 Path: dst, 386 Origin: src, 387 IsDir: fi.IsDir(), 388 Type: "link", 389 }) 390 case list[0].Layer == b.layer && list[0].Type != "link": 391 b.ui.Errorf("warning: link: '%v' exists in the repository\n", dst) 392 } 393 return true, nil 394 } 395 for _, dir := range sortKeys(b.l.Links) { 396 for _, l := range b.l.Links[dir] { 397 src := filepath.FromSlash(filepath.Clean(os.ExpandEnv(l.Src))) 398 dst := filepath.ToSlash(filepath.Join(dir, l.Dst)) 399 if len(l.Path) > 0 { 400 L: 401 for _, v := range l.Path { 402 for _, p := range filepath.SplitList(os.ExpandEnv(v)) { 403 switch ok, err := link(filepath.FromSlash(filepath.Clean(filepath.Join(p, src))), dst); { 404 case err != nil: 405 return err 406 case ok: 407 break L 408 } 409 } 410 } 411 } else if _, err := link(src, dst); err != nil { 412 return err 413 } 414 } 415 } 416 return nil 417 } 418 419 func (b *wcBuilder) subrepo() error { 420 for _, dir := range sortKeys(b.l.Subrepos) { 421 for _, sub := range b.l.Subrepos[dir] { 422 name := sub.Name 423 if name == "" { 424 name = filepath.Base(sub.Src) 425 } 426 dst, err := b.alias(filepath.ToSlash(filepath.Join(dir, name))) 427 if err != nil { 428 return fmt.Errorf("subrepo %v", err) 429 } 430 switch list, ok := b.WC[dst]; { 431 case !ok: 432 b.parents(dst, false) 433 b.WC[dst] = append(b.WC[dst], &Entry{ 434 Layer: b.layer, 435 Path: dst, 436 Origin: sub.Src, 437 Type: "subrepo", 438 }) 439 case list[0].Layer == b.layer && list[0].Type != "subrepo": 440 b.ui.Errorf("warning: subrepo: '%v' exists in the repository\n", dst) 441 } 442 } 443 } 444 return nil 445 } 446 447 func (b *wcBuilder) alias(path string) (string, error) { 448 for src := path; src != "."; src = filepath.Dir(src) { 449 if dst, ok := b.aliases[src]; ok { 450 if path == src { 451 path = dst 452 } else { 453 path = filepath.Join(dst, path[len(src)+1:]) 454 } 455 return b.wc.Rel('/', filepath.Clean(os.ExpandEnv(path))) 456 } 457 } 458 return path, nil 459 } 460 461 func (b *wcBuilder) parents(path string, linkable bool) { 462 inWC := true 463 for i, r := range path { 464 if r != '/' { 465 continue 466 } 467 p := path[:i] 468 e := b.find(p) 469 if e == nil { 470 e = &Entry{ 471 Layer: b.layer, 472 Path: p, 473 IsDir: true, 474 } 475 b.WC[p] = append(b.WC[p], e) 476 } 477 if !inWC { 478 continue 479 } 480 if linkable { 481 switch _, ok := b.State[p]; { 482 case ok: 483 inWC = false 484 if b.wc.Exists(p) && !b.wc.IsLink(p) { 485 e.Type = unlinkable 486 delete(b.State, p) 487 } 488 case b.wc.Exists(p): 489 e.Type = unlinkable 490 } 491 } else { 492 e.Type = unlinkable 493 } 494 } 495 } 496 497 func (b *wcBuilder) find(p string) *Entry { 498 for _, e := range b.WC[p] { 499 if e.Layer == b.layer { 500 return e 501 } 502 } 503 return nil 504 }