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		"os"
    22		"os/user"
    23		"strconv"
    24		"strings"
    25		"sync"
    26	)
    27	
    28	type intBool struct {
    29		int
    30		bool
    31	}
    32	
    33	var (
    34		lookupMu sync.RWMutex // guards rest
    35		uidName  = map[int]string{}
    36		gidName  = map[int]string{}
    37		userUid  = map[string]intBool{}
    38		groupGid = map[string]intBool{}
    39	
    40		parsedGroups, parsedPasswd bool
    41	)
    42	
    43	func getUserFromUid(id int) string {
    44		return cachedName(id, uidName, lookupUserid)
    45	}
    46	
    47	func getGroupFromGid(id int) string {
    48		return cachedName(id, gidName, lookupGroupId)
    49	}
    50	
    51	func getUidFromName(user string) (int, bool) {
    52		return cachedId(user, userUid, lookupUserToId)
    53	}
    54	
    55	func getGidFromName(group string) (int, bool) {
    56		return cachedId(group, groupGid, lookupGroupToId)
    57	}
    58	
    59	func cachedName(id int, m map[int]string, fn func(int) string) string {
    60		// TODO: use singleflight library here, keyed by 'id', rather than this lookupMu lock,
    61		// which is too coarse.
    62		lookupMu.RLock()
    63		name, ok := m[id]
    64		lookupMu.RUnlock()
    65		if ok {
    66			return name
    67		}
    68		lookupMu.Lock()
    69		defer lookupMu.Unlock()
    70		name, ok = m[id]
    71		if ok {
    72			return name // lost race, already populated
    73		}
    74		m[id] = fn(id)
    75		return m[id]
    76	}
    77	
    78	func cachedId(name string, m map[string]intBool, fn func(string) (int, bool)) (int, bool) {
    79		// TODO: use singleflight library here, keyed by 'name', rather than this lookupMu lock,
    80		// which is too coarse.
    81		lookupMu.RLock()
    82		intb, ok := m[name]
    83		lookupMu.RUnlock()
    84		if ok {
    85			return intb.int, intb.bool
    86		}
    87		lookupMu.Lock()
    88		defer lookupMu.Unlock()
    89		intb, ok = m[name]
    90		if ok {
    91			return intb.int, intb.bool // lost race, already populated
    92		}
    93		id, ok := fn(name)
    94		m[name] = intBool{id, ok}
    95		return id, ok
    96	}
    97	
    98	// lookupMu must be held.
    99	func lookupUserToId(name string) (uid int, ok bool) {
   100		u, err := user.Lookup(name)
   101		if err == nil {
   102			uid, err := strconv.Atoi(u.Uid)
   103			if err == nil {
   104				return uid, true
   105			}
   106		}
   107		return
   108	}
   109	
   110	// lookupMu must be held.
   111	func lookupUserid(id int) string {
   112		u, err := user.LookupId(strconv.Itoa(id))
   113		if err == nil {
   114			return u.Username
   115		}
   116		if _, ok := err.(user.UnknownUserIdError); ok {
   117			return ""
   118		}
   119		if parsedPasswd {
   120			return ""
   121		}
   122		parsedPasswd = true
   123		populateMap(uidName, nil, "/etc/passwd")
   124		return uidName[id]
   125	}
   126	
   127	// lookupMu must be held.
   128	func lookupGroupToId(group string) (gid int, ok bool) {
   129		if !parsedGroups {
   130			lookupGroupId(0) // force them to be loaded
   131		}
   132		intb := groupGid[group]
   133		return intb.int, intb.bool
   134	}
   135	
   136	// lookupMu must be held.
   137	func lookupGroupId(id int) string {
   138		if parsedGroups {
   139			return ""
   140		}
   141		parsedGroups = true
   142		populateMap(gidName, groupGid, "/etc/group")
   143		return gidName[id]
   144	}
   145	
   146	// Lame fallback parsing /etc/password for non-cgo systems where os/user doesn't work,
   147	// and used for groups (which also happens to work on OS X, generally)
   148	// nameMap may be nil.
   149	func populateMap(m map[int]string, nameMap map[string]intBool, file string) {
   150		f, err := os.Open(file)
   151		if err != nil {
   152			return
   153		}
   154		defer f.Close()
   155		bufr := bufio.NewReader(f)
   156		for {
   157			line, err := bufr.ReadString('\n')
   158			if err != nil {
   159				return
   160			}
   161			parts := strings.SplitN(line, ":", 4)
   162			if len(parts) >= 3 {
   163				idstr := parts[2]
   164				id, err := strconv.Atoi(idstr)
   165				if err == nil {
   166					m[id] = parts[0]
   167					if nameMap != nil {
   168						nameMap[parts[0]] = intBool{id, true}
   169					}
   170				}
   171			}
   172		}
   173	}
Website layout inspired by memcached.
Content by the authors.