diff --git a/cmn/api.go b/cmn/api.go index a6dbf0a199e..49f7ec9e739 100644 --- a/cmn/api.go +++ b/cmn/api.go @@ -334,6 +334,7 @@ func (s AllBsummResults) Finalize(dsize map[string]uint64, testingEnv bool) { var totalDisksSize uint64 for _, tsiz := range dsize { totalDisksSize += tsiz + // TODO -- FIXME: (local-playground + losetup, etc.) if testingEnv { break } @@ -342,7 +343,9 @@ func (s AllBsummResults) Finalize(dsize map[string]uint64, testingEnv bool) { if summ.ObjCount.Present > 0 { summ.ObjSize.Avg = int64(cos.DivRoundU64(summ.TotalSize.PresentObjs, summ.ObjCount.Present)) } - summ.UsedPct = cos.DivRoundU64(summ.TotalSize.OnDisk*100, totalDisksSize) + if totalDisksSize > 0 { + summ.UsedPct = cos.DivRoundU64(summ.TotalSize.OnDisk*100, totalDisksSize) + } } } diff --git a/cmn/bck.go b/cmn/bck.go index 9a7e0cc45c0..df2041e61c9 100644 --- a/cmn/bck.go +++ b/cmn/bck.go @@ -169,14 +169,17 @@ func (n Ns) _copy(b []byte, l int) int { return off } -func (n Ns) validate() error { +func (n Ns) validate() (err error) { if n.IsGlobal() { return nil } - if cos.IsAlphaNice(n.UUID) && cos.IsAlphaPlus(n.Name) { - return nil + if err = cos.CheckAlphaPlus(n.Name, "namepace"); err == nil { + if cos.IsAlphaNice(n.UUID) { + return nil + } + return fmt.Errorf(fmtErrNamespace, n.UUID, n.Name) } - return fmt.Errorf(fmtErrNamespace, n.UUID, n.Name) + return err } func (n Ns) contains(other Ns) bool { @@ -270,17 +273,14 @@ func (b *Bck) Validate() (err error) { return } -func (b *Bck) ValidateName() (err error) { +func (b *Bck) ValidateName() error { if b.Name == "" { return errors.New("bucket name is missing") } if b.Name == "." { return fmt.Errorf(fmtErrBckName, b.Name) } - if !cos.IsAlphaPlus(b.Name) { - err = fmt.Errorf(fmtErrBckName, b.Name) - } - return + return cos.CheckAlphaPlus(b.Name, "bucket name") } // ditto diff --git a/cmn/config.go b/cmn/config.go index 39160856be9..f0a45fa5c7e 100644 --- a/cmn/config.go +++ b/cmn/config.go @@ -1840,9 +1840,9 @@ func ValidateRemAlias(alias string) (err error) { return fmt.Errorf("cannot use %q as an alias", apc.QparamWhat) } if len(alias) < 2 { - err = fmt.Errorf("alias %q is too short: must have at least 2 letters", alias) - } else if !cos.IsAlphaPlus(alias) { - err = fmt.Errorf("alias %q is invalid: use only letters, numbers, dashes (-), and underscores (_)", alias) + err = fmt.Errorf(apc.RemAIS+" alias %q is too short: must have at least 2 letters", alias) + } else { + err = cos.CheckAlphaPlus(alias, apc.RemAIS+" alias") } - return + return err } diff --git a/cmn/cos/io.go b/cmn/cos/io.go index 1e426973a34..d0892d27522 100644 --- a/cmn/cos/io.go +++ b/cmn/cos/io.go @@ -367,14 +367,17 @@ func (f *SectionHandle) Read(buf []byte) (n int, err error) { } // either buffer is full or end of padding is reached. Nothing to read - if fromPad == 0 { + if fromPad <= 0 { + debug.Assert(fromPad == 0) return n, io.EOF } - // the number of remained bytes in padding is enough to complete read request + // the number of remaining bytes in padding is enough to complete read request for idx := n; idx < n+int(fromPad); idx++ { buf[idx] = 0 } + + debug.Assert(n < math.MaxInt-int(fromPad)) n += int(fromPad) // check for integer overflow diff --git a/cmn/cos/uuid.go b/cmn/cos/uuid.go index 6ddaa68829b..404f3fb83d4 100644 --- a/cmn/cos/uuid.go +++ b/cmn/cos/uuid.go @@ -5,6 +5,7 @@ package cos import ( + "errors" "fmt" "strconv" @@ -13,22 +14,30 @@ import ( "github.com/teris-io/shortid" ) -const LenShortID = 9 // UUID length, as per https://github.com/teris-io/shortid#id-length - const ( // Alphabet for generating UUIDs similar to the shortid.DEFAULT_ABC // NOTE: len(uuidABC) > 0x3f - see GenTie() uuidABC = "-5nZJDft6LuzsjGNpPwY7rQa39vehq4i1cV2FROo8yHSlC0BUEdWbIxMmTgKXAk_" +) - lenDaemonID = 8 // via cryptographic rand - lenTooLongID = 32 // suspiciously long - +const ( + LenShortID = 9 // UUID length, as per https://github.com/teris-io/shortid#id-length + lenDaemonID = 8 // min length, via cryptographic rand lenK8sProxyID = 13 + + // NOTE: cannot be smaller than any of the valid max lengths - see above + tooLongID = 32 +) + +// bucket name, remais alias +const ( + tooLongName = 64 ) const ( - OnlyNice = "may only contain letters, numbers, dashes (-), underscores (_), and dots (.)" - OnlyPlus = OnlyNice + ", and dots (.)" + mayOnlyContain = "may only contain letters, numbers, dashes (-), underscores (_)" + OnlyNice = "must be less than 32 characters and " + mayOnlyContain // NOTE tooLongID + OnlyPlus = mayOnlyContain + ", and dots (.)" ) var ( @@ -79,25 +88,22 @@ func IsValidUUID(uuid string) bool { return len(uuid) >= LenShortID && IsAlphaNice(uuid) } -func ValidateNiceID(id string, minlen int, tag string) (err error) { - if len(id) < minlen { - return fmt.Errorf("%s %q is too short", tag, id) - } - if len(id) >= lenTooLongID { - return fmt.Errorf("%s %q is too long", tag, id) - } - if !IsAlphaNice(id) { - err = fmt.Errorf("%s %q is invalid: must start with a letter and can only contain [A-Za-z0-9-_]", tag, id) - } - return -} - // // Daemon ID // func GenDaemonID() string { return CryptoRandS(lenDaemonID) } +func ValidateDaemonID(id string) error { + if len(id) < lenDaemonID { + return fmt.Errorf("node ID %q is too short", id) + } + if !IsAlphaNice(id) { + return fmt.Errorf("node ID %q is invalid: must start with a letter, "+OnlyNice, id) + } + return nil +} + func HashK8sProxyID(nodeName string) (pid string) { digest := xxhash.Checksum64S(UnsafeB(nodeName), MLCG32) pid = strconv.FormatUint(digest, 36) @@ -110,8 +116,6 @@ func HashK8sProxyID(nodeName string) (pid string) { return pid } -func ValidateDaemonID(id string) error { return ValidateNiceID(id, lenDaemonID, "node ID") } - // (when config.TestingEnv) func GenTestingDaemonID(suffix string) string { l := max(lenDaemonID-len(suffix), 3) @@ -126,12 +130,15 @@ func isAlpha(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } -// letters and numbers w/ '-' and '_' permitted with limitations (below) -// (see OnlyNice above) +// letters and numbers w/ '-' and '_' permitted with limitations (see OnlyNice const) func IsAlphaNice(s string) bool { l := len(s) - for i, c := range s { - if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { + if l > tooLongID { + return false + } + for i := range l { + c := s[i] + if isAlpha(c) || (c >= '0' && c <= '9') { continue } if c != '-' && c != '_' { @@ -145,21 +152,25 @@ func IsAlphaNice(s string) bool { } // alpha-numeric++ including letters, numbers, dashes (-), and underscores (_) -// period (.) is allowed except for '..' -// (see OnlyPlus above) -func IsAlphaPlus(s string) bool { - for i, c := range s { - if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' { +// period (.) is allowed except for '..' (OnlyPlus const) +func CheckAlphaPlus(s, tag string) error { + l := len(s) + if l > tooLongName { + return fmt.Errorf("%s is too long: %d > %d(max length)", tag, l, tooLongName) + } + for i := range l { + c := s[i] + if isAlpha(c) || (c >= '0' && c <= '9') || c == '-' || c == '_' { continue } if c != '.' { - return false + return errors.New(tag + " is invalid: " + OnlyPlus) } - if i < len(s)-1 && s[i+1] == '.' { - return false + if i < l-1 && s[i+1] == '.' { + return errors.New(tag + " is invalid: " + OnlyPlus) } } - return true + return nil } // 3-letter tie breaker (fast) diff --git a/cmn/err.go b/cmn/err.go index a96d9b8f6af..8650a528282 100644 --- a/cmn/err.go +++ b/cmn/err.go @@ -990,12 +990,13 @@ func InitErrHTTP(r *http.Request, err error, ecode int) (e *ErrHTTP) { } func (e *ErrHTTP) init(r *http.Request, err error, ecode int) { + const maxlen = 100 e.Status = http.StatusBadRequest if ecode != 0 { e.Status = ecode } tcode := fmt.Sprintf("%T", err) - if i := strings.Index(tcode, "."); i > 0 { + if i := strings.Index(tcode, "."); i > 0 && i < maxlen && len(tcode)-i < maxlen { if pkg := tcode[:i]; pkg != "*errors" && pkg != "errors" { e.TypeCode = tcode[i+1:] } diff --git a/cmn/http.go b/cmn/http.go index 66bb457e542..90ae40094c4 100644 --- a/cmn/http.go +++ b/cmn/http.go @@ -90,6 +90,9 @@ func MakeRangeHdr(start, length int64) string { // - splitAfter == true: strings.Split() the entire path; // - splitAfter == false: strings.SplitN(len(itemsPresent)+itemsAfter) // Returns all items that follow the specified `items`. + +const maxItems = 1000 + func ParseURL(path string, itemsPresent []string, itemsAfter int, splitAfter bool) ([]string, error) { var ( split []string @@ -99,7 +102,7 @@ func ParseURL(path string, itemsPresent []string, itemsAfter int, splitAfter boo path = path[1:] // remove leading slash } if splitAfter { - split = strings.Split(path, "/") + split = strings.SplitN(path, "/", maxItems) } else { split = strings.SplitN(path, "/", l+max(1, itemsAfter)) } diff --git a/ios/fsutils_linux.go b/ios/fsutils_linux.go index 79c3f3f8674..7f33c5bfaf9 100644 --- a/ios/fsutils_linux.go +++ b/ios/fsutils_linux.go @@ -15,6 +15,7 @@ import ( "unsafe" "github.com/NVIDIA/aistore/cmn/cos" + "github.com/NVIDIA/aistore/cmn/debug" "golang.org/x/sys/unix" ) @@ -135,12 +136,14 @@ func nameHasPrefix(name *[256]int8, s string) bool { } func GetFSStats(path string) (blocks, bavail uint64, bsize int64, err error) { - var fsStats unix.Statfs_t - fsStats, err = getFSStats(path) + var statfs unix.Statfs_t + statfs, err = getFSStats(path) if err != nil { return } - return fsStats.Blocks, fsStats.Bavail, fsStats.Bsize, nil + debug.Assert(statfs.Blocks > 0) + debug.Assert(statfs.Bsize > 0) + return statfs.Blocks, statfs.Bavail, statfs.Bsize, nil } func GetATime(osfi os.FileInfo) time.Time { diff --git a/xact/xs/nsumm.go b/xact/xs/nsumm.go index 215c3325771..a52ebbae1fa 100644 --- a/xact/xs/nsumm.go +++ b/xact/xs/nsumm.go @@ -79,6 +79,11 @@ func newSumm(p *nsummFactory) (r *XactNsumm, err error) { r = &XactNsumm{p: p} r.totalDiskSize = fs.GetDiskSize() + if r.totalDiskSize < cos.KiB { + err = fmt.Errorf("invalid disk size (%d bytes)", r.totalDiskSize) + debug.AssertNoErr(err) + return nil, err + } listRemote := p.Bck.IsCloud() && !p.msg.ObjCached if listRemote {