Home Download Docs Code Community
     1	/*
     2	Copyright 2012 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	package schema
    18	
    19	import (
    20		"bufio"
    21		"errors"
    22		"os"
    23		"os/user"
    24		"strconv"
    25		"strings"
    26		"sync"
    27	)
    28	
    29	type intBool struct {
    30		int
    31		bool
    32	}
    33	
    34	var (
    35		lookupMu sync.RWMutex // guards rest
    36		uidName  = map[int]string{}
    37		gidName  = map[int]string{}
    38		userUid  = map[string]intBool{}
    39		groupGid = map[string]intBool{}
    40	
    41		parsedGroups, parsedPasswd bool
    42	)
    43	
    44	func getUserFromUid(id int) string {
    45		return cachedName(id, uidName, lookupUserid)
    46	}
    47	
    48	func getGroupFromGid(id int) string {
    49		return cachedName(id, gidName, lookupGroupId)
    50	}
    51	
    52	func getUidFromName(user string) (int, bool) {
    53		return cachedId(user, userUid, lookupUserToId)
    54	}
    55	
    56	func getGidFromName(group string) (int, bool) {
    57		return cachedId(group, groupGid, lookupGroupToId)
    58	}
    59	
    60	func cachedName(id int, m map[int]string, fn func(int) string) string {
    61		// TODO: use singleflight library here, keyed by 'id', rather than this lookupMu lock,
    62		// which is too coarse.
    63		lookupMu.RLock()
    64		name, ok := m[id]
    65		lookupMu.RUnlock()
    66		if ok {
    67			return name
    68		}
    69		lookupMu.Lock()
    70		defer lookupMu.Unlock()
    71		name, ok = m[id]
    72		if ok {
    73			return name // lost race, already populated
    74		}
    75		m[id] = fn(id)
    76		return m[id]
    77	}
    78	
    79	func cachedId(name string, m map[string]intBool, fn func(string) (int, bool)) (int, bool) {
    80		// TODO: use singleflight library here, keyed by 'name', rather than this lookupMu lock,
    81		// which is too coarse.
    82		lookupMu.RLock()
    83		intb, ok := m[name]
    84		lookupMu.RUnlock()
    85		if ok {
    86			return intb.int, intb.bool
    87		}
    88		lookupMu.Lock()
    89		defer lookupMu.Unlock()
    90		intb, ok = m[name]
    91		if ok {
    92			return intb.int, intb.bool // lost race, already populated
    93		}
    94		id, ok := fn(name)
    95		m[name] = intBool{id, ok}
    96		return id, ok
    97	}
    98	
    99	// lookupMu must be held.
   100	func lookupUserToId(name string) (uid int, ok bool) {
   101		u, err := user.Lookup(name)
   102		if err == nil {
   103			uid, err := strconv.Atoi(u.Uid)
   104			if err == nil {
   105				return uid, true
   106			}
   107		}
   108		return
   109	}
   110	
   111	// lookupMu must be held.
   112	func lookupUserid(id int) string {
   113		u, err := user.LookupId(strconv.Itoa(id))
   114		if err == nil {
   115			return u.Username
   116		}
   117		var ue user.UnknownUserIdError
   118		if errors.As(err, &ue) {
   119			return ""
   120		}
   121		if parsedPasswd {
   122			return ""
   123		}
   124		parsedPasswd = true
   125		populateMap(uidName, nil, "/etc/passwd")
   126		return uidName[id]
   127	}
   128	
   129	// lookupMu must be held.
   130	func lookupGroupToId(group string) (gid int, ok bool) {
   131		if !parsedGroups {
   132			lookupGroupId(0) // force them to be loaded
   133		}
   134		intb := groupGid[group]
   135		return intb.int, intb.bool
   136	}
   137	
   138	// lookupMu must be held.
   139	func lookupGroupId(id int) string {
   140		if parsedGroups {
   141			return ""
   142		}
   143		parsedGroups = true
   144		populateMap(gidName, groupGid, "/etc/group")
   145		return gidName[id]
   146	}
   147	
   148	// Lame fallback parsing /etc/password for non-cgo systems where os/user doesn't work,
   149	// and used for groups (which also happens to work on OS X, generally)
   150	// nameMap may be nil.
   151	func populateMap(m map[int]string, nameMap map[string]intBool, file string) {
   152		f, err := os.Open(file)
   153		if err != nil {
   154			return
   155		}
   156		defer f.Close()
   157		bufr := bufio.NewReader(f)
   158		for {
   159			line, err := bufr.ReadString('\n')
   160			if err != nil {
   161				return
   162			}
   163			parts := strings.SplitN(line, ":", 4)
   164			if len(parts) >= 3 {
   165				idstr := parts[2]
   166				id, err := strconv.Atoi(idstr)
   167				if err == nil {
   168					m[id] = parts[0]
   169					if nameMap != nil {
   170						nameMap[parts[0]] = intBool{id, true}
   171					}
   172				}
   173			}
   174		}
   175	}
Website layout inspired by memcached.
Content by the authors.