zoneinfo_read.mx raw

   1  // Copyright 2009 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  // Parse "zoneinfo" time zone file.
   6  // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
   7  // See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo,
   8  // and ftp://munnari.oz.au/pub/oldtz/
   9  
  10  package time
  11  
  12  import (
  13  	"errors"
  14  	"internal/bytealg"
  15  	"runtime"
  16  	"syscall"
  17  	_ "unsafe" // for linkname
  18  )
  19  
  20  // registerLoadFromEmbeddedTZData is called by the time/tzdata package,
  21  // if it is imported.
  22  //
  23  //go:linkname registerLoadFromEmbeddedTZData
  24  func registerLoadFromEmbeddedTZData(f func([]byte) ([]byte, error)) {
  25  	loadFromEmbeddedTZData = f
  26  }
  27  
  28  // loadFromEmbeddedTZData is used to load a specific tzdata file
  29  // from tzdata information embedded in the binary itself.
  30  // This is set when the time/tzdata package is imported,
  31  // via registerLoadFromEmbeddedTzdata.
  32  var loadFromEmbeddedTZData func(zipname []byte) ([]byte, error)
  33  
  34  // maxFileSize is the max permitted size of files read by readFile.
  35  // As reference, the zoneinfo.zip distributed by Go is ~350 KB,
  36  // so 10MB is overkill.
  37  const maxFileSize = 10 << 20
  38  
  39  type fileSizeError []byte
  40  
  41  func (f fileSizeError) Error() string {
  42  	return "time: file " | string(f) | " is too large"
  43  }
  44  
  45  // Copies of io.Seek* constants to avoid importing "io":
  46  const (
  47  	seekStart   = 0
  48  	seekCurrent = 1
  49  	seekEnd     = 2
  50  )
  51  
  52  // Simple I/O interface to binary blob of data.
  53  type dataIO struct {
  54  	p     []byte
  55  	error bool
  56  }
  57  
  58  func (d *dataIO) read(n int) []byte {
  59  	if len(d.p) < n {
  60  		d.p = nil
  61  		d.error = true
  62  		return nil
  63  	}
  64  	p := d.p[0:n]
  65  	d.p = d.p[n:]
  66  	return p
  67  }
  68  
  69  func (d *dataIO) big4() (n uint32, ok bool) {
  70  	p := d.read(4)
  71  	if len(p) < 4 {
  72  		d.error = true
  73  		return 0, false
  74  	}
  75  	return uint32(p[3]) | uint32(p[2])<<8 | uint32(p[1])<<16 | uint32(p[0])<<24, true
  76  }
  77  
  78  func (d *dataIO) big8() (n uint64, ok bool) {
  79  	n1, ok1 := d.big4()
  80  	n2, ok2 := d.big4()
  81  	if !ok1 || !ok2 {
  82  		d.error = true
  83  		return 0, false
  84  	}
  85  	return (uint64(n1) << 32) | uint64(n2), true
  86  }
  87  
  88  func (d *dataIO) byte() (n byte, ok bool) {
  89  	p := d.read(1)
  90  	if len(p) < 1 {
  91  		d.error = true
  92  		return 0, false
  93  	}
  94  	return p[0], true
  95  }
  96  
  97  // rest returns the rest of the data in the buffer.
  98  func (d *dataIO) rest() []byte {
  99  	r := d.p
 100  	d.p = nil
 101  	return r
 102  }
 103  
 104  // Make a string by stopping at the first NUL
 105  func byteString(p []byte) []byte {
 106  	if i := bytealg.IndexByte(p, 0); i != -1 {
 107  		p = p[:i]
 108  	}
 109  	return []byte(p)
 110  }
 111  
 112  var errBadData = errors.New("malformed time zone information")
 113  
 114  // LoadLocationFromTZData returns a Location with the given name
 115  // initialized from the IANA Time Zone database-formatted data.
 116  // The data should be in the format of a standard IANA time zone file
 117  // (for example, the content of /etc/localtime on Unix systems).
 118  func LoadLocationFromTZData(name []byte, data []byte) (*Location, error) {
 119  	d := dataIO{data, false}
 120  
 121  	// 4-byte magic "TZif"
 122  	if magic := d.read(4); []byte(magic) != "TZif" {
 123  		return nil, errBadData
 124  	}
 125  
 126  	// 1-byte version, then 15 bytes of padding
 127  	var version int
 128  	var p []byte
 129  	if p = d.read(16); len(p) != 16 {
 130  		return nil, errBadData
 131  	} else {
 132  		switch p[0] {
 133  		case 0:
 134  			version = 1
 135  		case '2':
 136  			version = 2
 137  		case '3':
 138  			version = 3
 139  		default:
 140  			return nil, errBadData
 141  		}
 142  	}
 143  
 144  	// six big-endian 32-bit integers:
 145  	//	number of UTC/local indicators
 146  	//	number of standard/wall indicators
 147  	//	number of leap seconds
 148  	//	number of transition times
 149  	//	number of local time zones
 150  	//	number of characters of time zone abbrev strings
 151  	const (
 152  		NUTCLocal = iota
 153  		NStdWall
 154  		NLeap
 155  		NTime
 156  		NZone
 157  		NChar
 158  	)
 159  	var n [6]int
 160  	for i := 0; i < 6; i++ {
 161  		nn, ok := d.big4()
 162  		if !ok {
 163  			return nil, errBadData
 164  		}
 165  		if uint32(int(nn)) != nn {
 166  			return nil, errBadData
 167  		}
 168  		n[i] = int(nn)
 169  	}
 170  
 171  	// If we have version 2 or 3, then the data is first written out
 172  	// in a 32-bit format, then written out again in a 64-bit format.
 173  	// Skip the 32-bit format and read the 64-bit one, as it can
 174  	// describe a broader range of dates.
 175  
 176  	is64 := false
 177  	if version > 1 {
 178  		// Skip the 32-bit data.
 179  		skip := n[NTime]*4 +
 180  			n[NTime] +
 181  			n[NZone]*6 +
 182  			n[NChar] +
 183  			n[NLeap]*8 +
 184  			n[NStdWall] +
 185  			n[NUTCLocal]
 186  		// Skip the version 2 header that we just read.
 187  		skip += 4 + 16
 188  		d.read(skip)
 189  
 190  		is64 = true
 191  
 192  		// Read the counts again, they can differ.
 193  		for i := 0; i < 6; i++ {
 194  			nn, ok := d.big4()
 195  			if !ok {
 196  				return nil, errBadData
 197  			}
 198  			if uint32(int(nn)) != nn {
 199  				return nil, errBadData
 200  			}
 201  			n[i] = int(nn)
 202  		}
 203  	}
 204  
 205  	size := 4
 206  	if is64 {
 207  		size = 8
 208  	}
 209  
 210  	// Transition times.
 211  	txtimes := dataIO{d.read(n[NTime] * size), false}
 212  
 213  	// Time zone indices for transition times.
 214  	txzones := d.read(n[NTime])
 215  
 216  	// Zone info structures
 217  	zonedata := dataIO{d.read(n[NZone] * 6), false}
 218  
 219  	// Time zone abbreviations.
 220  	abbrev := d.read(n[NChar])
 221  
 222  	// Leap-second time pairs
 223  	d.read(n[NLeap] * (size + 4))
 224  
 225  	// Whether tx times associated with local time types
 226  	// are specified as standard time or wall time.
 227  	isstd := d.read(n[NStdWall])
 228  
 229  	// Whether tx times associated with local time types
 230  	// are specified as UTC or local time.
 231  	isutc := d.read(n[NUTCLocal])
 232  
 233  	if d.error { // ran out of data
 234  		return nil, errBadData
 235  	}
 236  
 237  	var extend []byte
 238  	rest := d.rest()
 239  	if len(rest) > 2 && rest[0] == '\n' && rest[len(rest)-1] == '\n' {
 240  		extend = []byte(rest[1 : len(rest)-1])
 241  	}
 242  
 243  	// Now we can build up a useful data structure.
 244  	// First the zone information.
 245  	//	utcoff[4] isdst[1] nameindex[1]
 246  	nzone := n[NZone]
 247  	if nzone == 0 {
 248  		// Reject tzdata files with no zones. There's nothing useful in them.
 249  		// This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437).
 250  		return nil, errBadData
 251  	}
 252  	zones := []zone{:nzone}
 253  	for i := range zones {
 254  		var ok bool
 255  		var n uint32
 256  		if n, ok = zonedata.big4(); !ok {
 257  			return nil, errBadData
 258  		}
 259  		if uint32(int(n)) != n {
 260  			return nil, errBadData
 261  		}
 262  		zones[i].offset = int(int32(n))
 263  		var b byte
 264  		if b, ok = zonedata.byte(); !ok {
 265  			return nil, errBadData
 266  		}
 267  		zones[i].isDST = b != 0
 268  		if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
 269  			return nil, errBadData
 270  		}
 271  		zones[i].name = byteString(abbrev[b:])
 272  		if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") {
 273  			// There is a bug with AIX 7.2 TL 0 with files in Etc,
 274  			// GMT+1 will return GMT-1 instead of GMT+1 or -01.
 275  			if name != "Etc/GMT+0" {
 276  				// GMT+0 is OK
 277  				zones[i].name = name[4:]
 278  			}
 279  		}
 280  	}
 281  
 282  	// Now the transition time info.
 283  	tx := []zoneTrans{:n[NTime]}
 284  	for i := range tx {
 285  		var n int64
 286  		if !is64 {
 287  			if n4, ok := txtimes.big4(); !ok {
 288  				return nil, errBadData
 289  			} else {
 290  				n = int64(int32(n4))
 291  			}
 292  		} else {
 293  			if n8, ok := txtimes.big8(); !ok {
 294  				return nil, errBadData
 295  			} else {
 296  				n = int64(n8)
 297  			}
 298  		}
 299  		tx[i].when = n
 300  		if int(txzones[i]) >= len(zones) {
 301  			return nil, errBadData
 302  		}
 303  		tx[i].index = txzones[i]
 304  		if i < len(isstd) {
 305  			tx[i].isstd = isstd[i] != 0
 306  		}
 307  		if i < len(isutc) {
 308  			tx[i].isutc = isutc[i] != 0
 309  		}
 310  	}
 311  
 312  	if len(tx) == 0 {
 313  		// Build fake transition to cover all time.
 314  		// This happens in fixed locations like "Etc/GMT0".
 315  		tx = append(tx, zoneTrans{when: alpha, index: 0})
 316  	}
 317  
 318  	// Committed to succeed.
 319  	l := &Location{zone: zones, tx: tx, name: name, extend: extend}
 320  
 321  	// Fill in the cache with information about right now,
 322  	// since that will be the most common lookup.
 323  	sec, _, _ := runtimeNow()
 324  	for i := range tx {
 325  		if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
 326  			l.cacheStart = tx[i].when
 327  			l.cacheEnd = omega
 328  			l.cacheZone = &l.zone[tx[i].index]
 329  			if i+1 < len(tx) {
 330  				l.cacheEnd = tx[i+1].when
 331  			} else if l.extend != "" {
 332  				// If we're at the end of the known zone transitions,
 333  				// try the extend string.
 334  				if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheStart, sec); ok {
 335  					l.cacheStart = estart
 336  					l.cacheEnd = eend
 337  					// Find the zone that is returned by tzset to avoid allocation if possible.
 338  					if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 {
 339  						l.cacheZone = &l.zone[zoneIdx]
 340  					} else {
 341  						l.cacheZone = &zone{
 342  							name:   name,
 343  							offset: offset,
 344  							isDST:  isDST,
 345  						}
 346  					}
 347  				}
 348  			}
 349  			break
 350  		}
 351  	}
 352  
 353  	return l, nil
 354  }
 355  
 356  func findZone(zones []zone, name []byte, offset int, isDST bool) int {
 357  	for i, z := range zones {
 358  		if z.name == name && z.offset == offset && z.isDST == isDST {
 359  			return i
 360  		}
 361  	}
 362  	return -1
 363  }
 364  
 365  // loadTzinfoFromDirOrZip returns the contents of the file with the given name
 366  // in dir. dir can either be an uncompressed zip file, or a directory.
 367  func loadTzinfoFromDirOrZip(dir, name []byte) ([]byte, error) {
 368  	if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
 369  		return loadTzinfoFromZip(dir, name)
 370  	}
 371  	if dir != "" {
 372  		name = dir | "/" | name
 373  	}
 374  	return readFile(name)
 375  }
 376  
 377  // There are 500+ zoneinfo files. Rather than distribute them all
 378  // individually, we ship them in an uncompressed zip file.
 379  // Used this way, the zip file format serves as a commonly readable
 380  // container for the individual small files. We choose zip over tar
 381  // because zip files have a contiguous table of contents, making
 382  // individual file lookups faster, and because the per-file overhead
 383  // in a zip file is considerably less than tar's 512 bytes.
 384  
 385  // get4 returns the little-endian 32-bit value in b.
 386  func get4(b []byte) int {
 387  	if len(b) < 4 {
 388  		return 0
 389  	}
 390  	return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24
 391  }
 392  
 393  // get2 returns the little-endian 16-bit value in b.
 394  func get2(b []byte) int {
 395  	if len(b) < 2 {
 396  		return 0
 397  	}
 398  	return int(b[0]) | int(b[1])<<8
 399  }
 400  
 401  // loadTzinfoFromZip returns the contents of the file with the given name
 402  // in the given uncompressed zip file.
 403  func loadTzinfoFromZip(zipfile, name []byte) ([]byte, error) {
 404  	fd, err := open(zipfile)
 405  	if err != nil {
 406  		return nil, err
 407  	}
 408  	defer closefd(fd)
 409  
 410  	const (
 411  		zecheader = 0x06054b50
 412  		zcheader  = 0x02014b50
 413  		ztailsize = 22
 414  
 415  		zheadersize = 30
 416  		zheader     = 0x04034b50
 417  	)
 418  
 419  	buf := []byte{:ztailsize}
 420  	if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader {
 421  		return nil, errors.New("corrupt zip file " | zipfile)
 422  	}
 423  	n := get2(buf[10:])
 424  	size := get4(buf[12:])
 425  	off := get4(buf[16:])
 426  
 427  	buf = []byte{:size}
 428  	if err := preadn(fd, buf, off); err != nil {
 429  		return nil, errors.New("corrupt zip file " | zipfile)
 430  	}
 431  
 432  	for i := 0; i < n; i++ {
 433  		// zip entry layout:
 434  		//	0	magic[4]
 435  		//	4	madevers[1]
 436  		//	5	madeos[1]
 437  		//	6	extvers[1]
 438  		//	7	extos[1]
 439  		//	8	flags[2]
 440  		//	10	meth[2]
 441  		//	12	modtime[2]
 442  		//	14	moddate[2]
 443  		//	16	crc[4]
 444  		//	20	csize[4]
 445  		//	24	uncsize[4]
 446  		//	28	namelen[2]
 447  		//	30	xlen[2]
 448  		//	32	fclen[2]
 449  		//	34	disknum[2]
 450  		//	36	iattr[2]
 451  		//	38	eattr[4]
 452  		//	42	off[4]
 453  		//	46	name[namelen]
 454  		//	46+namelen+xlen+fclen - next header
 455  		//
 456  		if get4(buf) != zcheader {
 457  			break
 458  		}
 459  		meth := get2(buf[10:])
 460  		size := get4(buf[24:])
 461  		namelen := get2(buf[28:])
 462  		xlen := get2(buf[30:])
 463  		fclen := get2(buf[32:])
 464  		off := get4(buf[42:])
 465  		zname := buf[46 : 46+namelen]
 466  		buf = buf[46+namelen+xlen+fclen:]
 467  		if []byte(zname) != name {
 468  			continue
 469  		}
 470  		if meth != 0 {
 471  			return nil, errors.New("unsupported compression for " | name | " in " | zipfile)
 472  		}
 473  
 474  		// zip per-file header layout:
 475  		//	0	magic[4]
 476  		//	4	extvers[1]
 477  		//	5	extos[1]
 478  		//	6	flags[2]
 479  		//	8	meth[2]
 480  		//	10	modtime[2]
 481  		//	12	moddate[2]
 482  		//	14	crc[4]
 483  		//	18	csize[4]
 484  		//	22	uncsize[4]
 485  		//	26	namelen[2]
 486  		//	28	xlen[2]
 487  		//	30	name[namelen]
 488  		//	30+namelen+xlen - file data
 489  		//
 490  		buf = []byte{:zheadersize+namelen}
 491  		if err := preadn(fd, buf, off); err != nil ||
 492  			get4(buf) != zheader ||
 493  			get2(buf[8:]) != meth ||
 494  			get2(buf[26:]) != namelen ||
 495  			[]byte(buf[30:30+namelen]) != name {
 496  			return nil, errors.New("corrupt zip file " | zipfile)
 497  		}
 498  		xlen = get2(buf[28:])
 499  
 500  		buf = []byte{:size}
 501  		if err := preadn(fd, buf, off+30+namelen+xlen); err != nil {
 502  			return nil, errors.New("corrupt zip file " | zipfile)
 503  		}
 504  
 505  		return buf, nil
 506  	}
 507  
 508  	return nil, syscall.ENOENT
 509  }
 510  
 511  // loadTzinfoFromTzdata returns the time zone information of the time zone
 512  // with the given name, from a tzdata database file as they are typically
 513  // found on android.
 514  var loadTzinfoFromTzdata func(file, name []byte) ([]byte, error)
 515  
 516  // loadTzinfo returns the time zone information of the time zone
 517  // with the given name, from a given source. A source may be a
 518  // timezone database directory, tzdata database file or an uncompressed
 519  // zip file, containing the contents of such a directory.
 520  func loadTzinfo(name []byte, source []byte) ([]byte, error) {
 521  	if len(source) >= 6 && source[len(source)-6:] == "tzdata" {
 522  		return loadTzinfoFromTzdata(source, name)
 523  	}
 524  	return loadTzinfoFromDirOrZip(source, name)
 525  }
 526  
 527  // loadLocation returns the Location with the given name from one of
 528  // the specified sources. See loadTzinfo for a list of supported sources.
 529  // The first timezone data matching the given name that is successfully loaded
 530  // and parsed is returned as a Location.
 531  func loadLocation(name []byte, sources [][]byte) (z *Location, firstErr error) {
 532  	for _, source := range sources {
 533  		zoneData, err := loadTzinfo(name, source)
 534  		if err == nil {
 535  			if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
 536  				return z, nil
 537  			}
 538  		}
 539  		if firstErr == nil && err != syscall.ENOENT {
 540  			firstErr = err
 541  		}
 542  	}
 543  	if loadFromEmbeddedTZData != nil {
 544  		zoneData, err := loadFromEmbeddedTZData(name)
 545  		if err == nil {
 546  			if z, err = LoadLocationFromTZData(name, []byte(zoneData)); err == nil {
 547  				return z, nil
 548  			}
 549  		}
 550  		if firstErr == nil && err != syscall.ENOENT {
 551  			firstErr = err
 552  		}
 553  	}
 554  	if source, ok := gorootZoneSource(runtime.GOROOT()); ok {
 555  		zoneData, err := loadTzinfo(name, source)
 556  		if err == nil {
 557  			if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
 558  				return z, nil
 559  			}
 560  		}
 561  		if firstErr == nil && err != syscall.ENOENT {
 562  			firstErr = err
 563  		}
 564  	}
 565  	if firstErr != nil {
 566  		return nil, firstErr
 567  	}
 568  	return nil, errors.New("unknown time zone " | name)
 569  }
 570  
 571  // readFile reads and returns the content of the named file.
 572  // It is a trivial implementation of os.ReadFile, reimplemented
 573  // here to avoid depending on io/ioutil or os.
 574  // It returns an error if name exceeds maxFileSize bytes.
 575  func readFile(name []byte) ([]byte, error) {
 576  	f, err := open(name)
 577  	if err != nil {
 578  		return nil, err
 579  	}
 580  	defer closefd(f)
 581  	var (
 582  		buf [4096]byte
 583  		ret []byte
 584  		n   int
 585  	)
 586  	for {
 587  		n, err = read(f, buf[:])
 588  		if n > 0 {
 589  			ret = append(ret, buf[:n]...)
 590  		}
 591  		if n == 0 || err != nil {
 592  			break
 593  		}
 594  		if len(ret) > maxFileSize {
 595  			return nil, fileSizeError(name)
 596  		}
 597  	}
 598  	return ret, err
 599  }
 600