Tools: hv: Fully support the new KVP verbs in the user level daemon
K. Y. Srinivasan [Fri, 16 Mar 2012 15:02:26 +0000 (08:02 -0700)]
Now fully support the new KVP messages in the user level daemon. Hyper-V defines
multiple persistent pools to which the host can write/read/modify KVP tuples.
In this patch we implement a file for each specified pool, where the KVP tuples
will be stored in the guest.

Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
Reviewed-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

tools/hv/hv_kvp_daemon.c

index a98878c..2fb9c3d 100644 (file)
@@ -39,7 +39,8 @@
 #include <ifaddrs.h>
 #include <netdb.h>
 #include <syslog.h>
-
+#include <sys/stat.h>
+#include <fcntl.h>
 
 /*
  * KVP protocol: The user mode component first registers with the
@@ -79,6 +80,250 @@ static char *os_build;
 static char *lic_version;
 static struct utsname uts_buf;
 
+
+#define MAX_FILE_NAME 100
+#define ENTRIES_PER_BLOCK 50
+
+struct kvp_record {
+       __u8 key[HV_KVP_EXCHANGE_MAX_KEY_SIZE];
+       __u8 value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
+};
+
+struct kvp_file_state {
+       int fd;
+       int num_blocks;
+       struct kvp_record *records;
+       int num_records;
+       __u8 fname[MAX_FILE_NAME];
+};
+
+static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT];
+
+static void kvp_acquire_lock(int pool)
+{
+       struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0};
+       fl.l_pid = getpid();
+
+       if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) {
+               syslog(LOG_ERR, "Failed to acquire the lock pool: %d", pool);
+               exit(-1);
+       }
+}
+
+static void kvp_release_lock(int pool)
+{
+       struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
+       fl.l_pid = getpid();
+
+       if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) {
+               perror("fcntl");
+               syslog(LOG_ERR, "Failed to release the lock pool: %d", pool);
+               exit(-1);
+       }
+}
+
+static void kvp_update_file(int pool)
+{
+       FILE *filep;
+       size_t bytes_written;
+
+       /*
+        * We are going to write our in-memory registry out to
+        * disk; acquire the lock first.
+        */
+       kvp_acquire_lock(pool);
+
+       filep = fopen(kvp_file_info[pool].fname, "w");
+       if (!filep) {
+               kvp_release_lock(pool);
+               syslog(LOG_ERR, "Failed to open file, pool: %d", pool);
+               exit(-1);
+       }
+
+       bytes_written = fwrite(kvp_file_info[pool].records,
+                               sizeof(struct kvp_record),
+                               kvp_file_info[pool].num_records, filep);
+
+       fflush(filep);
+       kvp_release_lock(pool);
+}
+
+static int kvp_file_init(void)
+{
+       int ret, fd;
+       FILE *filep;
+       size_t records_read;
+       __u8 *fname;
+       struct kvp_record *record;
+       struct kvp_record *readp;
+       int num_blocks;
+       int i;
+       int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
+
+       if (access("/var/opt/hyperv", F_OK)) {
+               if (mkdir("/var/opt/hyperv", S_IRUSR | S_IWUSR | S_IROTH)) {
+                       syslog(LOG_ERR, " Failed to create /var/opt/hyperv");
+                       exit(-1);
+               }
+       }
+
+       for (i = 0; i < KVP_POOL_COUNT; i++) {
+               fname = kvp_file_info[i].fname;
+               records_read = 0;
+               num_blocks = 1;
+               sprintf(fname, "/var/opt/hyperv/.kvp_pool_%d", i);
+               fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH);
+
+               if (fd == -1)
+                       return 1;
+
+
+               filep = fopen(fname, "r");
+               if (!filep)
+                       return 1;
+
+               record = malloc(alloc_unit * num_blocks);
+               if (record == NULL) {
+                       fclose(filep);
+                       return 1;
+               }
+               while (!feof(filep)) {
+                       readp = &record[records_read];
+                       records_read += fread(readp, sizeof(struct kvp_record),
+                                       ENTRIES_PER_BLOCK,
+                                       filep);
+
+                       if (!feof(filep)) {
+                               /*
+                                * We have more data to read.
+                                */
+                               num_blocks++;
+                               record = realloc(record, alloc_unit *
+                                               num_blocks);
+                               if (record == NULL) {
+                                       fclose(filep);
+                                       return 1;
+                               }
+                               continue;
+                       }
+                       break;
+               }
+               kvp_file_info[i].fd = fd;
+               kvp_file_info[i].num_blocks = num_blocks;
+               kvp_file_info[i].records = record;
+               kvp_file_info[i].num_records = records_read;
+               fclose(filep);
+
+       }
+
+       return 0;
+}
+
+static int kvp_key_delete(int pool, __u8 *key, int key_size)
+{
+       int i;
+       int j, k;
+       int num_records = kvp_file_info[pool].num_records;
+       struct kvp_record *record = kvp_file_info[pool].records;
+
+       for (i = 0; i < num_records; i++) {
+               if (memcmp(key, record[i].key, key_size))
+                       continue;
+               /*
+                * Found a match; just move the remaining
+                * entries up.
+                */
+               if (i == num_records) {
+                       kvp_file_info[pool].num_records--;
+                       kvp_update_file(pool);
+                       return 0;
+               }
+
+               j = i;
+               k = j + 1;
+               for (; k < num_records; k++) {
+                       strcpy(record[j].key, record[k].key);
+                       strcpy(record[j].value, record[k].value);
+                       j++;
+               }
+
+               kvp_file_info[pool].num_records--;
+               kvp_update_file(pool);
+               return 0;
+       }
+       return 1;
+}
+
+static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value,
+                       int value_size)
+{
+       int i;
+       int j, k;
+       int num_records = kvp_file_info[pool].num_records;
+       struct kvp_record *record = kvp_file_info[pool].records;
+       int num_blocks = kvp_file_info[pool].num_blocks;
+
+       if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
+               (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
+               return 1;
+
+       for (i = 0; i < num_records; i++) {
+               if (memcmp(key, record[i].key, key_size))
+                       continue;
+               /*
+                * Found a match; just update the value -
+                * this is the modify case.
+                */
+               memcpy(record[i].value, value, value_size);
+               kvp_update_file(pool);
+               return 0;
+       }
+
+       /*
+        * Need to add a new entry;
+        */
+       if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) {
+               /* Need to allocate a larger array for reg entries. */
+               record = realloc(record, sizeof(struct kvp_record) *
+                        ENTRIES_PER_BLOCK * (num_blocks + 1));
+
+               if (record == NULL)
+                       return 1;
+               kvp_file_info[pool].num_blocks++;
+
+       }
+       memcpy(record[i].value, value, value_size);
+       memcpy(record[i].key, key, key_size);
+       kvp_file_info[pool].records = record;
+       kvp_file_info[pool].num_records++;
+       kvp_update_file(pool);
+       return 0;
+}
+
+static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value,
+                       int value_size)
+{
+       int i;
+       int num_records = kvp_file_info[pool].num_records;
+       struct kvp_record *record = kvp_file_info[pool].records;
+
+       if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
+               (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
+               return 1;
+
+       for (i = 0; i < num_records; i++) {
+               if (memcmp(key, record[i].key, key_size))
+                       continue;
+               /*
+                * Found a match; just copy the value out.
+                */
+               memcpy(value, record[i].value, value_size);
+               return 0;
+       }
+
+       return 1;
+}
+
 void kvp_get_os_info(void)
 {
        FILE    *file;
@@ -315,6 +560,11 @@ int main(void)
         */
        kvp_get_os_info();
 
+       if (kvp_file_init()) {
+               syslog(LOG_ERR, "Failed to initialize the pools");
+               exit(-1);
+       }
+
        fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
        if (fd < 0) {
                syslog(LOG_ERR, "netlink socket creation failed; error:%d", fd);
@@ -389,9 +639,38 @@ int main(void)
                        }
                        continue;
 
+               /*
+                * The current protocol with the kernel component uses a
+                * NULL key name to pass an error condition.
+                * For the SET, GET and DELETE operations,
+                * use the existing protocol to pass back error.
+                */
+
                case KVP_OP_SET:
+                       if (kvp_key_add_or_modify(hv_msg->kvp_hdr.pool,
+                                       hv_msg->body.kvp_set.data.key,
+                                       hv_msg->body.kvp_set.data.key_size,
+                                       hv_msg->body.kvp_set.data.value,
+                                       hv_msg->body.kvp_set.data.value_size))
+                               strcpy(hv_msg->body.kvp_set.data.key, "");
+                       break;
+
                case KVP_OP_GET:
+                       if (kvp_get_value(hv_msg->kvp_hdr.pool,
+                                       hv_msg->body.kvp_set.data.key,
+                                       hv_msg->body.kvp_set.data.key_size,
+                                       hv_msg->body.kvp_set.data.value,
+                                       hv_msg->body.kvp_set.data.value_size))
+                               strcpy(hv_msg->body.kvp_set.data.key, "");
+                       break;
+
                case KVP_OP_DELETE:
+                       if (kvp_key_delete(hv_msg->kvp_hdr.pool,
+                                       hv_msg->body.kvp_delete.key,
+                                       hv_msg->body.kvp_delete.key_size))
+                               strcpy(hv_msg->body.kvp_delete.key, "");
+                       break;
+
                default:
                        break;
                }