1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package embed provides access to files embedded in the running Go program. 6 // 7 // Go source files that import "embed" can use the //go:embed directive 8 // to initialize a variable of type string, []byte, or [FS] with the contents of 9 // files read from the package directory or subdirectories at compile time. 10 // 11 // For example, here are three ways to embed a file named hello.txt 12 // and then print its contents at run time. 13 // 14 // Embedding one file into a string: 15 // 16 // import _ "embed" 17 // 18 // //go:embed hello.txt 19 // var s string 20 // print(s) 21 // 22 // Embedding one file into a slice of bytes: 23 // 24 // import _ "embed" 25 // 26 // //go:embed hello.txt 27 // var b []byte 28 // print(string(b)) 29 // 30 // Embedded one or more files into a file system: 31 // 32 // import "embed" 33 // 34 // //go:embed hello.txt 35 // var f embed.FS 36 // data, _ := f.ReadFile("hello.txt") 37 // print(string(data)) 38 // 39 // # Directives 40 // 41 // A //go:embed directive above a variable declaration specifies which files to embed, 42 // using one or more path.Match patterns. 43 // 44 // The directive must immediately precede a line containing the declaration of a single variable. 45 // Only blank lines and ‘//’ line comments are permitted between the directive and the declaration. 46 // 47 // The type of the variable must be a string type, or a slice of a byte type, 48 // or [FS] (or an alias of [FS]). 49 // 50 // For example: 51 // 52 // package server 53 // 54 // import "embed" 55 // 56 // // content holds our static web server content. 57 // //go:embed image/* template/* 58 // //go:embed html/index.html 59 // var content embed.FS 60 // 61 // The Go build system will recognize the directives and arrange for the declared variable 62 // (in the example above, content) to be populated with the matching files from the file system. 63 // 64 // The //go:embed directive accepts multiple space-separated patterns for 65 // brevity, but it can also be repeated, to avoid very long lines when there are 66 // many patterns. The patterns are interpreted relative to the package directory 67 // containing the source file. The path separator is a forward slash, even on 68 // Windows systems. Patterns may not contain ‘.’ or ‘..’ or empty path elements, 69 // nor may they begin or end with a slash. To match everything in the current 70 // directory, use ‘*’ instead of ‘.’. To allow for naming files with spaces in 71 // their names, patterns can be written as Go double-quoted or back-quoted 72 // string literals. 73 // 74 // If a pattern names a directory, all files in the subtree rooted at that directory are 75 // embedded (recursively), except that files with names beginning with ‘.’ or ‘_’ 76 // are excluded. So the variable in the above example is almost equivalent to: 77 // 78 // // content is our static web server content. 79 // //go:embed image template html/index.html 80 // var content embed.FS 81 // 82 // The difference is that ‘image/*’ embeds ‘image/.tempfile’ while ‘image’ does not. 83 // Neither embeds ‘image/dir/.tempfile’. 84 // 85 // If a pattern begins with the prefix ‘all:’, then the rule for walking directories is changed 86 // to include those files beginning with ‘.’ or ‘_’. For example, ‘all:image’ embeds 87 // both ‘image/.tempfile’ and ‘image/dir/.tempfile’. 88 // 89 // The //go:embed directive can be used with both exported and unexported variables, 90 // depending on whether the package wants to make the data available to other packages. 91 // It can only be used with variables at package scope, not with local variables. 92 // 93 // Patterns must not match files outside the package's module, such as ‘.git/*’ or symbolic links. 94 // Patterns must not match files whose names include the special punctuation characters " * < > ? ` ' | / \ and :. 95 // Matches for empty directories are ignored. After that, each pattern in a //go:embed line 96 // must match at least one file or non-empty directory. 97 // 98 // If any patterns are invalid or have invalid matches, the build will fail. 99 // 100 // # Strings and Bytes 101 // 102 // The //go:embed line for a variable of type string or []byte can have only a single pattern, 103 // and that pattern can match only a single file. The string or []byte is initialized with 104 // the contents of that file. 105 // 106 // The //go:embed directive requires importing "embed", even when using a string or []byte. 107 // In source files that don't refer to [embed.FS], use a blank import (import _ "embed"). 108 // 109 // # File Systems 110 // 111 // For embedding a single file, a variable of type string or []byte is often best. 112 // The [FS] type enables embedding a tree of files, such as a directory of static 113 // web server content, as in the example above. 114 // 115 // FS implements the [io/fs] package's [FS] interface, so it can be used with any package that 116 // understands file systems, including [net/http], [text/template], and [html/template]. 117 // 118 // For example, given the content variable in the example above, we can write: 119 // 120 // http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content)))) 121 // 122 // template.ParseFS(content, "*.tmpl") 123 // 124 // # Tools 125 // 126 // To support tools that analyze Go packages, the patterns found in //go:embed lines 127 // are available in “go list” output. See the EmbedPatterns, TestEmbedPatterns, 128 // and XTestEmbedPatterns fields in the “go help list” output. 129 package embed 130 131 import ( 132 "errors" 133 "io" 134 "io/fs" 135 "time" 136 ) 137 138 // An FS is a read-only collection of files, usually initialized with a //go:embed directive. 139 // When declared without a //go:embed directive, an FS is an empty file system. 140 // 141 // An FS is a read-only value, so it is safe to use from multiple goroutines 142 // simultaneously and also safe to assign values of type FS to each other. 143 // 144 // FS implements fs.FS, so it can be used with any package that understands 145 // file system interfaces, including net/http, text/template, and html/template. 146 // 147 // See the package documentation for more details about initializing an FS. 148 type FS struct { 149 // The compiler knows the layout of this struct. 150 // See cmd/compile/internal/staticdata's WriteEmbed. 151 // 152 // The files list is sorted by name but not by simple string comparison. 153 // Instead, each file's name takes the form "dir/elem" or "dir/elem/". 154 // The optional trailing slash indicates that the file is itself a directory. 155 // The files list is sorted first by dir (if dir is missing, it is taken to be ".") 156 // and then by base, so this list of files: 157 // 158 // p 159 // q/ 160 // q/r 161 // q/s/ 162 // q/s/t 163 // q/s/u 164 // q/v 165 // w 166 // 167 // is actually sorted as: 168 // 169 // p # dir=. elem=p 170 // q/ # dir=. elem=q 171 // w/ # dir=. elem=w 172 // q/r # dir=q elem=r 173 // q/s/ # dir=q elem=s 174 // q/v # dir=q elem=v 175 // q/s/t # dir=q/s elem=t 176 // q/s/u # dir=q/s elem=u 177 // 178 // This order brings directory contents together in contiguous sections 179 // of the list, allowing a directory read to use binary search to find 180 // the relevant sequence of entries. 181 files *[]file 182 } 183 184 // split splits the name into dir and elem as described in the 185 // comment in the FS struct above. isDir reports whether the 186 // final trailing slash was present, indicating that name is a directory. 187 func split(name string) (dir, elem string, isDir bool) { 188 if name[len(name)-1] == '/' { 189 isDir = true 190 name = name[:len(name)-1] 191 } 192 i := len(name) - 1 193 for i >= 0 && name[i] != '/' { 194 i-- 195 } 196 if i < 0 { 197 return ".", name, isDir 198 } 199 return name[:i], name[i+1:], isDir 200 } 201 202 // trimSlash trims a trailing slash from name, if present, 203 // returning the possibly shortened name. 204 func trimSlash(name string) string { 205 if len(name) > 0 && name[len(name)-1] == '/' { 206 return name[:len(name)-1] 207 } 208 return name 209 } 210 211 var ( 212 _ fs.ReadDirFS = FS{} 213 _ fs.ReadFileFS = FS{} 214 ) 215 216 // A file is a single file in the FS. 217 // It implements fs.FileInfo and fs.DirEntry. 218 type file struct { 219 // The compiler knows the layout of this struct. 220 // See cmd/compile/internal/staticdata's WriteEmbed. 221 name string 222 data string 223 hash [16]byte // truncated SHA256 hash 224 } 225 226 var ( 227 _ fs.FileInfo = (*file)(nil) 228 _ fs.DirEntry = (*file)(nil) 229 ) 230 231 func (f *file) Name() string { _, elem, _ := split(f.name); return elem } 232 func (f *file) Size() int64 { return int64(len(f.data)) } 233 func (f *file) ModTime() time.Time { return time.Time{} } 234 func (f *file) IsDir() bool { _, _, isDir := split(f.name); return isDir } 235 func (f *file) Sys() any { return nil } 236 func (f *file) Type() fs.FileMode { return f.Mode().Type() } 237 func (f *file) Info() (fs.FileInfo, error) { return f, nil } 238 239 func (f *file) Mode() fs.FileMode { 240 if f.IsDir() { 241 return fs.ModeDir | 0555 242 } 243 return 0444 244 } 245 246 func (f *file) String() string { 247 return fs.FormatFileInfo(f) 248 } 249 250 // dotFile is a file for the root directory, 251 // which is omitted from the files list in a FS. 252 var dotFile = &file{name: "./"} 253 254 // lookup returns the named file, or nil if it is not present. 255 func (f FS) lookup(name string) *file { 256 if !fs.ValidPath(name) { 257 // The compiler should never emit a file with an invalid name, 258 // so this check is not strictly necessary (if name is invalid, 259 // we shouldn't find a match below), but it's a good backstop anyway. 260 return nil 261 } 262 if name == "." { 263 return dotFile 264 } 265 if f.files == nil { 266 return nil 267 } 268 269 // Binary search to find where name would be in the list, 270 // and then check if name is at that position. 271 dir, elem, _ := split(name) 272 files := *f.files 273 i := sortSearch(len(files), func(i int) bool { 274 idir, ielem, _ := split(files[i].name) 275 return idir > dir || idir == dir && ielem >= elem 276 }) 277 if i < len(files) && trimSlash(files[i].name) == name { 278 return &files[i] 279 } 280 return nil 281 } 282 283 // readDir returns the list of files corresponding to the directory dir. 284 func (f FS) readDir(dir string) []file { 285 if f.files == nil { 286 return nil 287 } 288 // Binary search to find where dir starts and ends in the list 289 // and then return that slice of the list. 290 files := *f.files 291 i := sortSearch(len(files), func(i int) bool { 292 idir, _, _ := split(files[i].name) 293 return idir >= dir 294 }) 295 j := sortSearch(len(files), func(j int) bool { 296 jdir, _, _ := split(files[j].name) 297 return jdir > dir 298 }) 299 return files[i:j] 300 } 301 302 // Open opens the named file for reading and returns it as an [fs.File]. 303 // 304 // The returned file implements [io.Seeker] and [io.ReaderAt] when the file is not a directory. 305 func (f FS) Open(name string) (fs.File, error) { 306 file := f.lookup(name) 307 if file == nil { 308 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} 309 } 310 if file.IsDir() { 311 return &openDir{file, f.readDir(name), 0}, nil 312 } 313 return &openFile{file, 0}, nil 314 } 315 316 // ReadDir reads and returns the entire named directory. 317 func (f FS) ReadDir(name string) ([]fs.DirEntry, error) { 318 file, err := f.Open(name) 319 if err != nil { 320 return nil, err 321 } 322 dir, ok := file.(*openDir) 323 if !ok { 324 return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")} 325 } 326 list := make([]fs.DirEntry, len(dir.files)) 327 for i := range list { 328 list[i] = &dir.files[i] 329 } 330 return list, nil 331 } 332 333 // ReadFile reads and returns the content of the named file. 334 func (f FS) ReadFile(name string) ([]byte, error) { 335 file, err := f.Open(name) 336 if err != nil { 337 return nil, err 338 } 339 ofile, ok := file.(*openFile) 340 if !ok { 341 return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")} 342 } 343 return []byte(ofile.f.data), nil 344 } 345 346 // An openFile is a regular file open for reading. 347 type openFile struct { 348 f *file // the file itself 349 offset int64 // current read offset 350 } 351 352 var ( 353 _ io.Seeker = (*openFile)(nil) 354 _ io.ReaderAt = (*openFile)(nil) 355 ) 356 357 func (f *openFile) Close() error { return nil } 358 func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil } 359 360 func (f *openFile) Read(b []byte) (int, error) { 361 if f.offset >= int64(len(f.f.data)) { 362 return 0, io.EOF 363 } 364 if f.offset < 0 { 365 return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid} 366 } 367 n := copy(b, f.f.data[f.offset:]) 368 f.offset += int64(n) 369 return n, nil 370 } 371 372 func (f *openFile) Seek(offset int64, whence int) (int64, error) { 373 switch whence { 374 case 0: 375 // offset += 0 376 case 1: 377 offset += f.offset 378 case 2: 379 offset += int64(len(f.f.data)) 380 } 381 if offset < 0 || offset > int64(len(f.f.data)) { 382 return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid} 383 } 384 f.offset = offset 385 return offset, nil 386 } 387 388 func (f *openFile) ReadAt(b []byte, offset int64) (int, error) { 389 if offset < 0 || offset > int64(len(f.f.data)) { 390 return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid} 391 } 392 n := copy(b, f.f.data[offset:]) 393 if n < len(b) { 394 return n, io.EOF 395 } 396 return n, nil 397 } 398 399 // An openDir is a directory open for reading. 400 type openDir struct { 401 f *file // the directory file itself 402 files []file // the directory contents 403 offset int // the read offset, an index into the files slice 404 } 405 406 func (d *openDir) Close() error { return nil } 407 func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil } 408 409 func (d *openDir) Read([]byte) (int, error) { 410 return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")} 411 } 412 413 func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) { 414 n := len(d.files) - d.offset 415 if n == 0 { 416 if count <= 0 { 417 return nil, nil 418 } 419 return nil, io.EOF 420 } 421 if count > 0 && n > count { 422 n = count 423 } 424 list := make([]fs.DirEntry, n) 425 for i := range list { 426 list[i] = &d.files[d.offset+i] 427 } 428 d.offset += n 429 return list, nil 430 } 431 432 // sortSearch is like sort.Search, avoiding an import. 433 func sortSearch(n int, f func(int) bool) int { 434 // Define f(-1) == false and f(n) == true. 435 // Invariant: f(i-1) == false, f(j) == true. 436 i, j := 0, n 437 for i < j { 438 h := int(uint(i+j) >> 1) // avoid overflow when computing h 439 // i ≤ h < j 440 if !f(h) { 441 i = h + 1 // preserves f(i-1) == false 442 } else { 443 j = h // preserves f(j) == true 444 } 445 } 446 // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. 447 return i 448 } 449