diff --git a/bitmask/bitmasks.go b/bitmask/bitmasks.go new file mode 100644 index 0000000..2d3d868 --- /dev/null +++ b/bitmask/bitmasks.go @@ -0,0 +1,46 @@ +package bitmask + +// MaskBit is a flag container. +type MaskBit uint8 + +/* + NewMaskBit is a convenience function. + It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly. + It is highly recommended to set this default as a "None" flag (separate from your iotas!) + as shown in the example. +*/ +func NewMaskBit() (m *MaskBit) { + + m = new(MaskBit) + + return +} + +// HasFlag is true if m has MaskBit flag set/enabled. +func (m *MaskBit) HasFlag(flag MaskBit) (r bool) { + + var b MaskBit = *m + + if b&flag != 0 { + r = true + } + return +} + +// AddFlag adds MaskBit flag to m. +func (m *MaskBit) AddFlag(flag MaskBit) { + *m |= flag + return +} + +// ClearFlag removes MaskBit flag from m. +func (m *MaskBit) ClearFlag(flag MaskBit) { + *m &= flag + return +} + +// ToggleFlag switches MaskBit flag in m to its inverse; if true, it is now false and vice versa. +func (m *MaskBit) ToggleFlag(flag MaskBit) { + *m ^= flag + return +} diff --git a/bitmask/doc.go b/bitmask/doc.go new file mode 100644 index 0000000..bba44e0 --- /dev/null +++ b/bitmask/doc.go @@ -0,0 +1,45 @@ +/* +Package bitmask handles a flag-like opt/bitmask system. + +See https://yourbasic.org/golang/bitmask-flag-set-clear/ for more information. + +To use this, set constants like thus: + + package main + + import ( + "r00t2.io/goutils/bitmask" + ) + + const OPTNONE types.MaskBit = 0 + const ( + OPT1 types.MaskBit = 1 << iota + OPT2 + OPT3 + // ... + ) + + var MyMask *MaskBit + + func main() { + MyMask = types.NewMaskBit + + MyMask.AddFlag(OPT1) + MyMask.AddFlag(OPT3) + + _ = MyMask + } + +This would return true: + + MyMask.HasFlag(OPT1) + +As would this: + + MyMask.HasFlag(OPT3) + +But this would return false: + + MyMask.HasFlag(OPT2) +*/ +package bitmask diff --git a/logging/TODO b/logging/TODO index 319b432..7fca130 100644 --- a/logging/TODO +++ b/logging/TODO @@ -1,3 +1,11 @@ - Implement code line/func/etc. (only for debug?): https://stackoverflow.com/a/24809646 https://golang.org/pkg/runtime/#Caller + +- Support simultaneous writing to multiple Loggers. + +- Suport remote loggers? (eventlog, syslog, systemd) + +- DOCS. + +- Unit/Integration tests. diff --git a/logging/consts.go b/logging/consts.go index 6f5c8d8..5c45941 100644 --- a/logging/consts.go +++ b/logging/consts.go @@ -1,29 +1,11 @@ package logging import ( - `log/syslog` `os` - - `r00t2.io/goutils/types` ) const ( - devlog string = "/dev/log" logPerm os.FileMode = 0600 logPrefix string = "GOLANG PROGRAM" appendFlags int = os.O_APPEND|os.O_CREATE|os.O_WRONLY - syslogFacility syslog.Priority = syslog.LOG_USER -) - -// Flags for logger configuration -const ( - LogUndefined types.MaskBit = 1 << iota - LogJournald - LogSyslog - LogFile - LogStdout -) - -var ( - defLogPaths = []string{"/var/log/golang/program.log", "~/.local/log/golang/program.log"} ) diff --git a/logging/consts_linux.go b/logging/consts_linux.go new file mode 100644 index 0000000..223faf3 --- /dev/null +++ b/logging/consts_linux.go @@ -0,0 +1,28 @@ +package logging + +import ( + `log/syslog` + + `r00t2.io/goutils/bitmask` +) + +const ( + devlog string = "/dev/log" + syslogFacility syslog.Priority = syslog.LOG_USER +) + +// Flags for logger configuration +const ( + LogUndefined bitmask.MaskBit = 1 << iota + LogJournald + LogSyslog + LogFile + LogStdout +) + +var ( + defLogPaths = []string{ + "/var/log/golang/program.log", + "~/.local/log/golang/program.log", + } +) diff --git a/logging/consts_windows.go b/logging/consts_windows.go new file mode 100644 index 0000000..22804f7 --- /dev/null +++ b/logging/consts_windows.go @@ -0,0 +1,48 @@ +package logging + +import ( + `os` + `path/filepath` + `regexp` +) + +// Flags for logger configuration +const ( + LogUndefined types.MaskBit = 1 << iota + LogWinLogger + LogFile + LogStdout +) + +var ( + defLogPaths = []string{ + filepath.Join(os.Getenv("ALLUSERSPROFILE"), "golang", "program.log"), // C:\ProgramData\log\golang\program.log + filepath.Join(os.Getenv("LOCALAPPDATA"), "log", "golang", "program.log"), // C:\Users\\AppData\Local\log\golang\program.log + } +) + +var ptrnSourceExists *regexp.Regexp = regexp.MustCompile(`registry\skey\salready\sexists$`) + +// Default WinEventID +var DefaultEventID *WinEventID = &WinEventID{ + Alert: EventAlert, + Crit: EventCrit, + Debug: EventDebug, + Emerg: EventEmerg, + Err: EventErr, + Info: EventInfo, + Notice: EventNotice, + Warning: EventWarning, +} + +// Default Event IDs for WinEventID. +const ( + EventAlert uint32 = 1 << iota + EventCrit + EventDebug + EventEmerg + EventErr + EventInfo + EventNotice + EventWarning +) diff --git a/logging/funcs.go b/logging/funcs.go index 80ae0e0..c10edb9 100644 --- a/logging/funcs.go +++ b/logging/funcs.go @@ -1,126 +1,9 @@ package logging import ( - native "log" "os" - "path" - - "r00t2.io/goutils/types" - - sysd "github.com/coreos/go-systemd/journal" - "r00t2.io/sysutils/paths" ) -var ( - _ = sysd.Enabled() - _ = native.Logger{} - _ = os.Interrupt -) - -// GetLogger returns an instance of Logger that best suits your system's capabilities. -// If enableDebug is true, debug messages (which according to your program may or may not contain sensitive data) are rendered and written. -// If prefix is "\x00" (a null byte), then the default logging prefix will be used. If anything else, even an empty string, -// is specified then that will be used instead for the prefix. -// logpaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, -// it will be used (assuming you have no higher-level loggers available). Only the first logpaths entry that "works" will be used, later entries will be ignored. -func GetLogger(enableDebug bool, prefix string, logpaths ...string) (logger Logger, err error) { - - var logPath string - var logflags types.MaskBit - - // Configure system-supported logger(s). - if sysd.Enabled() { - // Use Journald. - logflags.AddFlag(LogJournald) - } else { - // If we can detect syslog, use that. If not, try to use a file logger (+ stdout). - // Last ditch, stdout. - var hasSyslog bool - var stat os.FileInfo - var devlogPath string = devlog - - if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil { - return - } - - if hasSyslog && !stat.Mode().IsRegular() { - logflags.AddFlag(LogSyslog) - } else { - var exists bool - var success bool - var ckLogPaths []string - logflags.AddFlag(LogStdout) - ckLogPaths = defLogPaths - if logpaths != nil { - ckLogPaths = logpaths - } - for _, p := range ckLogPaths { - if exists, _ = paths.RealPathExists(&p); exists { - if success, err = testOpen(p); err != nil { - continue - } else if !success { - continue - } - logflags.AddFlag(LogFile) - logPath = p - break - } else { - dirPath := path.Dir(p) - if err = paths.MakeDirIfNotExist(&dirPath); err != nil { - continue - } - if success, err = testOpen(p); err != nil { - continue - } else if !success { - continue - } - logflags.AddFlag(LogFile) - logPath = p - break - } - } - } - } - - if logflags.HasFlag(LogJournald) { - logger = &SystemDLogger{ - Prefix: logPrefix, - EnableDebug: enableDebug, - } - } else { - if logflags.HasFlag(LogSyslog) { - logger = &SyslogLogger{ - Prefix: logPrefix, - EnableDebug: enableDebug, - } - } else { - if logflags.HasFlag(LogFile) { - logger = &FileLogger{ - StdLogger: StdLogger{ - Prefix: logPrefix, - EnableDebug: enableDebug, - }, - Path: logPath, - } - } else { - logger = &StdLogger{ - Prefix: logPrefix, - EnableDebug: enableDebug, - } - } - } - } - - logger.Setup() - if prefix != "\x00" { - logger.SetPrefix(prefix) - } - - logger.Info("logger initialized of type %T with prefix %v", logger, logger.GetPrefix()) - - return -} - func testOpen(path string) (success bool, err error) { var f *os.File diff --git a/logging/funcs_linux.go b/logging/funcs_linux.go new file mode 100644 index 0000000..bc6d13c --- /dev/null +++ b/logging/funcs_linux.go @@ -0,0 +1,123 @@ +package logging + +import ( + native `log` + `os` + `path` + + sysd `github.com/coreos/go-systemd/journal` + `r00t2.io/sysutils/paths` +) + +var ( + _ = sysd.Enabled() + _ = native.Logger{} + _ = os.Interrupt +) + +/* + GetLogger returns an instance of Logger that best suits your system's capabilities. + If enableDebug is true, debug messages (which according to your program may or may not contain sensitive data) are rendered and written. + If prefix is "\x00" (a null byte), then the default logging prefix will be used. If anything else, even an empty string, + is specified then that will be used instead for the prefix. + logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, + it will be used (assuming you have no higher-level loggers available). Only the first logPaths entry that "works" will be used, later entries will be ignored. +*/ +func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logger, err error) { + + var logPath string + var logFlags types.MaskBit + + // Configure system-supported logger(s). + if sysd.Enabled() { + // Use Journald. + logFlags.AddFlag(LogJournald) + } else { + // If we can detect syslog, use that. If not, try to use a file logger (+ stdout). + // Last ditch, stdout. + var hasSyslog bool + var stat os.FileInfo + var devlogPath string = devlog + + if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil { + return + } + + if hasSyslog && !stat.Mode().IsRegular() { + logFlags.AddFlag(LogSyslog) + } else { + var exists bool + var success bool + var ckLogPaths []string + + logFlags.AddFlag(LogStdout) + ckLogPaths = defLogPaths + if logPaths != nil { + ckLogPaths = logPaths + } + for _, p := range ckLogPaths { + if exists, _ = paths.RealPathExists(&p); exists { + if success, err = testOpen(p); err != nil { + continue + } else if !success { + continue + } + logFlags.AddFlag(LogFile) + logPath = p + break + } else { + dirPath := path.Dir(p) + if err = paths.MakeDirIfNotExist(&dirPath); err != nil { + continue + } + if success, err = testOpen(p); err != nil { + continue + } else if !success { + continue + } + logFlags.AddFlag(LogFile) + logPath = p + break + } + } + } + } + + if logFlags.HasFlag(LogJournald) { + logger = &SystemDLogger{ + Prefix: logPrefix, + EnableDebug: enableDebug, + } + } else { + if logFlags.HasFlag(LogSyslog) { + logger = &SyslogLogger{ + Prefix: logPrefix, + EnableDebug: enableDebug, + } + } else { + if logFlags.HasFlag(LogFile) { + logger = &FileLogger{ + StdLogger: StdLogger{ + Prefix: logPrefix, + EnableDebug: enableDebug, + }, + Path: logPath, + } + } else { + logger = &StdLogger{ + Prefix: logPrefix, + EnableDebug: enableDebug, + } + } + } + } + + logger.Setup() + if prefix != "\x00" { + logger.SetPrefix(prefix) + } + + logger.Info("logger initialized of type %T with prefix %v", logger, logger.GetPrefix()) + + return +} diff --git a/logging/funcs_std.go b/logging/funcs_std.go index a5af2ad..f18f972 100644 --- a/logging/funcs_std.go +++ b/logging/funcs_std.go @@ -27,8 +27,11 @@ func (l *StdLogger) SetPrefix(prefix string) { l.Logger.SetPrefix(prefix) } -func (l *StdLogger) GetPrefix() string { - return l.Prefix +func (l *StdLogger) GetPrefix() (prefix string) { + + prefix = l.Prefix + + return } func (l *StdLogger) Alert(s string, v ...interface{}) (err error) { diff --git a/logging/funcs_sysd.go b/logging/funcs_sysd_linux.go similarity index 96% rename from logging/funcs_sysd.go rename to logging/funcs_sysd_linux.go index 493bee3..f39cf15 100644 --- a/logging/funcs_sysd.go +++ b/logging/funcs_sysd_linux.go @@ -29,8 +29,11 @@ func (l *SystemDLogger) SetPrefix(prefix string) { l.Prefix = prefix } -func (l *SystemDLogger) GetPrefix() string { - return l.Prefix +func (l *SystemDLogger) GetPrefix() (prefix string) { + + prefix = l.Prefix + + return } func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) { @@ -158,6 +161,7 @@ func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) { } func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) { + // TODO: implement code line, etc. // https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html // CODE_FILE=, CODE_LINE=, CODE_FUNC= diff --git a/logging/funcs_syslog.go b/logging/funcs_syslog_linux.go similarity index 98% rename from logging/funcs_syslog.go rename to logging/funcs_syslog_linux.go index ceaed18..fcc50b7 100644 --- a/logging/funcs_syslog.go +++ b/logging/funcs_syslog_linux.go @@ -58,8 +58,11 @@ func (l *SyslogLogger) SetPrefix(prefix string) { l.Setup() } -func (l *SyslogLogger) GetPrefix() string { - return l.Prefix +func (l *SyslogLogger) GetPrefix() (prefix string) { + + prefix = l.Prefix + + return } func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) { diff --git a/logging/funcs_windows.go b/logging/funcs_windows.go new file mode 100644 index 0000000..13b9ac2 --- /dev/null +++ b/logging/funcs_windows.go @@ -0,0 +1,100 @@ +package logging + +import ( + `errors` + `strings` +) + +/* + GetLogger returns an instance of Logger that best suits your system's capabilities. Note that this is a VERY generalized interface to the Windows Event Log. + If you require more robust logging capabilities (e.g. custom event IDs per uniquely identifiable event), + you will want to set up your own logger (golang.org/x/sys/windows/svc/eventlog). + If enableDebug is true, debug messages (which according to your program may or may not contain sensitive data) are rendered and written (otherwise they are ignored). + A blank source will return an error as it's used as the source name. Other functions, struct fields, etc. will refer to this as the "prefix". + A pointer to a WinEventID struct may be specified for eventIDs to map extended logging levels (as Windows only supports three levels natively). + If it is nil, a default one (DefaultEventID) will be used. + logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, + it will be used (assuming you have no higher-level loggers available). + Only the first logPaths entry that "works" will be used, later entries will be ignored. + Currently this will almost always return a WinLogger until multiple logging destination support is added. +*/ +func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths ...string) (logger Logger, err error) { + + var logPath string + var logFlags types.MaskBit + var exists bool + var success bool + var ckLogPaths []string + + if strings.TrimSpace(source) == "" { + err = errors.New("invalid source for Windows logging") + return + } + + // Configure system-supported logger(s). The Windows Event Logger (should) ALWAYS be available. + logFlags.AddFlag(LogWinLogger) + if eventIDs == nil { + eventIDs = DefaultEventID + } + + if logPaths != nil { + ckLogPaths = logPaths + ckLogPaths = append(ckLogPaths, defLogPaths...) + + for _, p := range ckLogPaths { + if exists, _ = paths.RealPathExists(&p); exists { + if success, err = testOpen(p); err != nil { + continue + } else if !success { + continue + } + logFlags.AddFlag(LogFile) + logPath = p + break + } else { + dirPath := path.Dir(p) + if err = paths.MakeDirIfNotExist(&dirPath); err != nil { + continue + } + if success, err = testOpen(p); err != nil { + continue + } else if !success { + continue + } + logFlags.AddFlag(LogFile) + logPath = p + break + } + } + } + + if logFlags.HasFlag(LogWinLogger) { + logger = &WinLogger{ + Prefix: source, + EnableDebug: enableDebug, + eids: eventIDs, + } + } else { + if logFlags.HasFlag(LogFile) { + logger = &FileLogger{ + StdLogger: StdLogger{ + Prefix: source, + EnableDebug: enableDebug, + }, + Path: logPath, + } + } else { + logger = &StdLogger{ + Prefix: source, + EnableDebug: enableDebug, + } + } + } + + logger.Setup() + + logger.Info("logger initialized of type %T with source %v", logger, logger.GetPrefix()) + + return + +} diff --git a/logging/funcs_winlogger_windows.go b/logging/funcs_winlogger_windows.go new file mode 100644 index 0000000..c12b310 --- /dev/null +++ b/logging/funcs_winlogger_windows.go @@ -0,0 +1,238 @@ +package logging + +import ( + `errors` +) + +func (l *WinLogger) Setup() { + + var err error + + /* + First a sanity check on the EventIDs. + Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000. + */ + for _, eid := range []uint32{ + l.eids.Alert, + l.eids.Crit, + l.eids.Debug, + l.eids.Emerg, + l.eids.Err, + l.eids.Info, + l.eids.Notice, + l.eids.Warning, + } { + if !(1 <= eid <= 1000) { + err = errors.New("event IDs must be between 1 and 1000 inclusive") + panic(err) + } + } + + if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { + if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil { + // It's an error we want to panic on. + panic(err) + } else { + // It already exists, so ignore the error. + err = nil + } + } + + if l.elog, err = eventlog.Open(l.Prefix); err != nil { + panic(err) + } + +} + +func (l *WinLogger) Shutdown() { + + var err error + + if err = l.elog.Close(); err != nil { + panic(err) + } + + if err = eventlog.Remove(l.Prefix); err != nil { + panic(err) + } + +} + +func (l *WinLogger) DoDebug(d bool) { + + l.EnableDebug = d + +} + +func (l *WinLogger) SetPrefix(prefix string) { + + var err error + + l.Prefix = prefix + + // To properly change the prefix, we need to tear down the old event log and create a new one. + if err = l.elog.Close(); err != nil { + panic(err) + } + + if err = eventlog.Remove(l.Prefix); err != nil { + panic(err) + } + + if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { + if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil { + // It's an error we want to panic on. + panic(err) + } else { + // It already exists, so ignore the error. + err = nil + } + } + + if l.elog, err = eventlog.Open(l.Prefix); err != nil { + panic(err) + } + +} + +func (l *WinLogger) GetPrefix() (prefix string) { + + prefix = l.Prefix + + return +} + +func (l *WinLogger) Alert(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + // Treat ALERT as Warning + err = l.elog.Warning(l.eids.Alert, msg) + + return +} + +func (l *WinLogger) Crit(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + // Treat CRIT as Error + err = l.elog.Error(l.eids.Crit, msg) + + return +} + +func (l *WinLogger) Debug(s string, v ...interface{}) (err error) { + + if !l.EnableDebug { + return + } + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + // Treat DEBUG as Info + err = l.elog.Info(l.eids.Debug, msg) + + return + +} + +func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + // Treat EMERG as Error + err = l.elog.Error(l.eids.Emerg, msg) + + return + +} + +func (l *WinLogger) Err(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + err = l.elog.Error(l.eids.Error, msg) + + return + +} + +func (l *WinLogger) Info(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + err = l.elog.Info(l.eids.Info, msg) + + return + +} + +func (l *WinLogger) Notice(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + // Treat NOTICE as Info + err = l.elog.Info(l.eids.Notice, msg) + + return + +} + +func (l *WinLogger) Warning(s string, v ...interface{}) (err error) { + + var msg string + + if v != nil { + msg = fmt.Sprintf(s, v...) + } else { + msg = s + } + + err = l.elog.Warning(l.eids.Warning, msg) + + return + +} diff --git a/logging/types.go b/logging/types.go index bba044e..9a150e3 100644 --- a/logging/types.go +++ b/logging/types.go @@ -2,7 +2,6 @@ package logging import ( "log" - "log/syslog" "os" ) @@ -22,24 +21,6 @@ type Logger interface { Shutdown() } -type SystemDLogger struct { - EnableDebug bool - Prefix string -} - -type SyslogLogger struct { - EnableDebug bool - Prefix string - alert, - crit, - debug, - emerg, - err, - info, - notice, - warning *syslog.Writer -} - type StdLogger struct { *log.Logger EnableDebug bool diff --git a/logging/types_linux.go b/logging/types_linux.go new file mode 100644 index 0000000..a352ffa --- /dev/null +++ b/logging/types_linux.go @@ -0,0 +1,23 @@ +package logging + +import ( + `log/syslog` +) + +type SystemDLogger struct { + EnableDebug bool + Prefix string +} + +type SyslogLogger struct { + EnableDebug bool + Prefix string + alert, + crit, + debug, + emerg, + err, + info, + notice, + warning *syslog.Writer +} diff --git a/logging/types_windows.go b/logging/types_windows.go new file mode 100644 index 0000000..0941d06 --- /dev/null +++ b/logging/types_windows.go @@ -0,0 +1,23 @@ +package logging + +import ( + `golang.org/x/sys/windows/svc/eventlog` +) + +type WinLogger struct { + EnableDebug bool + Prefix string + elog *eventlog.Log + eids *WinEventID +} + +type WinEventID struct { + Alert, + Crit, + Debug, + Emerg, + Err, + Info, + Notice, + Warning uint32 +} diff --git a/types/bitmasks.go b/types/bitmasks.go deleted file mode 100644 index e70d4e3..0000000 --- a/types/bitmasks.go +++ /dev/null @@ -1,64 +0,0 @@ -package types - -/* - See https://yourbasic.org/golang/bitmask-flag-set-clear/ for more information. - To use this, set constants like thus: - - const ( - OPT1 types.MaskBit = 1 << iota - OPT2 - OPT3 - // ... - ) - - type Object struct { - Opts BitMask - } - - o := Object{ - BitMask: uint8(0) - } - - o.AddFlag(OPT1) - o.AddFlag(OPT3) - - - This would return true: - o.HasFlag(OPT1) - As would this: - o.HasFlag(OPT3) - But this would return false: - o.HasFlag(OPT2) - -*/ -type BitMask interface { - HasFlag(bit MaskBit) bool - AddFlag(bit MaskBit) - ClearFlag(bit MaskBit) - ToggleFlag(bit MaskBit) -} - -// BitMasks -type MaskBit uint8 - -func (f MaskBit) HasFlag(flag MaskBit) (r bool) { - if f&flag != 0 { - r = true - } - return -} - -func (f *MaskBit) AddFlag(flag MaskBit) { - *f |= flag - return -} - -func (f *MaskBit) ClearFlag(flag MaskBit) { - *f &= flag - return -} - -func (f *MaskBit) ToggleFlag(flag MaskBit) { - *f ^= flag - return -}