views:

684

answers:

4

I have been scouring the Internet looking for a Java package/class that will allow me to parse the UNIX /etc/group file. While it really wouldn't be so hard to write this from scratch, I'm quite surprised not to find something already out there. There is a POSIX passwd class (see http://www.bmsi.com/java/posix/docs/posix.Passwd.html), but I'm not finding a similar class for /etc/group. Does such a thing exist?

A: 

Java is machine independent then I don't get surprised for this. The class you refer is not standard.

I think that the same reason that gives to hide the /etc/group file from everyone, can give us a clue for understand the reason why java don't provide it.

FerranB
I certainly agree that it's understandable that Java doesn't provide a built in class. I was simply surprised that somewhere on the Internet such a third party class didn't exist.
Michael Ridley
+4  A: 

Heres my code that tofubeer updated that I updated again. His didn't compile. missing InvalidGroupException class. Also, no package was specified. Switched EMPTY_LIST to emptyList() to avoid lack of parameterization.

package fileutils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class GroupReader2 {
    public static class InvalidGroupException extends Exception {
     private static final long serialVersionUID = 1L;

     public InvalidGroupException(String string) {
      super(string);
     }
    }

    public static GroupReader2 parseGroup(final String groupLine)
      throws InvalidGroupException {
     final String line;
     final String[] parts;

     if (groupLine == null) {
      throw new IllegalArgumentException("groupLine cannot be null");
     }

     line = groupLine.trim();

     if (line.startsWith("#") || line.isEmpty()) {
      return null;
     }

     parts = line.split(":");

     if (parts.length < 3) {
      throw new InvalidGroupException(groupLine
        + "must be in the format of name:passwd:gid[:userlist]");
     }

     try {
      final GroupReader2 group;
      final String name;
      final String passwd;
      final int gid;
      final List<String> userList;

      name = parts[0];
      passwd = parts[1];
      gid = Integer.parseInt(parts[2]);

      if (parts.length == 4) {
       userList = Arrays.asList(parts[3].split(","));
      } else {
       userList = Collections.emptyList();
      }

      group = new GroupReader2(name, passwd, gid, userList);

      return group;
     } catch (final NumberFormatException ex) {
      throw new InvalidGroupException(groupLine + " gid must be a number");
     }
    }

    private final int gid;
    private final String name;
    private final String passwd;

    private final List<String> userList;

    public GroupReader2(final String nm, final String pw, final int id,
      final List<String> users) {
     name = nm;
     passwd = pw;
     gid = id;
     userList = Collections.unmodifiableList(new ArrayList<String>(users));
    }

    public int getGid() {
     return (gid);
    }

    public String getName() {
     return (name);
    }

    public String getPasswd() {
     return (passwd);
    }

    public List<String> getUserList() {
     return (userList);
    }

    @Override
    public String toString() {
     final StringBuilder sb;

     sb = new StringBuilder();
     sb.append(name);
     sb.append(":");
     sb.append(passwd);
     sb.append(":");
     sb.append(gid);
     sb.append(":");

     for (final String user : userList) {
      sb.append(user);
      sb.append(",");
     }

     sb.setLength(sb.length() - 1);

     return (sb.toString());
    }
}
John Ellinwood
Nice. Two minor nits: (1) Vector is ancient, consider ArrayList instead; (2) Setters are misleading since you are providing read-only access.
jdigital
A minor nit on your minor nit, ArrayList should not be the variable type, declare the variable as List instead. Also make the variables final and the set methods will not compile.
TofuBeer
@jdigital Good ideas, I've edited to make those changes.
John Ellinwood
@TofuBeer all the variables are List already, and what won't compile? i ran this against my own /etc/groups before posting.
John Ellinwood
was just responding to the nits... but make gid, name, passwd, and userlist final (and get rid of the Group() constructor). For the List, I was just responding to jdigitals nits :-)
TofuBeer
@TofuBeer Oh ok, i see, sorry. If I make the member variables final, it won't compile because parseGroup needs to set them. I'd get rid of the constructor, but somebody may want to use it to construct a groupfile instead of just reading one in.
John Ellinwood
Just provide the the constructor that takes the arguments... others can still use it.
TofuBeer
I made the changes to your code that I would make and posted it as an answer below. There is a security issue in your code the userlist can be added to after the group is created).
TofuBeer
@TofuBeer I added your updates in plus some more.
John Ellinwood
cool. Good job. I figured it was easier to just show what I meant than do it through comments, hope you don't mind :-)
TofuBeer
Wow - you guys rock! I certainly didn't expect people to go and write the class FOR me. I was just wondering if such a thing exists. Apparently now it does!
Michael Ridley
+1  A: 

In case you are going to run this program anywhere except your own machine, reading /etc/group is not a nice idea.

The real-world uses nis/ldap etc to keep all this info..

You may want to wrap getpwent / getgrent family with JNI You may get some posix implementation of java which wraps these.

Hope that helps..

Vardhan Varma
Just the kind of thing I would have suggested... after all, these C libs already know how to access the appropriate file.
R. Bemrose
+4  A: 

Here is the code John Ellinwood provided, but made safer (edit: added Johns changes, slightly differently, to keep then in sync with the comments. Good to see how two people implement the same sort of code).

I chose to throws exceptions in the case of an invalid line, you could simply return null as he originally did (I don't see the point in using a file that has wrong data...).

The only "must do" change it wrapping the userList as an UnmodifableList (or returning a new copy of the list) otherwise a malicious user of this method could add things to the userList (call getUserList and then proceed to add items to it or remove items from it).

Since the Group class is immutable (all instance variables are final) there is no fear of them being changed by a caller.

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Group
{
    private final int gid;
    private final String name;
    private final String passwd;
    private final List<String> userList;

    public static Group parseGroup(final String groupLine)
        throws InvalidGroupException
    {
        final String   line;
        final String[] parts;

        if(groupLine == null)
        {
            throw new IllegalArgumentException("groupLine cannot be null");
        }

        line = groupLine.trim();

        if(line.startsWith("#") || line.isEmpty())
        {
             return null;
        }

        parts = line.split(":");

        if(parts.length < 3)
        {
            throw new InvalidGroupException(groupLine + "must be in the format of name:passwd:gid[:userlist]", line);
        }

        try
        {
            final Group        group;
            final String       name;
            final String       passwd;
            final int          gid;
            final List<String> userList;

            name   = parts[0];
            passwd = parts[1];
            gid    = Integer.parseInt(parts[2]);

            if(parts.length == 4)
            {
                userList = Arrays.asList(parts[3].split(","));
            }
            else
            {
                userList = Collections.emptyList();
            }

            group = new Group(name, passwd, gid, userList);

            return group;
        }
        catch(final NumberFormatException ex)
        {
            throw new InvalidGroupException(groupLine + " gid must be a number", line);
        }
    }

    public Group(final String nm, final String pw, final int id, final List<String> users)
    {
        name     = nm;
        passwd   = pw;
        gid      = id;
        userList = Collections.unmodifiableList(new ArrayList<String>(users));
    }

    public int getGid()
    {
        return (gid);
    }

    public String getName()
    {
        return (name);
    }

    public String getPasswd()
    {
        return (passwd);
    }

    public List<String> getUserList()
    {
        return (userList);
    }

    @Override
    public String toString()
    {
        final StringBuilder sb;

        sb = new StringBuilder();
        sb.append(name);
        sb.append(":");
        sb.append(passwd);
        sb.append(":");
        sb.append(gid);
        sb.append(":");

        for(final String user : userList)
        {
            sb.append(user);
            sb.append(",");
        }

        sb.setLength(sb.length() - 1);

        return (sb.toString());
    }
}

public class InvalidGroupException
    extends Exception
{
     private static final long serialVersionUID = 1L;
     private final String line;

     public InvalidGroupException(final String msg, final String ln)
     {
         super(msg);

         line = ln;
     }

     public String getLine()
     {
         return (line);
     }
}
TofuBeer
Doesn't compile. missing InvalidGroupException class. Also, no package was specified. Switched EMPTY_LIST to emptyList() to avoid lack of parameterization.
John Ellinwood
package is not required (always a good practice, but I don't bother with sample code like this).
TofuBeer