github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/schema/lookup.go (about)

     1  /*
     2  Copyright 2012 Google Inc.
     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  func lookupUserToId(name string) (uid int, ok bool) {
    99  	u, err := user.Lookup(name)
   100  	if err == nil {
   101  		uid, err := strconv.Atoi(u.Uid)
   102  		if err == nil {
   103  			return uid, true
   104  		}
   105  	}
   106  	return
   107  }
   108  
   109  func lookupGroupToId(group string) (gid int, ok bool) {
   110  	if !parsedGroups {
   111  		lookupGroupId(0) // force them to be loaded
   112  	}
   113  	intb := groupGid[group]
   114  	return intb.int, intb.bool
   115  }
   116  
   117  // lookupMu is held
   118  func lookupGroupId(id int) string {
   119  	if parsedGroups {
   120  		return ""
   121  	}
   122  	parsedGroups = true
   123  	populateMap(gidName, groupGid, "/etc/group")
   124  	return gidName[id]
   125  }
   126  
   127  // lookupMu is held
   128  func lookupUserid(id int) string {
   129  	u, err := user.LookupId(strconv.Itoa(id))
   130  	if err == nil {
   131  		return u.Username
   132  	}
   133  	if _, ok := err.(user.UnknownUserIdError); ok {
   134  		return ""
   135  	}
   136  	if parsedPasswd {
   137  		return ""
   138  	}
   139  	parsedPasswd = true
   140  	populateMap(uidName, nil, "/etc/passwd")
   141  	return uidName[id]
   142  }
   143  
   144  // Lame fallback parsing /etc/password for non-cgo systems where os/user doesn't work,
   145  // and used for groups (which also happens to work on OS X, generally)
   146  // nameMap may be nil.
   147  func populateMap(m map[int]string, nameMap map[string]intBool, file string) {
   148  	f, err := os.Open(file)
   149  	if err != nil {
   150  		return
   151  	}
   152  	defer f.Close()
   153  	bufr := bufio.NewReader(f)
   154  	for {
   155  		line, err := bufr.ReadString('\n')
   156  		if err != nil {
   157  			return
   158  		}
   159  		parts := strings.SplitN(line, ":", 4)
   160  		if len(parts) >= 3 {
   161  			idstr := parts[2]
   162  			id, err := strconv.Atoi(idstr)
   163  			if err == nil {
   164  				m[id] = parts[0]
   165  				if nameMap != nil {
   166  					nameMap[parts[0]] = intBool{id, true}
   167  				}
   168  			}
   169  		}
   170  	}
   171  }