Virtual WorkStation: Check-in [59ec3ffda9]
Overview
Comment:Fix template for creation and eject option for cdrom command
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | trunk
Files: files | file ages | folders
SHA1: 59ec3ffda94898ee26b63050fa34b511781ba9cc
User & Date: vitus on 2025-09-24 16:08:25.874
Other Links: manifest | tags
Context
2025-09-24
16:08
Fix template for creation and eject option for cdrom command Leaf check-in: 59ec3ffda9 user: vitus tags: trunk
2024-07-10
16:24
Run script through flake8 check-in: d7bb8aa950 user: vitus tags: trunk
Changes
Modified vws from [f94852cc76] to [7672593305].
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
55
56
57
58
59
60
61

62
63
64
65
66
67
68







-







            return None
        raise ex
    readfd, _, _ = select.select([sock], [], [], 0.1)
    if sock in readfd:
        _ = sock.recv(1024)
    return sock


def send_command(sock, command):
    """Sends monitor command to given socket and returns answer"""
    if sock is None:
        raise RuntimeError("None socket is passed to send_command")
    fcntl.flock(sock, fcntl.LOCK_EX)
    try:
        # There can be stray (qemu) prompt in the socket. Try to drain
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
78
79
80
81
82
83
84

85
86
87
88
89
90
91







-







            chunk = sock.recv(1024)
            if chunk == b"":
                raise IOError("Unexpected EOF from monitor")
            answer += chunk.decode("utf-8")
    finally:
        fcntl.flock(sock, fcntl.LOCK_UN)
    return answer


def spiceurl(sock):
    """Returns spice URI for given (as set of parsed args) VM"""
    output = send_command(sock, "info spice")
    url = None
    for line in output.split("\n"):
        if url is not None:
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
135
136
137
138
139
140
141

142
143
144
145
146
147
148
149
150
151
152
153
154
155

156
157
158
159
160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192


193
194
195
196

197
198
199

200
201
202
203
204
205
206







-
+













-





-
















-
-
















-
-




-



-







            continue
        addr_map[data[2]] = data[0]
    return addr_map


def validate_size(size):
    """Checks if size argument has proper format"""
    return re.match("\\d+[KMG]", size) is not None
    return re.match("[0-9]+[KMG]", size) is not None


def get_drives(vm_dir):
    """Return list of drive files in the VW directory"""
    result = []
    with open(vm_dir + "/start", "r", encoding="utf-8") as f:
        for line in f:
            if re.match("\\s*-drive .*", line) and line.find("media=disk") > -1:
                match = re.search("file=([^,\\s]*)", line)
                if match:
                    result.append(match.group(1))
    return result


def snapshot_mode(sock):
    """Returns True if VM is running in snapshot mode"""
    print("Entering snapshot_mode", file=sys.stderr)
    answer = send_command(sock, "info block")
    return re.search(": /tmp", answer) is not None


def read_netinfo(filename):
    """Reads network information from start script"""
    with open(filename, "r", encoding="utf-8") as f:
        for line in f:
            match = re.search("-net nic,(?:\\S*,)?macaddr=(\\S+) -net ([^, ]+)", line)
            if match:
                f = {"mac": match.group(1)}
                if match.group(2) == "user":
                    f["iface"] = "user"
                elif match.group(2) == "bridge":
                    f["iface"] = re.search("br=(\\S+)", line).group(1)
                else:
                    f["iface"] = "unknown"
                return f
    return {"iface": "unknown", "mac": "?", "ip": "?"}


def get_netinfo(sock):
    """Gets network information from the running VM"""
    answer = send_command(sock, "info network")
    match = re.search("bridge\\.0:.*,br=(\\S+).*macaddr=(\\S+)", answer, re.S)
    if match:
        return {"iface": match.group(1), "mac": match.group(2)}
    match = re.search("user.0:.*net=([^,]+).*\n.*macaddr=(\\S+)", answer)
    if match:
        return {"iface": "user", "ip": match.group(1), "mac": match.group(2)}
    match = re.search(
        "hub0port1:.*.br=(\\S+).*hub0port0:.*macaddr=(\\S+)", answer, flags=re.S
    )
    if match:
        return {"iface": match.group(1), "mac": match.group(2)}
    print(answer, file=sys.stderr)
    return {"iface": "unknown", "ip": "?", "mac": "?", "card": "?"}


#
# command implementation
#


def cmd_spiceuri(options):
    """vws spiceuri"""
    print(spiceurl(options.sock))


def fix_bridge_name(filename):
    """
    Checks if bridge listed in network configuration
    exists on this machine, and replaces it with default one
    from config, if not so
    """
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

299
300
301
302
303
304
305
240
241
242
243
244
245
246

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
292







-


















-
-




















-
+







                nxt = 1
            elif nxt == 1:
                nxt = 0
                snapshot_id = line[: line.index(" ")]
                return snapshot_id
    return None


def make_start_cmdline(options):
    """
    Append options passed from vws commandline to start script
    commandline
    """
    arg = []
    if options.cdrom:
        arg.append("-cdrom")
        arg.append(os.path.abspath(options.cdrom[0]))
    if options.snapshot:
        arg.append("-snapshot")
    if options.args:
        arg.append("".join(options.args))
    if options.password:
        os.environ["SPICE_PASSWORD"] = options.password[0]
    if arg:
        return shlex.join(arg)
    return ""


def cmd_start(options):
    """vws start"""
    if "DISPLAY" not in os.environ:
        # If cannot start GUI just don't do it.
        options.gui = False
    if options.stopped:
        arg = make_start_cmdline(options)
        cwd = os.getcwd()
        os.chdir(options.dir)
        # Check for snapshot
        snapshot_id = check_for_snapshot(options.dir)
        if snapshot_id is not None:
            arg = arg + " " + shlex.join(["-loadvm", snapshot_id])
        # Check for correct brige name
        try:
            os.stat("monitor")
        except FileNotFoundError:
            # We cannot find monitor socket. So this machine might be
            # never run on this host
            fix_bridge_name("start")
        os.system("./start%s" % arg)
        os.system("./start %s" % arg)
        os.chdir(cwd)
        time.sleep(2)
        options.sock = connect_vm(options.dir)
        if options.sock is None:
            print("VM start failed", file=sys.stderr)
            sys.exit(1)
        if snapshot_id:
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
303
304
305
306
307
308
309

310
311
312
313
314
315
316
317
318
319
320
321
322
323

324
325
326
327
328
329
330







-














-







            cmd_cdrom(options)
    uri = spiceurl(options.sock)
    if options.gui:
        os.system((config.get("tools", "viewer") + "&") % uri)
    elif not options.stopped:
        print("VM already running use uri %s" % uri, file=sys.stderr)


def cmd_stop(options):
    """vws stop"""
    print("entering cmd_stop", file=sys.stderr)
    if options.hard or snapshot_mode(options.sock):
        try:
            send_command(options.sock, "quit")
        except IOError as ex:
            # Expect IOError here
            if str(ex).find("EOF from monitor"):
                print("monitor socket closed")
            else:
                raise ex
    else:
        print(send_command(options.sock, "system_powerdown"))


def cmd_monitor(options):
    """vws monitor"""
    eol = False
    try:
        print("(qemu) ", end="")
        sys.stdout.flush()
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
350
351
352
353
354
355
356


357
358
359
360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377







-
-














-







    except KeyboardInterrupt:
        if not eol:
            print("")
        eol = True
        print("Keyboard interrupt")
    if not eol:
        print("")


def cmd_reset(options):
    """vws reset"""
    print(send_command(options.sock, "system_reset"))


def cmd_save(options):
    """vws save"""
    answer = send_command(options.sock, "savevm")
    if re.search("Error", answer):
        print(answer, file=sys.stderr)
        sys.exit(1)
    else:
        options.hard = True
        cmd_stop(options)


def cmd_cdrom(options):
    """vws cdrom"""
    if options.id is None:
        # Search for devices which could be interpreted as CDROM
        devlist = send_command(options.sock, "info block")
        idx = devlist.find("info block")
405
406
407
408
409
410
411


412

413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432

433
434
435
436
437

438
439
440
441
442
443
444
387
388
389
390
391
392
393
394
395

396
397
398


399
400
401
402
403
404
405
406
407
408
409
410
411
412
413

414
415
416
417
418

419
420
421
422
423
424
425
426







+
+
-
+


-
-















-
+




-
+







            if idx != -1:
                dev_id = dev[:idx]
            options.id = dev_id
            break
    if options.id is None:
        print("No CDROM device found among:\n" + devlist, file=sys.stderr)
        return 1
    if options.eject:
        answer = send_command(options.sock, "eject " + options.id)
    if options.file == "":
    elif options.file == "":
        print("Please specify either --eject or iso image", file=sys.stderr)
        return 1
    if options.file is None:
        answer = send_command(options.sock, "eject " + options.id)
    else:
        answer = send_command(
            options.sock, "change %s %s" % (options.id,
                                            os.path.abspath(options.file))
        )
    print(answer)
    return 0


def find_usb(options, devices):
    """Search for pattern or address given in options in the
    given list of devices.
    List should be produced by get_host_devices() or
    get_vm_devices()
    """
    if hasattr("pattern", options):
    if hasattr(options, "pattern"):
        for dev in devices:
            if re.search(options.pattern, dev[1]):
                options.address = dev[0]
                break
    elif not hasattr("address", options):
    elif not hasattr(options, "address"):
        print(
            "Address or search pattern for device " + "is not specified",
            file=sys.stderr,
        )
        options.sock.close()
        sys.exit(1)
    return options.address
569
570
571
572
573
574
575
576
577

578
579
580
581
582
583
584
551
552
553
554
555
556
557


558
559
560
561
562
563
564
565







-
-
+







def cmd_list(options):
    """vws list"""
    count = 0
    maxlen = 0
    vms = []
    for vmname, vmtype, dirname in all_vms(options.pattern):
        count += 1
        if maxlen < len(vmname):
            maxlen = len(vmname)
        maxlen = max(maxlen,len(vmname))
        if options.state:
            vms.append(make_vm_listing(vmname, dirname, vmtype))
        else:
            vms.append({"name": vmname})
    if options.state:
        add_ip_address(vms)
    for f in sorted(vms, key=lambda x: x["name"]):
645
646
647
648
649
650
651
652

653
654
655
656
657
658
659
626
627
628
629
630
631
632

633
634
635
636
637
638
639
640







-
+







        name, ext = os.path.splitext(i)
        newnames[i] = name + "." + options.snapname + ext
        if os.path.exists(newnames[i]):
            print("Snapshot %s already exists", options.snapname, file=sys.stderr)
            return 1
    for i in drives:
        os.rename(i, newnames[i])
        os.system('qemu-img create -f qcow2 -b "%s" "%s"' % (newnames[i], i))
        os.system('qemu-img create -f qcow2 -b "%s" -F qcow2 "%s"' % (newnames[i], i))
        os.chmod(i, 0o664)
    return 0


def cmd_snapshots(options):
    """vws snapshots - list existing snapshots"""
    os.chdir(options.dir)
704
705
706
707
708
709
710
711

712
713
714
715
716
717
718
685
686
687
688
689
690
691

692
693
694
695
696
697
698
699







-
+







        backing = get_backing(drive)
        if not backing:
            print("Drive %s has no snapshots" % drive, file=sys.stderr)
            continue
        # Unlink current image
        os.unlink(drive)
        # create new image with same backing file
        os.system('qemu-img create -f qcow2 -b "%s" "%s"' % (backing, drive))
        os.system('qemu-img create -f qcow2 -b "%s" -F qcow2 "%s"' % (backing, drive))
        os.chmod(drive, 0o664)


def cmd_commit(options):
    """
    Commits last snapshot changes into it's backing file
    There would be one snapshot less for virtual machine
828
829
830
831
832
833
834
835
836


837
838
839
840



841

842

843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862

863
864
865
866
867
868
869
809
810
811
812
813
814
815


816
817
818
819


820
821
822
823
824

825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844

845
846
847
848
849
850
851
852







-
-
+
+


-
-
+
+
+

+
-
+



















-
+







        if count > 0:
            time.sleep(10)


TEMPLATE = """#!/bin/sh
# Get machine name from current directory name
NAME=$(basename $(pwd))
# if remote access is enabled, then there should be
# SPICE_PASSWORD=password
# to enable remote access, create in the vm directory file named
# password with cleartext password
QEMU_AUDIO_DRV=spice
export QEMU_AUDIO_DRV
if [ -n "$SPICE_PASSWORD" ]; then
   SPICE_AUTH="password=$SPICE_PASSWORD"
if [ -f "password" ]; then
   PASSWORD_SECRET="-object secret id=password,forma=raw,file=password "
   SPICE_AUTH="password-secret=password"
else
   PASSWORD_SECRET=""
   SPICE_AUTH="disable-ticketing,addr=127.0.0.1"
   SPICE_AUTH="disable-ticketing=on,addr=127.0.0.1"
fi
SPICE_PORT=$(find_free_port 5900)
if [ "$1" = '-cdrom' ]; then
    shift
    CDROM=",file=$1"
    shift
fi
#set umask to make machine group-accessable
umask 002
{qemubinary} -name $NAME {accel} \\
-m {memory} \\
{drive} \\
{cdrom}$CDROM \\
{rtc}{net} \\
{usb} \\
{sound} \\
-chardev socket,server=on,wait=off,path=monitor,id=monitor \\
-mon chardev=monitor,mode=readline \\
-vga {vga} \\
-spice port=$SPICE_PORT,$SPICE_AUTH \\
${{PASSWORD_SECRET}} -spice port=$SPICE_PORT,$SPICE_AUTH \\
-device virtio-serial -chardev spicevmc,id=vdagent,name=vdagent \\
-device virtserialport,chardev=vdagent,name=com.redhat.spice.0 \\
-device ich9-usb-ehci1,id=usb \\
-device ich9-usb-uhci1,masterbus=usb.0,firstport=0,multifunction=on \\
-chardev spicevmc,name=usbredir,id=usbredirchardev1 \\
-device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \\
-daemonize -pidfile pid {extraargs}
886
887
888
889
890
891
892
893

894
895
896
897
898
899
900
869
870
871
872
873
874
875

876
877
878
879
880
881
882
883







-
+







    options = {
        "qemubinary": "qemu-system-x86_64",
        "accel": "-enable-kvm",
        "memory": "1024M",
        "vga": "qxl",
        "drive": "-drive media=disk,index=0,if={interface},file={image}",
        "cdrom": "-drive media=cdrom,index=2,if=ide",
        "sound": "-audio spice,id=au0,model=hda",
        "sound": "-audio driver=spice,id=au0,model=hda",
        "group": config.get("permissions", "vm_group"),
        "usb": "-usb",
        "rtc": "",
        "extraargs": '${1:+"$@"}',
    }
    macaddr = ":".join(["52"] + ["%02x" % x for x in os.urandom(5)])
    if parsed_args.shared:
931
932
933
934
935
936
937
938

939
940
941
942
943
944
945
914
915
916
917
918
919
920

921
922
923
924
925
926
927
928







-
+








    if not parsed_args.usb:
        options["usb"] = ""

    if not parsed_args.sound:
        options["sound"] = ""
    else:
        options["sound"] = "-soundhw " + parsed_args.sound
        options["sound"] = "-audio driver=spice,model=" + parsed_args.sound

    options["memory"] = parsed_args.mem
    if parsed_args.localtime:
        options["rtc"] = "-rtc base=localtime,clock=host \\\n"

    if os.path.exists(machinedir):
        if os.path.exists(os.path.join(machinedir, "start")):
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
957
958
959
960
961
962
963

964
965
966
967
968
969
970







-







        os.chdir(machinedir)
    else:
        print("Creating new image file of %s" % parsed_args.size, file=sys.stderr)
        os.chdir(machinedir)
        os.system("qemu-img create -f qcow2 %s %s" % (drivename, parsed_args.size))
    os.chmod(drivename, 0o664)
    options["drive"] = options["drive"].format(**driveopts)

    if hasattr(parsed_args, "debug") and parsed_args.debug:
        print(repr(driveopts), repr(options["drive"]))
        print(repr(options))
    with open("start", "w", encoding="utf-8") as script:
        script.write(TEMPLATE.format(**options))
    os.chmod("start", dirmode)
    # If installation media is specified vws start for new vm
1035
1036
1037
1038
1039
1040
1041
1042

1043
1044
1045
1046
1047
1048
1049
1017
1018
1019
1020
1021
1022
1023

1024
1025
1026
1027
1028
1029
1030
1031







-
+







                "AutoStartVMs": "/var/cache/vws/autostart",
            },
            "create options": {
                "net": "user",
                "size": "20G",
                "mem": "1G",
                "diskif": "virtio",
                "sound": "hda",
                "sound": "virtio",
                "arch": arch,
                "vga": "qxl",
            },
            "tools": {
                "viewer": "remote-viewer %s",
                "bridge_list": "/sbin/brctl show",
                "lsusb": "lsusb",
1206
1207
1208
1209
1210
1211
1212
1213


1214
1215
1216
1217
1218
1219
1220
1188
1189
1190
1191
1192
1193
1194

1195
1196
1197
1198
1199
1200
1201
1202
1203







-
+
+







    )
    p.add_argument(
        "file",
        nargs="?",
        default="",
        help="ISO image or special file to connect to drive",
    )
    p.add_argument("--eject", dest="file", action="store_const", const=None)
    p.add_argument("--eject", dest="eject", action="store_const",
                   const=True, default=False)
    usb = cmds.add_parser("usb", help="manage USB devices").add_subparsers(
        dest="subcommand", help="manage USB devices"
    )
    p = new_command(usb, "insert", help="attach device to the virtual machine")
    p.add_argument("pattern", help="Pattern of device name to look up in lsusb")
    p.add_argument(
        "--address", type=str, dest="address", nargs=1, help="exact address bus:device"