format = formatUSTAR | formatPAX | formatGNU
        paxHdrs = make(map[string]string)
 
-       verifyString := func(s string, size int, gnuLong bool, paxKey string) {
-
+       verifyString := func(s string, size int, paxKey string) {
                // NUL-terminator is optional for path and linkpath.
                // Technically, it is required for uname and gname,
                // but neither GNU nor BSD tar checks for it.
                tooLong := len(s) > size
-               if !isASCII(s) || (tooLong && !gnuLong) {
-                       // TODO(dsnet): GNU supports UTF-8 (without NUL) for strings.
+               allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath
+               if hasNUL(s) || (tooLong && !allowLongGNU) {
                        format &^= formatGNU // No GNU
                }
                if !isASCII(s) || tooLong {
                        }
                }
        }
-       verifyTime := func(ts time.Time, size int, ustarField bool, paxKey string) {
+       verifyTime := func(ts time.Time, size int, paxKey string) {
                if ts.IsZero() {
                        return // Always okay
                }
                needsNano := ts.Nanosecond() != 0
+               hasFieldUSTAR := paxKey == paxMtime
                if !fitsInBase256(size, ts.Unix()) || needsNano {
                        format &^= formatGNU // No GNU
                }
-               if !fitsInOctal(size, ts.Unix()) || needsNano || !ustarField {
+               if !fitsInOctal(size, ts.Unix()) || needsNano || !hasFieldUSTAR {
                        format &^= formatUSTAR // No USTAR
                        if paxKey == paxNone {
                                format &^= formatPAX // No PAX
                }
        }
 
-       // TODO(dsnet): Add GNU long name support.
-       const supportGNULong = false
-
        var blk block
        v7 := blk.V7()
        ustar := blk.USTAR()
        gnu := blk.GNU()
-       verifyString(h.Name, len(v7.Name()), supportGNULong, paxPath)
-       verifyString(h.Linkname, len(v7.LinkName()), supportGNULong, paxLinkpath)
-       verifyString(h.Uname, len(ustar.UserName()), false, paxUname)
-       verifyString(h.Gname, len(ustar.GroupName()), false, paxGname)
+       verifyString(h.Name, len(v7.Name()), paxPath)
+       verifyString(h.Linkname, len(v7.LinkName()), paxLinkpath)
+       verifyString(h.Uname, len(ustar.UserName()), paxUname)
+       verifyString(h.Gname, len(ustar.GroupName()), paxGname)
        verifyNumeric(h.Mode, len(v7.Mode()), paxNone)
        verifyNumeric(int64(h.Uid), len(v7.UID()), paxUid)
        verifyNumeric(int64(h.Gid), len(v7.GID()), paxGid)
        verifyNumeric(h.Size, len(v7.Size()), paxSize)
        verifyNumeric(h.Devmajor, len(ustar.DevMajor()), paxNone)
        verifyNumeric(h.Devminor, len(ustar.DevMinor()), paxNone)
-       verifyTime(h.ModTime, len(v7.ModTime()), true, paxMtime)
-       verifyTime(h.AccessTime, len(gnu.AccessTime()), false, paxAtime)
-       verifyTime(h.ChangeTime, len(gnu.ChangeTime()), false, paxCtime)
+       verifyTime(h.ModTime, len(v7.ModTime()), paxMtime)
+       verifyTime(h.AccessTime, len(gnu.AccessTime()), paxAtime)
+       verifyTime(h.ChangeTime, len(gnu.ChangeTime()), paxCtime)
 
        if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
                return formatUnknown, nil
 
                        Uname:    "rawr",
                        Gname:    "dsnet",
                }},
+       }, {
+               // This archive was generated by Writer but is readable by both
+               // GNU and BSD tar utilities.
+               // The archive generated by GNU is nearly byte-for-byte identical
+               // to the Go version except the Go version sets a negative Devminor
+               // just to force the GNU format.
+               file: "testdata/gnu-utf8.tar",
+               headers: []*Header{{
+                       Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
+                       Mode: 0644,
+                       Uid:  1000, Gid: 1000,
+                       ModTime:  time.Unix(0, 0),
+                       Typeflag: '0',
+                       Uname:    "☺",
+                       Gname:    "⚹",
+                       Devminor: -1,
+               }},
+       }, {
+               // This archive was generated by Writer but is readable by both
+               // GNU and BSD tar utilities.
+               // The archive generated by GNU is nearly byte-for-byte identical
+               // to the Go version except the Go version sets a negative Devminor
+               // just to force the GNU format.
+               file: "testdata/gnu-not-utf8.tar",
+               headers: []*Header{{
+                       Name:     "hi\x80\x81\x82\x83bye",
+                       Mode:     0644,
+                       Uid:      1000,
+                       Gid:      1000,
+                       ModTime:  time.Unix(0, 0),
+                       Typeflag: '0',
+                       Uname:    "rawr",
+                       Gname:    "dsnet",
+                       Devminor: -1,
+               }},
        }, {
                // BSD tar v3.1.2 and GNU tar v1.27.1 both rejects PAX records
                // with NULs in the key.
 
        "time"
 )
 
+// hasNUL reports whether the NUL character exists within s.
+func hasNUL(s string) bool {
+       return strings.IndexByte(s, 0) >= 0
+}
+
 // isASCII reports whether the input is an ASCII C-style string.
 func isASCII(s string) bool {
        for _, c := range s {
        if len(s) > len(b) {
                f.err = ErrFieldTooLong
        }
-       s = toASCII(s) // TODO(dsnet): Remove this for UTF-8 support in GNU format
        copy(b, s)
        if len(s) < len(b) {
                b[len(s)] = 0
        }
        switch k {
        case paxPath, paxLinkpath, paxUname, paxGname:
-               return strings.IndexByte(v, 0) < 0
+               return !hasNUL(v)
        default:
-               return strings.IndexByte(k, 0) < 0
+               return !hasNUL(k)
        }
 }
 
                formats: formatUnknown,
        }, {
                header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
-               formats: formatUnknown,
+               formats: formatGNU,
        }, {
                header:  &Header{Size: math.MaxInt64},
                paxHdrs: map[string]string{paxSize: "9223372036854775807"},
        }, {
                header:  &Header{Name: strings.Repeat("a", nameSize)},
                formats: formatUSTAR | formatPAX | formatGNU,
+       }, {
+               header:  &Header{Name: strings.Repeat("a", nameSize+1)},
+               paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
+               formats: formatPAX | formatGNU,
        }, {
                header:  &Header{Linkname: "用戶名"},
                paxHdrs: map[string]string{paxLinkpath: "用戶名"},
-               formats: formatPAX,
+               formats: formatPAX | formatGNU,
        }, {
                header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
                paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
 
        }
 
        // Pack the main header.
-       var f formatter
-       blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal)
+       var f formatter // Ignore errors since they are expected
+       fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
+       blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal)
        blk.SetFormat(formatPAX)
-       if f.err != nil && len(paxHdrs) == 0 {
-               return f.err // Should never happen, otherwise PAX headers would be used
-       }
        return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
 }
 
        // TODO(dsnet): Support writing sparse files.
        // See https://golang.org/issue/13548
 
-       // TODO(dsnet): Support long filenames (with UTF-8) support.
+       // Use long-link files if Name or Linkname exceeds the field size.
+       const longName = "././@LongLink"
+       if len(hdr.Name) > nameSize {
+               data := hdr.Name + "\x00"
+               if err := tw.writeRawFile(longName, data, TypeGNULongName, formatGNU); err != nil {
+                       return err
+               }
+       }
+       if len(hdr.Linkname) > nameSize {
+               data := hdr.Linkname + "\x00"
+               if err := tw.writeRawFile(longName, data, TypeGNULongLink, formatGNU); err != nil {
+                       return err
+               }
+       }
 
        // Pack the main header.
-       var f formatter
+       var f formatter // Ignore errors since they are expected
        blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric)
        if !hdr.AccessTime.IsZero() {
                f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix())
                f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix())
        }
        blk.SetFormat(formatGNU)
-       if f.err != nil {
-               return f.err // Should never happen since header is validated
-       }
        return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
 }
 
 
                        },
                }},
                err: ErrHeader,
+       }, {
+               file: "testdata/gnu-utf8.tar",
+               entries: []*entry{{
+                       header: &Header{
+                               Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
+                               Mode: 0644,
+                               Uid:  1000, Gid: 1000,
+                               ModTime:  time.Unix(0, 0),
+                               Typeflag: '0',
+                               Uname:    "☺",
+                               Gname:    "⚹",
+                               Devminor: -1, // Force use of GNU format
+                       },
+               }},
+       }, {
+               file: "testdata/gnu-not-utf8.tar",
+               entries: []*entry{{
+                       header: &Header{
+                               Name:     "hi\x80\x81\x82\x83bye",
+                               Mode:     0644,
+                               Uid:      1000,
+                               Gid:      1000,
+                               ModTime:  time.Unix(0, 0),
+                               Typeflag: '0',
+                               Uname:    "rawr",
+                               Gname:    "dsnet",
+                               Devminor: -1, // Force use of GNU format
+                       },
+               }},
        }}
 
        for _, v := range vectors {